SwiftUI is a game-changer for building user interfaces across Apple platforms. One of the most powerful tools it offers is @ViewBuilder
, a specialized result builder that transforms closures into SwiftUI views. If you’ve been working with SwiftUI for a while, you’ve likely encountered @ViewBuilder
, but you might still be wondering: What exactly does it do? And more importantly, how should it be used in different scenarios?
In this post, we’ll explore @ViewBuilder
from the ground up, dissect its implementation, and dive into two key methodologies for using it in SwiftUI views—embedding it as a property vs. using it in the initializer. Along the way, we’ll unpack the differences between these approaches and help you choose the right one for your SwiftUI apps.
What is @ViewBuilder
?
At its core, @ViewBuilder
is a result builder—a Swift feature that enables functions or closures to return multiple views in a declarative way. Introduced with SwiftUI, @ViewBuilder
allows us to group multiple View objects together in a closure without needing to explicitly return them as a Tuple
or Array
.
Normally, Swift functions return a single value, but SwiftUI views are often composed of multiple subviews. That’s where @ViewBuilder
comes in—it enables SwiftUI to “build” complex hierarchies of views from within a single closure.
Here’s a simple example of @ViewBuilder
in action:
@ViewBuilder
func buildWelcomeView() -> some View {
Text("Hello, Mr. Skywalker!")
Button("Click me") {
print("Button clicked")
}
}
Without @ViewBuilder
, you’d need to return these views in a tuple or encapsulate them in a container like a VStack
or HStack
. But @ViewBuilder
allows Swift to treat these multiple views as a cohesive group, simplifying how we construct UI components.
@ViewBuilder
in SwiftUI Custom Views
Using @ViewBuilder
becomes particularly useful when creating custom SwiftUI components that require flexible, user-provided content. You can declare content as a ViewBuilder-enabled closure, giving developers the freedom to inject various view hierarchies into your component.
But what’s the best way to integrate @ViewBuilder into your custom SwiftUI views? There are two common methodologies, each with its own nuances: using @ViewBuilder
as a property or embedding it within the initializer.
Approach #1: @ViewBuilder
as a Property
In this first approach, we apply @ViewBuilder
directly to a property:
struct ContainerOneView<Content: View>: View {
@ViewBuilder var content: () -> Content
var body: some View {
VStack {
content()
}
}
}
Here, the content is passed as a closure, which is evaluated whenever the body
of the view is recomputed. This is a dynamic approach—every time SwiftUI needs to re-render the view (due to state changes or other triggers), it will call content()
again, building a fresh set of views.
🙋♀️ Why Use This Approach?
This methodology is highly flexible and dynamic. The content closure gets evaluated every time the view is updated, meaning it can adapt to changes in state or environment. It allows your custom view to change its child views dynamically, which can be useful in reactive UI updates.
For example, if your ContainerOneView
is inside a View
that responds to user input, SwiftUI will rebuild the content every time the state changes, ensuring that the UI remains in sync with the data.
Approach #2: @ViewBuilder
in the Initializer
In contrast, the second approach moves @ViewBuilder
to the initializer, where the content is evaluated once and stored as a value:
struct ContainerTwoView<Content: View>: View {
private let content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
VStack {
content
}
}
}
This design stores the result of the @ViewBuilder
closure in a property during initialization. As a result, the content
is fixed once the view is created, and no further evaluation of the closure occurs during subsequent body
re-computations.
🙋 Why Use This Approach?
This approach is ideal when your view’s content is static—meaning it doesn’t change dynamically during runtime. The performance benefit comes from not needing to re-evaluate the closure on each update cycle. If the content remains constant throughout the lifecycle of the view, this method can reduce unnecessary overhead and make your app run more efficiently.
Key Differences Between the Two Approaches
Both approaches leverage @ViewBuilder
, but they differ in when and how often the content
is evaluated.
-
Reactivity
-
Dynamic (Property): In the first approach, the closure is evaluated each time the body is computed. This means the content can change based on runtime conditions, making it suitable for dynamic views.
-
Static (Initializer): In the second approach, the content is evaluated once during initialization. The content is then stored as a constant and does not change unless the view is re-initialized.
-
-
Use Case
-
Dynamic Content: If the content needs to update frequently, such as when responding to state changes, the property approach is better.
-
Static Content: When the content remains constant and doesn’t need to be dynamically rebuilt, using the initializer is more efficient.
-
-
Performance
-
Dynamic (Property): The overhead of re-evaluating the closure on each view update can be higher, but this is necessary when dealing with dynamic content.
-
Static (Initializer): This approach can improve performance by storing a pre-built view, which is faster for views that don’t need to change.
-
Conclusion
Understanding the subtle differences between using @ViewBuilder
as a property versus using it in an initializer can significantly impact how your SwiftUI app behaves, both in terms of flexibility and performance.
When your content is dynamic and needs to react to state changes, applying @ViewBuilder
as a property gives you the power to rebuild the view as needed. However, when your content is static and doesn’t need frequent updates, embedding @ViewBuilder
in the initializer offers a more efficient approach.
Mastering this balance will help you craft better-performing, more responsive SwiftUI apps that take full advantage of Swift’s declarative UI framework.
👨🏻💻 Happy coding!