70 %
Chris Biscardi

What is a SwiftUI Property Wrapper

Swift has the concept of properties. Property wrappers allows you to decorate some code with logic for getting and setting that property. Where in React, you might reach for hooks: In Swift you reach for property wrappers.

A property wrapper adds a layer of separation between code that manages how a property is stored and the code that defines a property. For example, if you have properties that provide thread-safety checks or store their underlying data in a database, you have to write that code on every property. When you use a property wrapper, you write the management code once when you define the wrapper, and then reuse that management code by applying it to multiple properties.

SwiftUI's usage of property wrappers is the same as the core language's, in a specific context. Typically you'll see SwiftUI property wrappers used to modify the get/set behavior of some state.

We can use the @State property wrapper like we would with React's useState hook, to manage a hypothetical colorMode value:

swift
struct ContentView: View {
@State
var colorMode = "light"
var body: some View {
Button(action: {
colorMode = colorMode == "light" ? "dark" : "light"
}, label: {
Text("Button")
})
.padding()
.foregroundColor(colorMode == "light" ? .blue : .red)
}
}

Contrast that with the same code that stores the value in UserDefaults, which persists across app restarts.

swift
struct ContentView: View {
@AppStorage("colorMode")
var colorMode = "light"
var body: some View {
Button(action: {
colorMode = colorMode == "light" ? "dark" : "light"
}, label: {
Text("Button")
})
.padding()
.foregroundColor(colorMode == "light" ? .blue : .red)
}
}

This is the power of property wrappers

Projected values

So there's the wrapped value, which in the above AppStorage case is the username and then there are Projected Values. Example from the Swift docs that implements a @SmallNumber property wrapper that includes a projected value. The projected value is accessed using a $ before the variable name (which incidentally isn't something you can do in userland swift code, so this will never conflict with your own variables). So a variable username and the projected value $username can be different values.

swift
@propertyWrapper
struct SmallNumber {
private var number: Int
var projectedValue: Bool
init() {
self.number = 0
self.projectedValue = false
}
var wrappedValue: Int {
get { return number }
set {
if newValue > 12 {
number = 12
projectedValue = true
} else {
number = newValue
projectedValue = false
}
}
}
}
struct SomeStructure {
@SmallNumber var someNumber: Int
}
var someStructure = SomeStructure()
someStructure.someNumber = 4
print(someStructure.$someNumber)
// Prints "false"
someStructure.someNumber = 55
print(someStructure.$someNumber)
// Prints "true"

List of SwiftUI property wrappers