Skip to content
Property Wrappers
step 1/5

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…