@ViewBuilder: what is it, result builder for View in SwiftUI

Author: IT Sectr Published: 2026-06-24 Reading time: 7 min

@ViewBuilder is a result builder annotation in SwiftUI designed for declarative construction of View hierarchies. According to Apple Developer Documentation, 2024, @ViewBuilder transforms a block of code with multiple expressions and conditional logic into a single View type understandable to the Swift compiler. Without this annotation, it would be impossible to use the familiar declarative SwiftUI syntax with if/else and multiple elements in the body.

Key Takeaways

  • @ViewBuilder — a result builder that composes multiple Views into a composition without extra containers
  • buildBlock — wraps a sequence of expressions into a TupleView of up to 10 elements
  • buildEither — creates ConditionalContent for if/else and switch branches
  • Limitation — up to 10 elements in a single block without Group or ForEach
  • Implicit application — body is already wrapped in @ViewBuilder, custom functions require explicit annotation

What is @ViewBuilder in SwiftUI?

@ViewBuilder is an annotation that implements the result builder pattern (SE-0289), which allows SwiftUI to compose multiple Views into a single composition using declarative syntax. It automatically wraps multiple expressions, conditional constructs, and optional values into their corresponding types: TupleView, ConditionalContent, OptionalContent.

Before result builders, developers had to manually wrap elements in VStack or HStack, and use ternary operators or factory methods for conditional logic. @ViewBuilder made SwiftUI syntax concise and readable, allowing you to write code that looks like regular Swift with if/else and loops.

According to Swift Evolution SE-0289, result builders are a general mechanism not tied to SwiftUI. @ViewBuilder is one implementation of this mechanism, alongside @StringBuilder for string construction and library implementations for other DSLs. In SwiftUI, @ViewBuilder is used not only for body but also for closure parameters of containers (VStack, HStack, ZStack, List).

Difference from the Imperative Approach

In imperative UIKit, you explicitly create a UIView, configure its properties, and add it to the hierarchy via addSubview. In SwiftUI with @ViewBuilder, you declaratively describe which Views should be displayed, and SwiftUI handles the creation, updating, and removal of elements based on state changes.

How @ViewBuilder Works: Result Builder

Result builder is a Swift mechanism that transforms a sequence of expressions into a single composite value through static methods buildBlock, buildOptional, buildEither, and others. When the compiler sees the @ViewBuilder annotation, it automatically applies these methods to the code block during compilation.

swift
@resultBuilder
struct ViewBuilder {
    static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1) -> TupleView<(C0, C1)>
    static func buildIf<C>(_ c: C?) -> C?
    static func buildEither<T, F>(first: T) -> ConditionalContent<T, F>
    static func buildEither<T, F>(second: F) -> ConditionalContent<T, F>
}

buildBlock accepts 1 to 10 expressions and returns a TupleView. Each arity (number of expressions) has its own buildBlock overload: from buildBlock to buildBlock. This is why the number of elements in a single @ViewBuilder block is limited to 10.

buildEither (first/second) handles if/else constructs. Each branch is passed to the corresponding method, and the result is wrapped in ConditionalContent — a type that hides the specific branch types and provides a unified interface for SwiftUI.

Implicit @ViewBuilder Behavior

In SwiftUI, the body property is already implicitly annotated with @ViewBuilder — you don't see this annotation in code, but the compiler applies it automatically. However, for custom properties that return multiple Views, or for closure parameters, the annotation must be specified explicitly.

@ViewBuilder Limitations and How to Work Around Them

Limitation 1 — 10 elements in a block. This is the most well-known limitation of @ViewBuilder. If you need to display more than 10 elements at the same level, the compiler will produce an error. Workarounds include Group, ForEach, List, or breaking into subcomponents. Group does not add visual nesting, but each Group counts as one element.

swift
struct ManyElementsView: View {
    var body: some View {
        Group {
            Text("1"); Text("2"); Text("3")
            Text("4"); Text("5"); Text("6")
            Text("7"); Text("8"); Text("9")
        }
        Group {
            Text("10"); Text("11"); Text("12")
        }
    }
}

Limitation 2 — lack of support for certain constructs. @ViewBuilder does not support do/catch, guard, for-in (without ForEach), and other control flow constructs. For loops, use ForEach with identifiable data. For error handling, use separate Views that accept Result or optional values.

Limitation 3 — debugging complexity. When errors occur in @ViewBuilder, the compiler produces verbose messages where it's hard to find the root cause. Typical issues: type mismatches in if/else branches, exceeding the 10-element limit, or missing required buildBlock overloads.

@ViewBuilder Usage Patterns

Pattern 1: conditional display via if/else. The most common use case for @ViewBuilder. It allows showing different Views based on state without using ternary operators or factory methods.

swift
struct StatusView: View {
    var status: LoadStatus

    @ViewBuilder
    var body: some View {
        switch status {
        case .loading:
            ProgressView("Loading...")
        case .loaded(let data):
            DataView(data: data)
        case .error(let message):
            ErrorView(message: message)
        }
    }
}

Pattern 2: @ViewBuilder in function and initializer parameters. Used to create reusable containers that accept child Views via a closure. This is the standard pattern for libraries and UI components.

swift
struct SectionCard<Content: View>: View {
    let title: String
    @ViewBuilder let content: Content

    var body: some View {
        VStack(alignment: .leading) {
            Text(title).font(.headline)
            content
        }
        .padding()
        .background(Color.gray.opacity(0.1))
        .cornerRadius(12)
    }
}

Pattern 3: composition with ForEach. @ViewBuilder works correctly with ForEach, allowing dynamic generation of elements from a data array. Each ForEach element counts as one expression in the @ViewBuilder context.

Creating a Custom ViewBuilder for Reusable Components

Custom ViewBuilder is a user-defined function or property annotated with @ViewBuilder that returns some View. Such functions allow encapsulating complex display logic and reusing it across different parts of the application.

swift
struct FormRow<Content: View>: View {
    let label: String
    @ViewBuilder let content: Content

    var body: some View {
        HStack {
            Text(label)
                .frame(width: 120, alignment: .trailing)
            content
        }
    }
}

// Usage:
FormRow(label: "Name") {
    TextField("Enter name", text: $name)
}

FormRow(label: "Gender") {
    Picker("Select", selection: $gender) {
        Text("Male").tag(Gender.male)
        Text("Female").tag(Gender.female)
    }
}

Important rule: a custom function with @ViewBuilder must return some View, not a concrete type or the View protocol. Only an opaque type allows hiding the concrete implementation while preserving composition flexibility.

Performance: custom @ViewBuilder functions add no overhead compared to direct body code. The compiler inlines calls and optimizes the resulting code. Breaking body into @ViewBuilder functions improves readability without sacrificing performance.

Frequently Asked Questions

What is @ViewBuilder in SwiftUI?

@ViewBuilder is a result builder annotation that transforms a block of code with multiple expressions and conditions into a single View type. It allows using familiar Swift syntax (if/else, switch, optional expressions) inside SwiftUI's declarative UI.

Why can't you put more than 10 elements in @ViewBuilder?

The limitation stems from the buildBlock implementation — there is a separate method overload for each arity from 1 to 10. Swift does not support variadic generics, so the number of overloads is fixed. To work around this, use Group, ForEach, or subcomponents.

Do I need to explicitly specify @ViewBuilder before body?

No, the View protocol implicitly applies @ViewBuilder to the body property. However, for custom properties, methods, and closure parameters that return multiple Views, the annotation must be specified explicitly. Without it, the compiler will not be able to handle multiple expressions.

How does @ViewBuilder handle optional expressions?

For optional expressions, the buildIf method is used, which accepts an optional View and returns it if a value exists. If the value is nil, buildIf returns nil, and the element is not displayed. This allows using if let inside the body.

Can you use @ViewBuilder with switch?

Yes, since Swift 5.9 @ViewBuilder supports switch through the buildExpression method. The compiler transforms each case branch into the corresponding buildEither call. Switch support makes code more readable compared to nested if/else constructs.

Summary

  • @ViewBuilder — a result builder for declarative construction of View hierarchies in SwiftUI
  • buildBlock wraps a sequence of expressions into TupleView (up to 10 elements)
  • buildEither creates ConditionalContent for if/else and switch branches
  • buildIf handles optional expressions and if without else
  • Group and ForEach help work around the 10-element limit per block
  • Custom @ViewBuilder functions improve reusability without performance loss
  • @ViewBuilder is implicitly applied to body, but requires explicit annotation for parameters

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also