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 uses EmptyView 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:

Passionate iOS developer for almost six years. SwiftUI/Combine early adopter. Author of SwiftUI NavigationStack https://bit.ly/2H6YjQS

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store