How to use ViewBuilder in SwiftUI

Issue #767

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

It is available in body and most SwiftUI modifiers

1
2
3
4
5
6
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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.

1
2
3
4
5
6
7
8
9
10
11
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

1
2
3
4
5
6
7
8
9
10
11
12
13
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

1
2
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

1
2
3
4
5
6
7
8
9
10
11
12
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

1
2
3
4
5
6
7
8
9
10
11
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct MessageView: View {
let showsHUD: Bool

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

Comments