← Back to list

SwiftUI Internals: Understanding @ViewBuilder and ViewModifier

Let’s start to clear all the doubts and understand why/when we need to use these powerful UI concepts.

Rajneesh Kumar · 2026-05-18 19:26 · 1 claps · 6.4 min read
#swiftui #viewbuilder #swiftui-viewmodifier #swiftui-group
Open on Medium ↗
Wiki topics: 📱 · Mobile Development

SwiftUI Internals: Understanding @ViewBuilder and ViewModifier

Let’s start to clear all the doubts and understand why/when we need to use these powerful UI concepts.

Start from @ViewBuilder

@ViewBuilder is a Result Builder used by SwiftUI to allow multiple child views inside a closure.

Confused …😕, Let’s explore.

In the above code return statement is missing, but where are you going to add the return, if you choose Text(“A”) → Text(“B”) will be abandon. If you choose Text(“B”) → Text(“A”) will be abandon.

Two have both the Text view you need to either add inside VStack, HStack or similar container view but these container come with modifiers to apply design.

To solve this @ViewBuilder introduced, which add these views like as stack and bind in single view. 🙌 ……Error gone

A simple view binder which stitch all views that is present in closure irrespective of any view type.

@ViewBuilder Is NOT Runtime Magic

@ViewBuilder is:

  • NOT reflection
  • NOT XML rendering
  • NOT runtime parsing

It is a: Compile-time transformation

Swift rewrites:

@ViewBuilder
var body: some View {
    Text("A")
    Text("B")
}

into something conceptually like:

ViewBuilder.buildBlock(
    Text("A"),
    Text("B")
)

What is ViewBuilder Internally?

ViewBuilder itself is a type marked with:

@resultBuilder
struct ViewBuilder { }

The @resultBuilder attribute tells Swift:

“This type can transform closure syntax into builder method calls.”

Why Do We Use @?

This is a common confusion. ViewBuilder is a TYPE.

@ViewBuilder becomean ATTRIBUTE.

The @ syntax tells the compiler:

“Apply special transformation behaviour here.”

>> Core Builder Functions

A Result Builder uses special static methods.

buildBlock: Combines multiple views.

Text("A")
Text("B")

// becomes

buildBlock(
    Text("A"),
    Text("B")
)

buildEither: Handles if/else.

if isLoggedIn {
    HomeView()
} else {
    LoginView()
}

buildOptional: Handles optional branches.

if let user = user {
    Text(user.name)
}

buildArray: Handles loops.

👋 HOPE … till here some concepts must be build up.

var body: some View {}

body: is a backed by @ViewBuilder

Before iOS 14.0 body through error if you write like this

var body: some View {
    Text("A")
    Text("B")
}

1. @ViewBuilder — Using as a Components

One of the most common use cases.

struct CardView<Content: View>: View {

    let content: Content

    init(
        @ViewBuilder content: () -> Content
    ) {
        self.content = content()
    }

    var body: some View {
        VStack {
            content
        }
        .padding()
        .background(Color.gray.opacity(0.1))
        .cornerRadius(12)
    }
}

--------------------------------
Uses:

var body: some View {
  CardView {
      Text("Hello")
      Image(systemName: "star.fill")
  }
}

Why This Fails Without @ViewBuilder

Without builder:

init(content: () -> Content)

This fails:

CardView {
    Text("Hello")
    Image(systemName: "star")
}

Because normal closures can only return ONE expression.

Can This Still Works Without Builder

Yes… just need to wrap in VStack or any other container.

CardView {
    VStack {
        Text("Hello")
        Image(systemName: "star")
    }
}

Because closure returns ONE view: VStack

Variadic Parameters and SwiftUI

SwiftUI builders heavily rely on concepts similar to variadic parameters.

Example:

func log(_ items: String...) {
    print(items)
}

------------------------ Usage-------------
log("A", "B", "C")

Internally Swift converts them into arrays.

Why Variadics Exist

Mainly for:

  • cleaner APIs
  • DSL readability
  • builder syntax
  • ergonomic call sites

2. @ViewBuilder —Usingwith function

Basic Example

@ViewBuilder
func content() -> some View {
    Text("Hello")
    Image(systemName: "star")
}

This works because @ViewBuilder allows:

  • multiple views
  • conditionals
  • optional branches

inside the function body.

Without @ViewBuilder

func content() -> some View {
    Text("Hello")
    Image(systemName: "star") // ❌ Error
}

Normal functions can only return ONE expression.

Can I make content optional 🙋

let content: Content

Yes — but not directly as:

let content: Content?

because Content already conforms to View, and SwiftUI’s generic view system is designed around concrete view types, not optional generic view storage.

This usually creates problems like:

Type 'Content?' does not conform to 'View'

Correct Ways to Make Content Optional

1. Best SwiftUI Way → Use EmptyView

A standard SwiftUI pattern.

struct Card<Content: View>: View {
    let content: Content

    init(@ViewBuilder content: () -> Content = { EmptyView() }) {
        self.content = content()
    }
    var body: some View {
        VStack {
            content
        }
    }
}

Usage:

Card()

--------------------- OR -------------------------------

Card {
    Text("Hello")
}

Why This Works

EmptyView is still a valid View.

So Swift infers:

Content == EmptyView

instead of Optional<Content>.

This is how many Apple APIs behave internally.

2. Optional Generic Using Conditional Rendering

Possible, but ugly and rarely used.

struct Card<Content: View>: View {
    let content: Content?
    var body: some View {
        VStack {
            if let content {
                content
            }
        }
    }
}

But this has problems:

  • Optional generic complexity
  • Type inference issues
  • Harder APIs
  • Less SwiftUI-idiomatic

❌ Usually avoided.

3. Multiple Initialisers

Very common in SwiftUI internals.

struct Card<Content: View>: View {
    let content: Content?
  init() where Content == EmptyView {
        self.content = EmptyView()
    }
    init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }
    var body: some View {
        VStack {
            content
        }
    }
}

Usage:

Card()

--------------------- OR -------------------------------

Card {
    Text("Hello")
}

Why Apple Prefers EmptyView

SwiftUI is heavily generic + compile-time optimised.

Optional views:

Content?

introduce:

  • extra enum wrapping
  • conditional rendering paths
  • more type complexity

EmptyView is:

  • zero-cost
  • Optimised
  • compile-time friendly
  • identity-safe for diffing

Important Interview Knowledge

Optional<View> vs EmptyView

SwiftUI internally prefers:

EmptyView

NOT:

nil

because SwiftUI’s rendering system works best with concrete types.

Example from SwiftUI APIs

Many APIs conceptually behave like:

Section {
    Text("Content")
} header: {
    EmptyView()
}

instead of:

header: nil

Advanced Generic Insight

This works:

Card<EmptyView>()

because:

EmptyView: View

and Swift resolves generic specialisation at compile time.

Most Idiomatic Production Solution

struct Card<Content: View>: View {
    let content: Content
    init(
        @ViewBuilder content: () -> Content = { EmptyView() }
    ) {
        self.content = content()
    }
    var body: some View {
        VStack {
            content
        }
    }
}

This is:

  • clean
  • performant
  • SwiftUI-native
  • interview-quality
  • Apple-style API design

Common SwiftUI Usage

1. Reusable UI Functions

@ViewBuilder
func profileSection() -> some View {
    Text("Medium")
        .font(.title)
    Text("SwiftUI Concepts")
        .foregroundColor(.gray)
}

Usage:

VStack {
    profileSection()
}

2. Conditional UI

@ViewBuilder
func statusView(isPremium: Bool) -> some View {
  if isPremium {
        Text("Premium User")
    } else {
        Text("Free User")
    }
}

3. Returning Different View Types

Without builder:
------------------------------------------------------------------------------
func badge(flag: Bool) -> some View {
if flag {
        Text("Admin")
    } else {
        Image(systemName: "person")
    }
}

------------------------------------------------------------------------------
❌ Fails because return types differ.
With builder:
------------------------------------------------------------------------------

@ViewBuilder
func badge(flag: Bool) -> some View {
  if flag {
        Text("Admin")
    } else {
        Image(systemName: "person")
    }
}

------------------------------------------------------------------------------
✅ Works.

When Should You Use @ViewBuilder on Functions?

Use it when function:

  • returns UI
  • contains multiple views
  • contains conditionals
  • creates reusable view fragments

Photo by Steve A Johnson on Unsplash

Photo by Steve A Johnson on Unsplash

Now — — shift gear towards to ViewModifier

What is ViewModifier?

ViewModifier is a protocol and SwiftUI’s reusable styling/composition mechanism.

Instead of repeating:

Text("Hello")
    .padding()
    .background(Color.blue)

You encapsulate it.

Basic Custom Modifier

struct PrimaryButtonModifier: ViewModifier {
      func body(content: Content) -> some View {
        content
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(12)
    }
}

------------------------------ Usage ------------------------------------

Text("Login")
    .modifier(PrimaryButtonModifier())

Better API Style

SwiftUI-style APIs use extensions.

extension View {
func primaryButtonStyle() -> some View {
        modifier(PrimaryButtonModifier())
    }
}

------------------------------ Usage ------------------------------------

Text("Login")
    .primaryButtonStyle()

Modifier Order Matters: This is extremely important.

Example 1

Text("Hello")
    .background(Color.red)
    .padding()

Example 2

Text("Hello")
    .padding()
    .background(Color.red)

Different result.

Why? — — — — — Because every modifier creates a NEW wrapper view.

Why There Is No @ViewModifier 🤔 … Because

ViewModifier is NOT a compiler feature.

It’s just a normal protocol:

protocol ViewModifier

Unlike:

  • @ViewBuilder
  • @State
  • @Binding

which require compiler transformations.

The Most Important Distinction

@ViewBuilder

→ Creates the view tree

ViewModifier

→ Transforms the view tree

Photo by Roberto Sorin on Unsplash

Photo by Roberto Sorin on Unsplash

Now — — Let’s talk about Group

Conceptually SwiftUI’s Group looks like:

struct Group<Content: View>: View {

    let content: Content

    init(
        @ViewBuilder content: () -> Content
    ) {
        self.content = content()
    }

    var body: some View {
        content
    }
}

So:

Group {
    Text("A")
    Text("B")
}

works because:

  • Group initializer accepts a @ViewBuilder
  • builder combines child views

What Group Actually Does

Group is basically: A transparent container

  • groups views logically
  • does NOT create real layout
  • does NOT add spacing
  • does NOT stack views visually

Unlike:

VStack
HStack
ZStack

which create layout behaviour.

Are you thinking 🤔 Group looks similar to the @ViewBuilder

HUGE Important Insight

Group is NOT @ViewBuilder

Many developers confuse them.

@ViewBuilder

  • compiler syntax transformation
  • compile-time only

Group

  • an actual SwiftUI type
  • a real view container

Think Like This

@ViewBuilder

“How can multiple expressions become one view?”

Group

“How can I hold multiple child views without layout?”

“Hope things will be cleared and understand the intent behind the @ViewBuilder, ViewModifier and Group“

Photo by Alexas_Fotos on Unsplash

Photo by Alexas_Fotos on Unsplash


메타데이터
post_id
bde892da94f0
slug
swiftui-internals-understanding-viewbuilder-and-viewmodifier-bde892da94f0
url
https://medium.com/@kumarrajnees/swiftui-internals-understanding-viewbuilder-and-viewmodifier-bde892da94f0
canonical_url
https://medium.com/@kumarrajnees/swiftui-internals-understanding-viewbuilder-and-viewmodifier-bde892da94f0
author_url
https://medium.com/@kumarrajnees
status
ok
fetched_at
2026-08-08 00:02:55