First introduced in Swift 5.1, property wrappers allow us devs to add our own implementation details to a property before they are initialized. Currently, by delaying initialization we can declare a property as lazy, and then return our property when it’s first requested. Property wrappers allow us to wrap all this logic into a nice separate piece of code that can be attached to any property like the way lazy does. This opens a whole new way of creating properties.
Combine introduced one property wrapper called Published which can be assigned to a property to expose a publisher for that value.
Swift UI also exposes multiple new property wrappers in Swift 5.1.
@propertyWrapper @dynamicMemberLookup public struct Binding<Value>
@Binding – allows the property’s value to live somewhere else and is shared in both places. For example, in SwiftUI when you initialize a TextField you pass it in a Binding property, which is then updated when the text is inputted by the user.
@propertyWrapper public struct Environment<Value> : DynamicProperty
@Environment – is used to read individual data from the environment with the associated key. A common example would be if you need to read size class information.
@propertyWrapper public struct Environment<Value> : DynamicProperty
@Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass
@Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass
@EnvironmentObject – similar to the environment property wrapper. This allows properties to be assigned to the entire object from the environment.
@propertyWrapper public struct EnvironmentObject<ObjectType> : DynamicProperty where ObjectType : ObservableObject
@FetchRequest – will return any NSFetchRequestResult object that has been saved to the environment. For anyone using CoreData, this will allow requests to be done directly on the property. As this property wrapper also conforms to DynamicProperty, values will be automatically refreshed when they are changed.
@propertyWrapper public struct GestureState<Value> : DynamicProperty
@GestureState – Gesture states will reset the value to its initial value once the gesture finishes.
@propertyWrapper public struct ObservedObject<ObjectType> : DynamicProperty where ObjectType : ObservableObject
@ObservedObject – can be attached to any ObservableObject property, to receive updates when the underlying object changes.
@propertyWrapper public struct State<Value> : DynamicProperty
@State – state properties can be used to store basic information within a View. Attaching the @State property wrapper, allows us to mutate values inside a struct.
That was a look at the built-in property wrappers but it is possible to create your own. Below is a very straightforward example of how you can change property values as they’re about to be initialised.
I have a simple weather struct that has its values set from a JSON feed. The feed only returns its temperatures in degrees, however I want to use Fahrenheit.
struct Weather: Decodable {
let temperatureHigh: Double
let temperatureLow: Double
}
I could add a private property for each temperature and override the setter and getter to use the private version.
struct Weather: Decodable {
private var p_temperatureHigh: Double
private var p_temperatureLow: Double
var temperatureHigh: Double {
set { p_temperatureHigh = newValue * 1.8 + 32 }
get { p_temperatureHigh }
}
var temperatureLow: Double {
set { p_temperatureLow = newValue * 1.8 + 32 }
get { p_temperatureLow }
}
}
However, there is a lot of boilerplate and duplication here which is very messy. Wouldn’t it be similar if we could wrap all this logic up and assign it to the property in one word?
@propertyWrapper
struct Fahrenheit<Value: FloatingPoint> {
private(set) var value: Value = .zero
var wrappedValue: Value {
get { value }
set { value = newValue * 1.8 + 32 }
}
init(wrappedValue value: Value) {
self.wrappedValue = value
}
}
Here we have my Fahrenheit property wrapper which will automatically convert the property’s value from Celsius to Fahrenheit. It can then be assigned as follows.
struct Weather: Decodable {
@Fahrenheit var temperatureHigh: Double
@Fahrenheit var temperatureLow: Double
}
There we have it. A really nice elegant way of wrapping any initialization logic for your properties. Be sure to check out the Swift proposal for property wrappers which has way more information than I could ever write about.