Home Utilizing SwiftUI's ViewBuilder to Simplify Dynamic View Composition
Post
Cancel

Utilizing SwiftUI's ViewBuilder to Simplify Dynamic View Composition

TLDR

The fundamental building block of SwiftUI declarative nature that allows us to construct dynamic and conditional views in swiftUI. It allows us to pass multiple child views as parameter, compose them and then return a single view as an output.

Intro

ViewBuilders are a special type in SwiftUI that enables us to construct views dynamically. We use them as an attribute for functions or closures, allowing them to accept multiple child views and return a single view. By using ViewBuilder, we can build complex view hierarchies in a declarative and easy-to-read manner.

They are Everywhere

You might not be aware of it but you are already using ViewBuilders, because they form the fundamental building blocks for view composition in SwiftUI. From essential container views like VStack, HStack, and ZStack to versatile components like Button, the presence of ViewBuilders underscores their significance in enabling flexible view composition.

1
2
3
4
5
public protocol View {
    associatedtype Body : View

    @ViewBuilder var body: Self.Body { get }
}

Heck even our SwiftUI computed body property is a ViewBuilder. If you jump to the protocol definition of the View you will validate this to be true. They are essentially the lego blocks of SwiftUI’s declarative nature.

The Problem ViewBuilder Solves

In traditional UIKit or AppKit development, constructing complex view hierarchies often involved conditional statements and loops. This approach usually lead to verbose and error-prone code. ViewBuilder addresses this problem by offering an elegant declarative solution.

ViewBuilder enables us to create views that adapt to dynamic factors such as data changes, user interactions, device characteristics or just plain ole conditional statements. It streamlines the process of constructing and managing complex view hierarchies, making SwiftUI the concise and declarative framework we love it to be.

How ViewBuilder Solves the Problem

ViewBuilder leverages Swift’s trailing closure syntax to provide its functionality. When a function or closure is annotated with @ViewBuilder, it can accept multiple views as a trailing closure, which can then be used to construct the final view hierarchy within the function or closure.

Let’s take a look at a simple example to demonstrate how it works:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct ConditionalView<Content: View>: View {
    let condition: Bool
    let content: () -> Content
    
    init(condition: Bool, @ViewBuilder content: @escaping () -> Content) {
        self.condition = condition
        self.content = content
    }
    
    var body: some View {
        if condition {
            content()
        } else {
            EmptyView()
        }
    }
}

In this example, ConditionalView takes a condition and a closure marked with @ViewBuilder. It conditionally displays the content closure’s result based on the provided condition. By using ViewBuilder, we achieve a clean and concise way to construct conditional views.

Consequently, ViewBuilders are not just limited to structs, we can also use them on functions to encapsulate view construction logic and create reusable view components with dynamic behaviors. Here’s an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            buildText("Hello")
            buildText("ViewBuilder")
        }
    }
    
    @ViewBuilder
    func buildText(_ text: String) -> some View {
        if text.count > 5 {
            Text(text)
                .font(.largeTitle)
                .foregroundColor(.blue)
        } else {
            Text(text)
                .font(.title)
                .foregroundColor(.black)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

More Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import SwiftUI

struct CustomButton<Content: View>: View {
    private let action: () -> Void
    private let content: () -> Content
    
    init(action: @escaping () -> Void, @ViewBuilder content: @escaping () -> Content) {
        self.action = action
        self.content = content
    }
    
    var body: some View {
        Button(action: action) {
            content()
                .font(.headline)
                .padding()
                .foregroundColor(.white)
                .background(Color.blue)
                .cornerRadius(10)
        }
    }
}

struct ContentView: View {
    var body: some View {
        VStack {
            CustomButton(action: {
                print("Button tapped!")
            }) {
                Text("Tap Me")
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


In our example above, we create a custom CustomButton component that accepts an action closure and a @ViewBuilder content closure. The action closure is executed when the button is tapped.

Inside the body of CustomButton, we use the Button view and pass the action closure to its action parameter. The @ViewBuilder content closure is invoked as the button’s content, allowing us to pass any SwiftUI views into it.

In the ContentView, we demonstrate the usage of CustomButton. We provide the action closure to handle the button tap event, and pass a Text view as the content of the button.

By using ViewBuilder in the CustomButton, we achieve a flexible and reusable component that allows us to customize the button’s appearance and behavior while maintaining a clean and concise syntax.

Conclusion

The @ViewBuilder attribute in SwiftUI provides a convenient way to define the body of a view without requiring a return keyword before each individual view. It allows us to use the view builder attribute within initializers, method definitions, or in front of computed properties. By leveraging the view builder attribute, we can incorporate conditional logic, such as if-else statements or switch statements, directly into our views, resulting in more concise code and enhanced readability. This attribute simplifies the process of constructing complex view hierarchies, making our code more compact and easier to understand.

This post is licensed under CC BY 4.0 by the author.