Understanding Property Wrapper
Property wrapper adds a wrapper over a property which stores it and defines how the property will behave
As the name suggests its a wrapper over the property, so you can fiddle with the raw value before returning it. Property wrappers can be used with struct
enum
and class
.
You must have seen and even used @State
, @Binding
, @Published
, @ObservedObject
and a lot more in SwiftUI
which highly relies on Property Wrapper
.
We will implement a Music Rating app where a user can rate the music from 1 to 5. If a user mistakenly sets the value greater than 5 (which means he really liked the music) we reset the value to 5 which is the highest rating. Similarly, if he disliked the music and set the value to 0, we reset the value to 1 which is the lowest possible rating.
Defining a property wrapper:
- Prefix the keyword
@properyWrapper
before the struct, enum, or class. We have created a struct Rating and prefixed@properyWrapper
beforestruct
Rating. - Property wrapper must contain a non-static property named
wrappedValue
. You can use the getter and setter methods to perform your checks and validations for the variables.
Usage:
- To use the property wrapper, you prefix the wrapper’s name before the property as an attribute. We have prefixed
@Rating
that goes before the attributerating
. - To use the variable just set the attribute value. If you set the value, the
wrappedValue
setter gets called where validation is performed. If the newValue is higher than 5, the rating is reset to 5. Similarly, when the user sets the value smaller than 1, it resets to 1.
Simple enough! Let’s take a more practical example.
Setting Initial Values on Wrapped Properties
Hardcoding the maximum and minimum ratings is not the right way to do it. So we will be passing the max and min ratings to the initializer. As you can see in the below code, we have added multiple initializers.
Let’s understand how initializers are called on PropertyWrappers.
- When you don’t specify an initial value for a property
init()
is called where we set the max, min, and default rating.
// calls init()
@Rating var masakali: Int
2. When you specify an initial value for a property init(wrappedValue:)
is called. You can set the wrapped value in two ways. The first one by assigning value to it. The second way is by adding arguments after they attribute.
When you write parameters in the parentheses after the attribute you basically call the initializer which accepts those arguments.
struct MusicRating {
// calls init(wrappedValue:)
@Rating var masakali: Int = 5
@Rating (wrappedValue: 5) var masakali: Int
}
3. On similar principles, we can have more than one argument in initializers. When you write parameters in the parentheses after attribute init(wrappedValue: maxRating: minRating:)
gets called.
struct MusicRating {
// calls init(wrappedValue: maxRating: minRating:)
@Rating (maxRating: 5, minRating: 1) var masakali: Int = 5
@Rating (wrappedValue: 0, maxRating: 5, minRating: 1) var masakali: Int
}
Projected value in a property wrapper
You can add additional functionality on property wrapper using projected values
. The name of the projected value will be similar to wrapped value and can be accessed by appending a $
sign before the wrapped value.
Projected values are optional to implement and could be used if you add some extra information along with the wrapped value. In the below implementation, the projected value returns the number of stars on the rating given by the user.
Using UserDefaults with property wrapper
UserPreferences
is a property wrapper that helps to save and get values from UserDefaults.
Using SecureDefaults to store Secure object
SecureDefaults
can be used to wrap the objects conforming to SecureCoding into and from UserDefaults
.
That’s pretty much what propertyWrapper
is all about. One of the features I will be using extensively in my projects and will be updating the same blog. do you have a use case that you think should be mentioned here? Let me know in the comments!