Understanding Result Builders & View Builders in SwiftUI Part 2
if you haven’t read about resultBuilders you can read about it here part1.
@ViewBuilder
, based on@ResultBuilders
, are a type of domain-specific language (DSL) that helps constitute what we call today SwiftUI. They are used to collect child views and combine them into a final View which is then returned. if we look at the definition@ViewBuilder
in detail it uses the @ResultBuilder
under the hood.
let’s see how SwiftUI uses ViewBuilder
ViewBuilder in View protocol (body of any SwiftUI View)
@ViewBuilder
is a function builder in SwiftUI that allows you to create views by composing smaller views. It is used to construct views from closures and is an integral part of SwiftUI's design, allowing for more readable and maintainable code when creating complex view hierarchies.
public protocol View {
associatedtype Body : View
@ViewBuilder var body: Self.Body { get }
}
the View
protocol uses the @ViewBuilder
attribute for its body
property. what does this mean? This means that any SwiftUI struct conforming to View
protocol can use @ViewBuilder
to define its body. so in short ViewBuilder
allows the body to create custom views that can build and combine other views to create complex layouts.
Let’s dig deeper with examples
lets look at different View builder methods in more detail
EmptyView
if we have an empty view the View builder
will use the method below to handle an empty view.
public static func buildBlock() -> EmptyView
we can verify this in the debugger and we can see the type returned is an EmptyView.
Single View
if we have a single view, the View builder
will use the method below to handle a single view.
public static func buildBlock<Content>(_ content: Content) -> Content where Content : View
we can verify this in the debugger and we can see the type returned is a Text.
MultipleViews
let’s take a VStack as an example for multiple views
if you also look into the VStack
init function definition below you will see it uses@ViewBuilder
to combine child views into one View.
The VStack
initializer takes a closure as a parameter, and that closure is marked as @ViewBuilder. This allows us to write a number of expressions inside — each of which represents a view. In essence, the closure passed to the stack builds a list of views, which become subviews of the stack in this example.
//Vstack init definition
@inlinable public init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content)
Looking at the declaration of the ViewBuilder struct, we can see the method below for handling a list of two or more views:
public static func buildBlock<each Content>(_ content: repeat each Content) -> TupleView<(repeat each Content)> where repeat each Content : View
As we can see from the return type, this constructs a TupleView wrapping our two views: the image and the text.
let's also see an example of multiple Child Views
here we can see we have every child view wrapped in a TupleView.
We can think of a view builder as a mechanism to construct a tuple view that represents lists of views.Everything is combined to be a single view at the end which the body of SwiftUI view requires.
Conditional Content
You can also use view builders to construct dynamic Views. Here’s how we can conditionally include a view:
we can see that the VStack still has two subviews: an image, and an optional text. From this view tree, SwiftUI knows that the stack will always have an image as the first subview, and perhaps a text as the second subview.
we can also use other statements — such as if let, switch, or if/else — to create conditional views:
ViewBuilder uses the method below for handling Conditional Content.
SwiftUI uses view builders in many places. All container views like stacks and grids, as well as modifiers like background and overlay, take a view builder closure to construct their subviews. Furthermore, the body property of each view is implicitly marked with
ViewBuilder
, as is thebody(content:)
method of view modifiers.
References: