SwiftUI tips and tricks
Optional ViewBuilder closures in SwiftUI
How to create a custom view that accepts optional content through ViewBuilder closures
ViewBuilder
closures are one of the most important and widely used function builders in SwiftUI. They let you create a view out of a closure containing multiple child views.
For example, we might create a view called RedPage
which is a reusable full screen view with a red background, a main content and a header:
You can use it like this:
The result is:
Let’s make the ViewBuilder optional
Now, what if I wanted the header in the red page to be optional? I want my custom RedPage
to be called like in the example above, or just like this:
If you try the last snippet the compiler will complain that:
Generic parameter ‘Header’ could not be inferred
The compiler is right (it’s always right 😤). And I bet you’d like me to try to change the RedPage
init into the following:
Nice try. It would have worked for a standard closure, but ViewBuilder
closures are not real closures. They are function builders and the things work differently here. The compiler would say:
Function builder attribute ‘ViewBuilder’ can only be applied to a parameter of function type
No worries, we can fix this issue by leveraging the Swift where
clause and the SwiftUI EmptyView
view. The where
clause in Swift can be used for several things. In this case we’ll use it to create an extension of RedPage
that specifies a constraint on the header
type.
EmptyView
is a special kind of view in SwiftUI that fits perfect for this situation. Apple says:
[…]
EmptyView
represents the absence of a view. SwiftUI usesEmptyView
in situations where a SwiftUI view type defines one or more child views with generic parameters, and allows the child views to be absent. […]
Now everything works as expected and the result is:
Just another little tip
If you need your custom view to accept more than one optional content (for example a version of RedPage
with an optional header and an optional footer) you’ll probably end up writing:
Fortunately, Swift 5.3 allows us to write the snippet above in a more concise and meaningful way. Apple introduced the ability to attach a where
clause to functions inside generic types and extensions. Thus, you can turn the code above into: