Annotating properties with result builder attributes
Discover page available: SwiftUINew 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.