I dunno about you but I distinctly remember where I was and what I was doing when SwiftUI was announced. My first reaction upon seeing Craig Federighi show how a view controller with multiple hundred lines of code could be replaced with a single 20 line view was just pure joy1.

My second reaction was: Wait, hang on, how the hell does that code even work?

This was the first ever public SwiftUI sample code, and even as — or perhaps especially as — someone who has been writing Swift for a long time, it appeared mostly alien to me.

struct Content : View {
	
	@State var model = Themes.listModel
	
	var body: some View {
		List(model.items, action: model.selectItem) { item in
			Image(item.image)
			VStack(alignment: .leading) {
				Text(item.title)
				Text(item.subtitle)
					.color(.gray)
			}
		}
	}
}

Since then I’ve gotten some answers and come up with more questions, and this post attempts to explain why and how SwiftUI views are constructed like they are.

Note: There’s one bit of syntax from that sample I’m not going to cover: The @State declaration there and the related property wrappers and types introduced for data propagation in SwiftUI. I’d recommend reading Jared Sinclair’s excellent blog post to learn more about those.

The Missing Return

Apart from the @State, the first bit of new syntax when you in that block is the some keyword, but we’re gonna keep that aside for now; we’re going to need to learn some other information first to understand why that is needed.

The next new bit of syntax then is the lack of a return. The body in the snippet is meant to return a value of the type some View, whatever that is, but the return keyword is never used.

This change was included in Swift in the open and long before WWDC, via SE-0255: Implicit returns from single-expression functions. The title is pretty descriptive as to what it does: It enables you to skip the return keyword for any functions or computed properties made of a single expression, bringing the behaviour parity to with that for closures.

Result Builders

Visually it appears similar to the skipped return, but the next bit of syntax, involving nested components such as the Image and VStack within the List, are a separate and much more complex feature altogether.

Result builders are a planned feature2 that enables developers to provide a simpler interface for creating tree like structures. It can be thought of as an implicit macro system, where any declarations within a result builder call are expanded using the rules provided by the implementer.

Every SwiftUI view which can show a bunch of other views has as part of its initialiser a parameter attributed with @ViewBuilder, enabling the custom result builder implementation in SwiftUI.

For instance, this is what the initialiser for VStack looks like:

struct VStack<Content: View>: View {
	init(
		alignment: HorizontalAlignment = .center, 
		spacing: CGFloat? = nil, 
		@ViewBuilder content: () -> Content
	)
}

The content closure there is responsible for interpreting the DSL syntax, and generating another view, of the type Content, which must be a View as well. More about this Content type later in the post.

Consider this snippet below:

VStack {
	Text("some text")
}

It is equivalent to writing out the following:

VStack {
	return ViewBuilder.buildBlock(Text("some text"))
}

Not just the implementation, but the very existence of the various buildBlock calls is abstracted out.

Control Flow

While if is the only control flow statement that currently works in result builders, support for if let, if case, switch, and for ... in loops has been added to Swift, and will thus likely be available in SwiftUI with the next release.

Each of these are used by result builder adopters via their own methods similar to buildBlock, such as buildIf, buildEither, buildArray, and so on.

This pretty much brings result builders up to par with the rest of the language — other structures like guard, repeat, etc. don’t quite mesh with the goal of creating a DSL and so are not planned — but at this point you might be wondering: What’s the point? It seems like an awful lot of work to do something that we should be able to do with just vanilla Swift.

A common refrain is that it seems like the existing Array syntax could be repurposed to fit this use case, without the need of inventing a whole new feature that needs reimplementation for common things like control flow. There has even been some confusion about the feature actually being exactly that under the hood: Arrays with trailing commas elided, as was pitched in the rejected SE-0257: Eliding commas from multiline expression lists.

There are two reasons this isn’t the case.

Firstly, Array literals also don’t have support for control flow labels as well, and they would need to be built in. Building a new feature that is designed specifically for the use case of DSLs, and can thus also be extended in more ways that Array syntax can’t, seems like a better choice.

Secondly, and more importantly: Arrays in Swift are generic over a single element type. This seems like a minor thing but it is crucial for how result builders are used in SwiftUI.

Type Information and Opaque Return Types

Unlike with an Array which requires all of its elements to be of the same type, the various ViewBuilder.build... methods operate on each individual element in the builder, meaning that not only can the types be different, that type information is retained and not erased down to a common base type.

Additionally, the custom implementations of the control flow elements aren’t a drawback but rather a massive positive, as they expose the outputs of all branches to the result builder, rather than just the successful one.

Consider this snippet below:

struct ContentView: View {
	let shouldShowImage: Bool
	
	var body: some View {
		VStack {
			if shouldShowImage {
				Image("some image")
			} else {
				Text("no image for you!")
			}
		}
	}
}

You might think that the VStack expands out to something like this:

VStack {
	if shouldShowImage {
		return ViewBuilder.buildBlock(Image("some image"))
	} else {
		return ViewBuilder.buildBlock(Text("no image for you!"))
	}
}

However that’s not quite how it works. Instead, the result looks more like this3:

VStack {
	let partialView: _ConditionalContent<Image, Text>
	if shouldShowImage {
		partialView = ViewBuilder.buildEither(first: Image("some image"))
	} else {
		partialView = ViewBuilder.buildEither(second: Text("no image for you!"))
	}
	return ViewBuilder.buildBlock(partialView)
)

This means that regardless of which code paths your code follows for a given run given the bits of data driving them, the type of all possible results is known beforehand, and additionally encoded into the VStack when the ViewBuilder output is used in its initialiser. The views you’re displaying may change, but the Content type of your VStack is always going to be the same.

This is also where the some View from above comes into play. The some means that the property returns an opaque type, meaning that while it doesn’t need to specify it, the body property is required to return a View with the exact same type every time is is called, and ViewBuilder does all the hard work of figuring out just what type it is based on your DSL.

The type of any view’s body is stored as an associated type on the View protocol known as Body. If you printed out ContentView.Body.self, here’s what the result looks like:

VStack<_ConditionalContent<Image, Text>>

A view’s Body type is a direct representation of the shape of its view tree, and it is what it returned every single time a view’s body is fetched.

This was a simple example, but consider now that your SwiftUI app is essentially one giant tree of nested views, the type of each of which is known at compile time4.

This also means that regardless of the current state of every single bit of data in your app, it has the same exact structure. The exact views being rendered may change, but the underlying shape of the entire view tree remains the same.

This is what enables SwiftUI to quickly diff your view bodies when the underlying model changes, relying on Swift’s type system rather than manual identifiers5 or other heuristics to determine the difference between the existing and the new view trees.

Conclusion

Result builders are just the gateway to understanding the world of SwiftUI, and I hope this post gives you a good understanding of the DSL and why it looks and works the way it does.

If you’d like to learn more about result builders, you can read the original draft proposal, and read this Swift forums thread to follow along with progress on the implementation.

Lastly, while result builders aren’t meant to be publicly used yet, their availability in development toolchains for all of Apple’s platforms means folks have been building a lot of cool stuff using them already, a list of which can be viewed on this GitHub repo.

Note: At the time of publication, this feature was called function builders. The article has been updated to use the new name.


  1. As documented on Twitter.

  2. The proposal for the feature hasn’t passed Swift Evolution yet, and thus isn’t part of Swift proper. Apple includes an underscored implementation just for SwiftUI in the iOS 13 toolchain, which isn’t meant to be used by 3rd parties.

  3. This code won’t actually compile if you run it, since _ConditionalContent is a private view.

  4. You can cheat the system a bit by using an AnyView, which erases all type information. However this means that diffs are much more expensive, which is why it’s recommended to avoid these if you can, and limit usage to leaf nodes as much as possible.

  5. Though those are still used when displaying a list of views using a ForEach.