Reading — step 1 of 5
Learn
~1 min readProperty Wrappers and Key Paths
A property wrapper packages get/set logic into a reusable type. SwiftUI's @State, @Binding, @Published — all property wrappers.
Definition:
@propertyWrapper
struct Clamped<Value: Comparable> {
var value: Value
let range: ClosedRange<Value>
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
var wrappedValue: Value {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
}
Usage:
struct Config {
@Clamped(0...100) var volume: Int = 50
}
var c = Config()
c.volume = 200 // capped to 100
print(c.volume) // 100
c.volume = -50 // capped to 0
print(c.volume) // 0
The wrapper handles validation; the user code stays clean.
projectedValue — secondary access via $:
@propertyWrapper
struct Logged<T> {
private var value: T
var changes: [T] = []
init(wrappedValue: T) { self.value = wrappedValue }
var wrappedValue: T {
get { value }
set {
changes.append(newValue)
value = newValue
}
}
var projectedValue: [T] { changes }
}
struct Counter {
@Logged var count: Int = 0
}
var c = Counter()
c.count = 1
c.count = 5
c.count = 10
print(c.$count) // [1, 5, 10] — accesses projectedValue
SwiftUI uses this — @State var x = 0 exposes $x as a Binding<Int> for two-way binding.
Use cases:
- Validation (
@Clamped) - Persistence (
@AppStorage,@SceneStorage) - Reactive state (
@State,@Published) - Dependency injection (
@Inject) - Lazy/cached computation
Compiler-rewrites mean wrappers have zero overhead at runtime.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…