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:
struct ContentView: View {@Statevar 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.
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
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.
@propertyWrapperstruct SmallNumber {private var number: Intvar projectedValue: Boolinit() {self.number = 0self.projectedValue = false}var wrappedValue: Int {get { return number }set {if newValue > 12 {number = 12projectedValue = true} else {number = newValueprojectedValue = false}}}}struct SomeStructure {@SmallNumber var someNumber: Int}var someStructure = SomeStructure()someStructure.someNumber = 4print(someStructure.$someNumber)// Prints "false"someStructure.someNumber = 55print(someStructure.$someNumber)// Prints "true"