@State, @Binding and SwiftUI's declarative body are built on these two features.
A property wrapper packages the logic for storing a property. It is a type marked @propertyWrapper with a wrappedValue:
@propertyWrapper struct Clamped {
var wrappedValue: Int { didSet { wrappedValue = min(max(wrappedValue, 0), 100) } }
}Using @Clamped var volume = 50 makes the compiler store a hidden _volume: Clamped and route volume reads and writes to _volume.wrappedValue. A wrapper can expose a projected value through projectedValue, accessed with $volume; SwiftUI's @State uses this to hand out a Binding. Wrappers can take arguments: @Clamped(range: 0...10) var x = 5 (with a matching init(wrappedValue:range:)).
A result builder (@resultBuilder) transforms a closure's sequence of statements into a single value by calling static methods such as buildBlock, buildOptional (for if without else), buildEither (for if/else and switch) and buildArray (for for loops). SwiftUI's @ViewBuilder is a result builder: each view expression in body becomes an argument to buildBlock, which is why you list views without return or commas.
Writing your own builder is useful for DSLs: HTML generation, attributed strings, test fixtures. Keep in mind that inside a builder closure only expressions and supported control flow are allowed — arbitrary statements like guard aren't transformed.
Example
@propertyWrapper
struct Clamped {
private var value: Int
let range: ClosedRange<Int>
init(wrappedValue: Int, _ range: ClosedRange<Int>) {
self.range = range
value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
var wrappedValue: Int {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
var projectedValue: ClosedRange<Int> { range }
}
struct Speaker { @Clamped(0...10) var volume = 5 }
var s = Speaker()
s.volume = 42
print(s.volume, s.$volume) // 10 0...10
@resultBuilder enum LinesBuilder {
static func buildBlock(_ parts: String...) -> String { parts.joined(separator: "\n") }
static func buildOptional(_ part: String?) -> String { part ?? "" }
}
func text(@LinesBuilder _ make: () -> String) -> String { make() }
let showFooter = false
print(text { "Title"; "Body"; if showFooter { "Footer" } }) // Title / Body / (empty)Remember
- A property wrapper's
wrappedValuehandles get/set;$nameis its projected value - Result builders turn statement lists into one value via
buildBlock& co. @Stateand@ViewBuilderare these features in SwiftUI
Watch out for
- Putting
guardor other unsupported statements inside a result-builder closure.