Skip to content
Property Wrappers
step 1/6

Reading — step 1 of 6

Learn

~2 min readProtocol-Oriented Programming

Property wrappers (Swift 5.1+) let you separate the storage of a property from its access pattern. They're how SwiftUI's @State, @Binding, @Published work — and a powerful tool for any cross-cutting property logic.

The basic shape

@propertyWrapper
struct Trimmed {
    private var value: String = ""
    var wrappedValue: String {
        get { value }
        set { value = newValue.trimmingCharacters(in: .whitespaces) }
    }
    
    init(wrappedValue: String) {
        self.value = wrappedValue.trimmingCharacters(in: .whitespaces)
    }
}

struct User {
    @Trimmed var name: String
}

var u = User(name: "   Alice   ")
print(u.name)        // "Alice"
u.name = "  Bob  "
print(u.name)        // "Bob"

The wrapper intercepts get/set. Anyone using name writes plain assignments; the wrapper applies the trimming silently.

Validating values

@propertyWrapper
struct Clamped<T: Comparable> {
    private var value: T
    private let range: ClosedRange<T>
    
    var wrappedValue: T {
        get { value }
        set { value = min(max(newValue, range.lowerBound), range.upperBound) }
    }
    
    init(wrappedValue: T, _ range: ClosedRange<T>) {
        self.range = range
        self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
    }
}

struct Player {
    @Clamped(0...100) var health: Int = 100
}

var p = Player()
p.health = 150       // clamped to 100
p.health = -50       // clamped to 0
print(p.health)      // 0

Projected values with $

Wrappers can expose a SECONDARY value via projectedValue:

@propertyWrapper
struct Logged<T> {
    private var value: T
    private(set) var history: [T] = []   // projected value
    
    var wrappedValue: T {
        get { value }
        set { value = newValue; history.append(newValue) }
    }
    
    var projectedValue: [T] { history }
    
    init(wrappedValue: T) { self.value = wrappedValue }
}

struct Counter {
    @Logged var count: Int = 0
}

var c = Counter()
c.count = 5
c.count = 10
c.count = 7
print(c.count)        // 7
print(c.$count)       // [0, 5, 10, 7] — accessed via $

SwiftUI's wrappers

SwiftUI uses property wrappers extensively:

  • @State — UI-local mutable state in views
  • @Binding — two-way connection to parent state
  • @Published — for ObservableObject properties
  • @Environment — pull values from the environment
  • @FetchRequest — Core Data queries

Understanding wrappers makes SwiftUI feel less magical.

Common mistakes

  • Confusing wrappedValue with the projected $ valueobj.x is wrappedValue; obj.$x is projectedValue.
  • Using property wrappers on lazy/computed properties — they don't work; wrappers ARE storage.
  • Wrappers in protocol requirements — you can't directly require a property be wrapped. Use a regular property requirement and adopt wrappers in conforming types.
  • Excessive wrapping — every @ adds indirection. Use wrappers for genuinely cross-cutting concerns, not minor boilerplate.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…