In the last post we looked at view
modifiers as a way of
building custom UI components that use other views as a form of content.
In this one we’re going to look at a different approach using
ViewBuilder.
We’re actually going to build the exact same card structure we built before:

When we used a ViewModifier this became a .card method we applied to
the HStack like .frame or .padding, this time we’re going to make
a block construct like HStack itself.
Our goal is that the code to make character card will look like:
struct ContentView : View {
var body: some View {
Card {
HStack {
Image("brawler")
Text("Sir Bunnington")
.font(.title)
}
}
}
}
To achieve this we’ll need Card to be a View again rather than a
modifier, and we’ll use generics to handle all of the possible types of
view that the content might be:
struct Card<Content> : View
where Content : View
{
var content: Content
var body: some View {
content
.padding()
.background(Color.white)
.cornerRadius(8)
.shadow(radius: 4)
}
}
This approach in general is useful for views where there is a single
view that needs to be passed in to the constructor. Good examples of
this being used in SwiftUI include NavigationLink for the
destination parameter.
But when we need a more complex child layout we don’t always want to
have to abstract it into a custom View structure, and instead want to
be able to use a block in at that point.
This is where view builders come in. @ViewBuilder is an attribute that
we can declare on the parameters of methods we define, and most
usefully, that includes the constructor.
So we can extend our above example to set the value of content from a
view builder:
struct Card<Content> : View
where Content : View
{
var content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
content
.padding()
.background(Color.white)
.cornerRadius(8)
.shadow(radius: 4)
}
}
The builder is a method that takes no arguments and returns a view,
which we allow type inference to define as the Content our type is
generic over. We set the property of that type by calling the method,
which invokes the block passed.
And now we can use the Card view exactly as we intended in our goal
code.
Patterns
The decision about whether to use a view, view modifier or a view builder is ultimately going to come down to what makes the most sense for your code.
Some like Button make sense as view builders.
But there are good examples in SwiftUI of view modifiers that instinct
might suggest be view builders. .frame is a modifier on a view, there
is no Frame builder, even though it’s placing the view inside it.
Only once you start combining multiple frames does it makes sense why it’s a modifier, since it’s easier to combine modifiers than it is to combine builders.
A good rule of thumb for me has to be to use a modifier first, and only use a builder when the code patterns really pulled strongly for that syntax.