Using the ViewBuilder Attribute to Implement SwiftUI Views in Methods

Posted in SwiftUI

February 25th, 2022

⏱ Reading Time: 6 mins

SwiftUI provides undeniably a funny and innovative way to create user interfaces. We can get great visual results much faster and with less pain comparing to UIKit, despite any lack of features SwiftUI might still has. Usually, we tend to break complex views in smaller components, which, of course, is a good practice; nobody really wants a huge code structure that contains everything in a view, when it’s possible to have smaller chunks or readable and manageable code.

Most of the times we achieve that by creating additional SwiftUI source files, where each one contains the implementation of a different part of the view. Although this is the way to go in the majority of cases, there are circumstances where it seems like a bit of overkill to create a new file just to keep a small portion of the view separated.

Thankfully, there is an alternative, more immediate and quick to implement, which is to implement parts of a view in methods. That is feasible thanks to a specific attribute in SwiftUI, called ViewBuilder, which according to official documentation, it is:

A custom parameter attribute that constructs views from closures.

Closures are unnamed methods; so, if we can create and return SwiftUI views from closures, we can definitely do so from methods too. The how-to is explained practically right next in this post. But in addition to that, I’m also presenting how to pass a SwiftUI view as argument to a method. Providing and getting SwiftUI views to and from methods go in pair pretty often.

A sample view to work on

For the sake of the demonstration, let’s suppose that we have a SwiftUI view that displays a list of colors, along with the respective color names.

iPhone screenshot showing six colors as a list along with their names. These are red, green, blue, yellow, purple and mint.

The following custom type represents a color item programmatically:

To keep things simple, a collection of color items is held in the view itself using a state property like so:

The actual content is implemented in the view’s body, where a scroll view embeds a ForEach container in order to show all color items existing in the colorItems array. There, a ZStack contains the color view matching to the item in the current iteration, and a text view that displays the color name on top of the color view.

A bunch of demo color items are initialized and appended to colorItems array in a separate method:

This method is called in the onAppear(_:) view modifier which is applied to the scroll view:

Implementing view parts in a ViewBuilder method

Even though the implementation shown above is simple enough and does not require many lines of code, it can still be broken into different components. For instance, we can take the ZStack along with its contents from the ForEach container, and keep it separately as a standalone view.

Instead of adding a new SwiftUI source file to the project in order to achieve that, we will implement a new method within the current view struct and source file. There are specific rules to respect:

  • We must prefix the method with the @ViewBuilder attribute.
  • The return value of the method should be some View.
  • There should be one top-level view in the method’s body only, such as a container view. In this, though, they can exist as many inner views as necessary.

With all that in mind, here’s the definition of such a method which we’ll call row(for:):

We already know what it’s going to contain; the ZStack container along with the embedded views in it:

Let’s make a pause for a moment and see what we just did; we managed to break the view’s implementation in two parts, with the second having been moved into a method! All that without adding any new files and types, effortlessly! ViewBuilder is a really useful tool, but we should not overdo with it, nor define methods that are getting too long.

Back to the view’s body, we can now update it and make use of the row(for:) method like so:

That’s definitely much cleaner and shorter!

Passing SwiftUI views as arguments to methods

In addition to what we met just right above, it’s also worth demonstrating how to provide a SwiftUI view as argument to a method. This is extremely useful when we want to define our own container views, without creating, once again, new SwiftUI source files.

In order to cover this case too, let’s add a condition to the mini demo app we are discussing about. Let’s say that we want to display color items as a vertical grid with two columns in iPad devices, and as a list in any other device. To achieve that easily, we will create a custom container view, which will be returning the one or the other depending on the case. Even though there are a couple of ways to address that new requirement, we’ll focus here on how to manage it inside the body of a method.

There is one important rule to remember when talking about SwiftUI views as arguments; the parameter value should be a closure returning a SwiftUI view. The interesting point is how this can be done, since returning a View value is not possible. For example, the following will not work:

In order to work around this, it’s necessary to make myContainer(content:) a generic method, declaring a generic type that conforms to the View protocol. The purpose of doing so is to set that generic type as the return value of the closure.

See the addition of the Content generic type to the method right below, and the requirement that Content should be conforming to View:

Inside the method’s body now we’ll add a condition where we’ll be checking the type of the current device. If it’s an iPad, then we’ll specify a vertical grid; the content will still be the listing of all color items in the ForEach:

Finally, we’ll show the SwiftUI view provided as argument using the parameter value. We’ll just call the content closure like a method.

Here’s the entire method including that:

In the view’s body again, we can now use the myContainer(content:) method as the top-level container view:

When we’ll run the app, we’ll get either a list of colors, or a two-column grid, depending on the type of the device. What makes it really interesting is that we took care of all that inside a method using the ViewBuilder attribute. And most importantly, that method works as a custom container view!

Overview

ViewBuilder attribute is a powerful tool that can help us implement parts of a view separately, but leaving aside the need to create new files and SwiftUI view types. I personally enjoy using it, but only for chunks of code that are not too large; in such cases a separate view is always the preferred solution. If you have not been using ViewBuilder in your SwiftUI views, then I would suggest to reconsider and start creating methods marked with this attribute when you see it fits. Undeniably, small units of code are easier to read, test, and update when necessary. Lastly, always keep in mind that it’s possible to provide SwiftUI views as arguments in methods; there might be times where you’ll need to do so, and it’s good to know how.

Thank you for reading, enjoy coding! ????

Stay Up To Date

Subscribe to my newsletter and get notifiied instantly when I post something new on SerialCoder.dev.

    We respect your privacy. Unsubscribe at any time.