Articles, podcasts and news about Swift development, by John Sundell.

Annotating properties with result builder attributes

Published on 07 Apr 2021
Discover page available: SwiftUI

New in Swift 5.4: Result builder attributes can now be attached directly to closure-based properties, which can make it much easier to write things like custom SwiftUI containers, and other code that utilizes Swift’s result builders feature.

Let’s start by taking a look at an example from “Creating custom SwiftUI container views”, in which we’ve built our own SwiftUI container view that renders a horizontal stack of subviews within a ScrollView.

Since we want to be able to use the full power of SwiftUI’s DSL when creating instances of that container view, we’ve given it an initializer that takes a closure marked with SwiftUI’s @ViewBuilder attribute, which in turn lets us use it the exact same way as we’d use the built-in containers that SwiftUI ships with:

struct Carousel<Content: View>: View {
    var content: () -> Content

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

    var body: some View {
        ScrollView(.horizontal) {
            HStack(content: content).padding()
        }
    }
}

Starting in Swift 5.4, though, we no longer need that explicit initializer, and can instead directly mark our content property itself with the @ViewBuilder attribute — like this:

struct Carousel<Content: View>: View {
    @ViewBuilder var content: () -> Content

    var body: some View {
        ScrollView(.horizontal) {
            HStack(content: content).padding()
        }
    }
}

Really nice! Even with the above change in place, we can keep using our Carousel view just like before, thanks to Swift’s memberwise initializers feature.

This change also applies to any custom result builders that we’ve defined as well. For example, in “A deep dive into Swift’s result builders”, we built a SettingsGroup type using a custom result builder called SettingsBuilder. Before Swift 5.4, we’d once again have to manually implement that type’s initializer in order to be able to use our result builder within its settings closure:

struct SettingsGroup {
    var name: String
    var settings: () -> [Setting]

    init(name: String,
         @SettingsBuilder settings: () -> [Setting]) {
        self.name = name
        self.settings = settings
    }
}

But now we can refactor the above type to instead simply look like this:

struct SettingsGroup {
    var name: String
    @SettingsBuilder var settings: () -> [Setting]
}

Granted, this is a very small change in the grand scheme of things, but a very welcome one if you ask me. Also, the fact that result builder attributes now behave the same way that property wrapper ones do is definitely a big win in terms of consistency.