Issue #767

SwiftUI ’s ViewBuilder is a custom parameter attribute that constructs views from closures.

It is available in body and most SwiftUI modifiers

public protocol View {
    associatedtype Body : View
    @ViewBuilder var body: Self.Body { get }
}

public func contextMenu<MenuItems>(@ViewBuilder menuItems: () -> MenuItems) -> some View where MenuItems : View

In these ViewBuilder enabled places we can perform conditional logic to construct views. For example here in our SampleView, we have switch statement in body

struct SampleView: View {
    enum Position {
        case top, bottom, left, right
    }

    let position: Position

    var body: some View {
        switch position {
        case .top:
            Image(systemName: SFSymbol.person.rawValue)
        default:
            EmptyView()
        }
    }

    var profile: some View {
        if true {
            return Image(systemName: SFSymbol.person.rawValue)
        }
    }
}

ViewBuilder applies to both property and function. If we want to have the same logic style as in body in our custom property or methods, we can annotate with ViewBuilder. This works like magic, SwiftUI can determine the types of our expression.

extension SampleView {
    @ViewBuilder
    func profile2(position: Position) -> some View {
        switch position {
        case .top:
            Image(systemName: SFSymbol.person.rawValue)
        default:
            EmptyView()
        }
    }
}

Use ViewBuilder to construct View

We can use ViewBuiler as our parameter that constructs View. For example we can build an IfLet that construct View with optional check.

public struct IfLet<T, Content: View>: View {
    let value: T?
    let content: (T) -> Content

    public init(_ value: T?, @ViewBuilder content: @escaping (T) -> Content) {
        self.value = value
        self.content = content
    }

    public var body: some View {
        if let value = value {
            content(value)
        }
    }
}

With ViewBuilder we can apply logic inside our closure

struct EmailView: View {
    let email: String?

    var body: some View {
        IfLet(email) { email in
            if email.isEmpty {
                Circle()
            } else {
                Text(email)
            }
        }
    }
}

Use ViewBuilder where we can’t use closure

In some modifers like overlay, SwiftUI expects a View, not a closure that returns a View. There we cannot use additional logic

extension View {
    @inlinable public func overlay<Overlay>(_ overlay: Overlay, alignment: Alignment = .center) -> some View where Overlay : View

The below won’t work as we can’t do conditional statement in overlay modifier

struct MessageView: View {
    let showsHUD: Bool

    var body: some View {
        Text("Message")
            .overlay(
                if showsHUD {
                    Text("HUD")
                }
            )
    }
}

But we can make something like MakeView that provides a ViewBuilder closure

public struct MakeView<Content: View>: View {
    let content: Content

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

    public var body: some View {
        content
    }
}

So we can use a conditional statement in any modifier that does not accept ViewModifier

struct MessageView: View {
    let showsHUD: Bool

    var body: some View {
        Text("Message")
            .overlay(
                MakeView {
                    if showsHUD {
                        Text("HUD")
                    }
                }
            )
    }
}