@propertyWrapper struct Name { var wrappedValue: T { get set } } defines a wrapper — applying @Name to a property routes every read and write through wrappedValue's getter/setter instead of storing the value directly, letting the wrapper add validation, persistence, or transformation logic transparently. An optional projectedValue (accessed via $propertyName, as in $count for a property wrapped with @State) exposes additional functionality beyond the wrapped value itself, such as a Binding. This is the exact, non-magical mechanism SwiftUI's @State and Combine's @Published are built from — any project can define an equally real, reusable wrapper the same way.
Writing a custom property wrapper: Clamped
Every assignment to value routes through this custom setter — the clamping happens transparently, with the property looking like an ordinary Int everywhere it's used.
@propertyWrapper
struct Clamped {
private var value: Int
let range: ClosedRange<Int>
var wrappedValue: Int {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
init(wrappedValue: Int, _ range: ClosedRange<Int>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
}
struct Volume {
@Clamped(0...100) var level: Int = 50
}
var v = Volume()
v.level = 150
print(v.level) // 100 — silently clamped to the range's upper boundprojectedValue: exposing extra functionality via $
$loggedValue accesses the projectedValue specifically — a separate channel from the wrapped value itself, exactly how SwiftUI's $count exposes a Binding alongside @State's plain Int.
@propertyWrapper
struct Logged<T> {
private var value: T
private(set) var projectedValue: [String] = [] // a log of every value it's held
var wrappedValue: T {
get { value }
set {
projectedValue.append("\(newValue)")
value = newValue
}
}
init(wrappedValue: T) {
self.value = wrappedValue
}
}
struct Settings {
@Logged var volume: Int = 50
}
var s = Settings()
s.volume = 75
s.volume = 90
print(s.$volume) // ["75", "90"] — accessed via $, the projectedValueWhy this matters: the same mechanism as @State/@Published
@State's wrappedValue triggers a SwiftUI view refresh on write, and @Published's wrappedValue notifies Combine subscribers — conceptually identical to the custom Clamped wrapper above, just with different logic inside the same get/set mechanism.