SwiftUI under the Hood: Variadic Views

Matching SwiftUI’s view APIs in their ergonomics is hard to get right. In this post we’ll learn how to write view APIs that feel truly native to the platform.

By The Moving Parts Team on

SwiftUI is a highly expressive framework for writing UI code. Using the View­Builder result builder, we simply write out the views in a declarative manner one after another and have the result builder take care of creating the necessary Tuple­Views:

let stack: VStack<TupleView<(Text, Text)>> = VStack {
    Text("First")
    Text("Second")
}

It just works™, as they say. But zooming in on Tuple­View, we notice something interesting: it is transparent to view modifiers!

Let’s manually create a Tuple­View and invoke a view modifier on it:

let tupleView = ViewBuilder.buildBlock(
    Text("First"), Text("Second")
)

tupleView.border(.blue)

Somebody new to SwiftUI might expect the blue border to be drawn around the combined shape of the two views, but this isn’t the case. Instead, each member of the tuple receives its own border.

We can nest tuple views and observe that this behavior composes:

// TupleView<(Text, Text)>
let inner = ViewBuilder.buildBlock(
    Text("First"), Text("Second")
)

// TupleView<(TupleView<(Text, Text)>, Text)> let outer = ViewBuilder.buildBlock( inner, Text("Third") )
outer.border(.blue)

Even more curious, if we wrap this nested tuple in a List, we can see that the resulting view is able to inject list row separators between each individual view:

let inner = ViewBuilder.buildBlock(
    Text("First"), Text("Second")
)

let outer = ViewBuilder.buildBlock( inner, Text("Third") )
List { outer }

The same is true if we use the more common Group. However, if we were to use e.g. VStack to wrap the three Text views, the result is only a single cell.

So evidently, there are views that can be “disassembled” like Tuple­View and Group and those that present themselves as a single element to their parent, like VStack.

This poses two questions:

  • How can we modify the individual children of a View like List?

  • How can we implement a view like Group or Tuple­View that is open to these adjustments?

Spelunking through SwiftUI, you might come across some interesting bits of underscored API. For example, the _print­Changes helper can be extremely valuable when debugging your code, but there is more!

If we take a look at the interface presented by VStack, some interesting bits stand out:

@frozen
public struct VStack<Content> : View where Content : View {
    @usableFromInline
    internal var _tree: _VariadicView.Tree<_VStackLayout, Content>

@inlinable public init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content) { _tree = .init( root: _VStackLayout(alignment: alignment, spacing: spacing), content: content()) }
public typealias Body = Swift.Never }

Because its initializer is @inlinable, it is effectively available as source code. The @inlinable attribute allows the compiler to replace calls in your code with the body of the method, in this case creating a _Variadic­View.Tree that receives a _VStack­Layout as well as the Content the VStack is parameterized with.

_VStack­Layout doesn’t make a big secret about its functionality: it’s the brains of VStack. Given how big of a share stack views are likely going to have in an app, enabling this optimization makes sense. Since VStack is able to layout its children and access their layout­Priority, it requires the ability to address them individually.

Let’s take a look at _Variadic­View.Tree next. Again, we notice @inlinable optimizations at work:

public enum _VariadicView {
  @frozen
  public struct Tree<Root, Content> where Root : _VariadicView_Root {

public var root: Root
public var content: Content
@inlinable internal init(root: Root, content: Content) { self.root = root self.content = content }
@inlinable public init(_ root: Root, @ViewBuilder content: () -> Content) { self.root = root self.content = content() } } }

The _Variadic­View.Tree is parameterized by a type conforming to _Variadic­View_Root as well as Content.

We’ve already seen that Content may conform to View in the previous example and in fact, if Content does, so does _Variadict­View.Tree:

extension _VariadicView.Tree : View where Root : _VariadicView_ViewRoot, Content : View {}

This protocol in turn looks like so:

public protocol _VariadicView_ViewRoot : _VariadicView_Root {
  associatedtype Body : SwiftUI.View

@ViewBuilder func body(children: _VariadicView.Children) -> Body }

If you squint a little, this looks a lot like Button­Style and similar protocols. And _Variadic­View.Children sounds like it might be a collection of the view’s children?

Let’s do a quick recap:

  • A _Variadic­View.Tree takes a Root and the results of a View­Builder.

  • The Root in turn produces a View from a list of children.

  • If the Tree’s Content conforms to View, so does the Tree itself.

Now that we know just enough to be dangerous, we can get our hands dirty and see how far get.

Writing our own container view

Let’s try writing our own container view in the vein of List and VStack that inserts a Divider between every view.

First we need to set up an initializer that takes a @View­Builder:

struct DividedVStack<Content: View>: View {
    var content: Content

init(@ViewBuilder content: () -> Content) { self.content = content() } }

For the body of this view, we’ll instantiate a _Variadic­View.Tree with a Divided­VStack­Layout and our content:

struct DividedVStack<Content: View>: View {
    var body: some View {
        _VariadicView.Tree(DividedVStackLayout()) {
            content
        }
    }
}

For the Divided­VStack­Layout, we’ll follow _VStack­Layout’s lead and conform to _Variadic­View_Unary­View­Root.

We’ll also have to implement body(children:). Because _Variadic­View.Children conforms to View itself, the following already compiles:

struct DividedVStackLayout: _VariadicView_UnaryViewRoot {
    @ViewBuilder
    func body(children: _VariadicView.Children) -> some View {
        children
    }
}

However, it doesn’t stop there. _Variadic­View.Children is also a Random­Access­Collection and its Element conforms both to View as well as Identifiable.

This makes _Variadic­View.Children an ideal candidate for use with a For­Each. We add a Divider after every element while we’re at it. For layout, we can rely on VStack.

Finally, we want to omit the last Divider. To do that, we can make a note of the last id in the collection and skip the Divider once we’ve reached it:

struct DividedVStackLayout: _VariadicView_UnaryViewRoot {
    @ViewBuilder
    func body(children: _VariadicView.Children) -> some View {
        let last = children.last?.id

VStack { ForEach(children) { child in child
if child.id != last { Divider() } } } } }

Using the resulting View feels right at home with the rest of SwiftUI:

DividedVStack {
    Text("First")
    Text("Second")
    Text("Third")
}

We also wanted to build a view that would be transparent to view modifiers or container views. If we try this with our Divided­VStack, we notice it behaves like a single view – much like VStack.

DividedVStack {
    Text("First")
    Text("Second")
    Text("Third")
}
.border(.blue)

This is where the Unary in _Variadic­View_Unary­View­Root comes into play. Trees whose Root conforms to this protocol act like a single view rather than a list of views. Luckily, the equivalent _Variadic­View_Multi­View­Root protocol exists as well.

Trees whose Root conforms to _Variadic­View_Multi­View­Root aren’t limited in this way. Armed with this protocol, we can separate the layout-aspect of our Divided­VStack from injecting the dividers:

struct Divided<Content: View>: View {
    /* … */

var body: some View { _VariadicView.Tree(DividedLayout()) { content } } }
struct DividedLayout: _VariadicView_MultiViewRoot { @ViewBuilder func body(children: _VariadicView.Children) -> some View { let last = children.last?.id
ForEach(children) { child in child
if child.id != last { Divider() } } } }

Now we can apply view modifiers to all elements of the view and still determine the layout freely:

HStack {
    Divided {
        Text("First")
        Text("Second")
        Text("Third")
    }
    .border(.blue)
}

We hope you found this peek under the hood of SwiftUI insightful, even though these APIs aren’t exactly public.

The ability to write flexible container views that compose well is crucial to maintain design systems at scale and we hope Apple will announce something similar – if not better! – this WWDC.

Until then, Moving Parts can help your company leverage SwiftUI today by offering you a powerful library of components that seamlessly blend in with the rest of the framework and make your engineering team more productive. Don't hesitate to reach out to request a demo.

If you like to follow along with our work and be kept up to date with future posts like this, consider following us on Twitter or joining our mailing list.