Skip to content
AgileLogics
6 min read

Property Wrappers and Result Builders

Prerequisites: Generics, Closures

Two Swift features let library authors create tidy, declarative APIs. You use them constantly in SwiftUI (@State, VStack { ... }), so it's worth knowing how they work.

Property wrappers

A property wrapper packages the logic for how a property is stored and accessed, so it can be reused with a single annotation:

@propertyWrapper
struct Clamped {
    private var value: Int
    let range: ClosedRange<Int>

    init(wrappedValue: Int, _ range: ClosedRange<Int>) {
        self.range = range
        self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
    }

    var wrappedValue: Int {
        get { value }
        set { value = min(max(newValue, range.lowerBound), range.upperBound) }
    }
}

struct Speaker {
    @Clamped(0...10) var volume = 5
}

var speaker = Speaker()
speaker.volume = 15
print(speaker.volume)
speaker.volume = -3
print(speaker.volume)
10
0
  • @propertyWrapper marks the type; wrappedValue is what the property reads and writes.
  • @Clamped(0...10) var volume = 5 calls Clamped(wrappedValue: 5, 0...10).
  • A wrapper can also expose a projected value, accessed with $ — that's how SwiftUI's $name produces a binding from @State var name.

Result builders

A result builder turns a closure containing a list of expressions into a single combined value:

@resultBuilder
struct ListBuilder {
    static func buildBlock(_ items: String...) -> [String] {
        items
    }
}

func shoppingList(@ListBuilder _ content: () -> [String]) -> [String] {
    content()
}

let list = shoppingList {
    "Milk"
    "Eggs"
    "Bread"
}
print(list)
["Milk", "Eggs", "Bread"]

Each line in the closure becomes an argument to buildBlock. Builders can also support if, switch, and for by adding methods such as buildOptional, buildEither, and buildArray.

SwiftUI's @ViewBuilder is a result builder — which is why you can list views one after another inside a VStack without commas or arrays.

When to write your own

You'll use both features constantly, but write your own rarely. A property wrapper earns its keep when the same property logic repeats across many types; a result builder when you're designing a DSL for others to use.