SwiftUI: Customizing differentView layouts with ResultBuilders

Pedro Alvarez
5 min readJun 9, 2023

--

Image from https://wallpapersafari.com/wallpapers-lake/

In SwiftUI there are multiple times when you declare a type of View in your interface that contains some subviews that are visually arranged in some way. VStack , HStack and ZStack are the most classical examples since they take some View parameters and place them linearly across some axis(x, y or z), but there are other cases such as a List , which works like a UITableView ,reusing its cells from the memory, and transforms each of its subviews into vertically positioned cells with some divider or a specific layout depending on the listStyle modifier parameter or even a SwiftUI ScrollView , which takes its subviews, place them linearly and make them scrollable.

You may be wondering, "Hey, what is the magic? It receives totally separated Viewparameters and builds a new layout with them. How is that possible?". In order to understand this complex topic, we need to jump even deeper in the SwiftUI architecture and see how it parses the different parameters a component receives in order to transform it into a new one. Before you ask, no, the closure bellow is not a View itself:

Result Builders

Ok, let's talk about resultBuilders . What are they? Basically it is a property wrapper that receives a closure syntax with some listed parameters and them return a single new output as a result to some computation the resultBuilder class does. The input closure that lists the parameters is known as block. What this property wrapper really does is calling a function known as buildBlock that parses the parameters of the block and returns a new result of any type, but in our case, we will be returning some View :

Let's explain what's happening with some code. We will create a very simple View representing a cell that may receive a block of parameters that may consist of four scenarios:

  1. Two vertically aligned labels: a header and a subheader
  2. Two vertically aligned labels and a leading view: a header, a subheader and a leadingView
  3. Two vertically aligned labels and a trailing view: a header, a subheader and a trailingView
  4. Two vertically aligned labels plus a leading view and a trailing view: a header, a subheader, a leadingView and a trailingView

For that, we need four implementations of a buildBlock including these types of inputs.

Creating our CellBuilder

Let's create a new enum that will be our resultBuilder and implement our block parsing functions:

In order for it to work and parse the blocks it receives, we need to implement an overloading method called buildBlock :

Basically what is happening is that each listed parameter of the CellBuilder wrapper closure is being passed to the buildBlock function, which returns a new View type. Here is the usage of it:

We are tagging the closure that returns a new View with the CellBuilder property wrapper, and when we call the closure, it transforms the parameters into the new View and inserts it in the CellView body.

Preview of our CellView

As a result, the resultBuilder will parse the parameters it receives into a new View and will insert it in the body. The buildBlock may contain multiple types of parameters but we need to handle and implement each , let's check other cases:

Adding trailing and leading views to the cell

Let's suppose we now want to receive in our block besides the two strings a leading view, or a trailing view or maybe both. We need to implement three other variations:

And we can now call our View this way:

Also, if you have some variations for your resultBuilder block that have the same inputs as other ones just with one less parameters, as in any other Swift function, you can make that parameter optional by providing a default value. Imagine you don't need the subheader in every case, so you can provide it as an optional and pass nothing as this parameter and have an if let verification:

This is the same as if you had a custom block that only received the header string and just placed it in the VStack .

Dealing with ambiguities

Now imagine you have another variation for the block that instead of the leading and trailing views, you have some leading and trailing Texts :

Text conforms to the View protocol, so which block would be called if we rendered this?

The answer is: Despite Text being a View type, the concrete type referring to Text has more priority than a generic one that conforms to View , so we are calling the block that receives Text .

Conclusion

Apple provided a great shortcut for customizing different View layouts just by passing some parameters within a closure syntax. With that you can manage complex and different contexts just by defining the types of parameters you are expecting to your component and them mapping the parameter types into the respective block builders to result in different layouts. This makes SwiftUI an even more powerful tool and improves the reusability of your code. I hope this helps you simplify your Views and that you enjoyed ;).

--

--

Pedro Alvarez
Pedro Alvarez

Written by Pedro Alvarez

Mobile Engineer | iOS | Android | KMP | Flutter | WWDC19 scholarship winner | Blockchain enthusiast https://www.linkedin.com/in/pedro-alvarez94/

No responses yet