Using Property Wrappers in iOS development iOS 05.08.2020

As the name suggests its a wrapper over the property, so you can change the raw value before returning it. You can think of property wrapper as a regular property, which delegates its get and set to some other type. Property wrappers can be used with struct, enum and class.

Property wrappers are a feature that was introduced in Swift 5.1 and they play a huge role in SwiftUI and Combine which are two frameworks that shipped alongside Swift 5.1 in iOS 13.

The idea of property wrappers, is that commonly used computed property getter and setter patterns can be encapsulated into a type with a wrappedValue computed property:

@propertyWrapper 
struct MyWrapper { 
    // ...
    var wrappedValue : SomeType { 
        get { /*...*/ }
        set { /*...*/ } 
    }
}

You can then declare your computed property using the property wrapper type name as a custom attribute:

@MyWrapper var myProperty

The result is that, behind the scenes, a MyWrapper instance is created for you, and when your code gets or sets the value of myProperty, it is the getter or setter of this MyWrapper instance that is called.

In real life, your property wrapper’s purpose will almost certainly be to act as a facade for access to a stored instance property, declared inside the property wrapper’s type.

To implement your own property wrapper, you need to do a few things:

  • Declare a struct that will wrap the property.
  • Mark the struct as a @propertyWrapper before the struct keyword.
  • Implement the var wrappedValue computed variable. You can use get to get the value of the property, and set to set it. With this, you can see that you can let a property wrapper store its value anywhere.

To exemplify this, we will write a simple property wrapper that works with Strings and it changes them to uppercase letters.

@propertyWrapper
struct Capitalized {
    private(set) var text: String = ""
    var wrappedValue: String {
        get { return text }
        set { text = newValue.uppercased() }
    }
}

struct User {
    @Capitalized var firstName: String
    @Capitalized var lastName: String
}

var user = User(firstName: "Thomas", lastName: "Anderson")

user.lastName // prints ANDERSON

Another example

@propertyWrapper
struct Rating {
    private var _rating: Int = 0

    var wrappedValue: Int {
        get {
            return _rating
        } set (value) {
            if value > 5 {
                _rating = 5
            } else if value < 1 {
                _rating = 1
            } else {
                _rating = value
            }
        }
    }
}

struct MusicRating {
    @Rating var rating: Int

    init(rating: Int) {
        self.rating = rating
        print(rating)
    }
}

MusicRating(rating: 9)// prints 5
MusicRating(rating: 0)// prints 1

Styling UILabel

@propertyWrapper
public class TitleLabel {
    public var wrappedValue: UILabel

    public init(text: String) {
        self.wrappedValue = UILabel()
        wrappedValue.text = text
        configureLabel()
    }

    private func configureLabel() {
        wrappedValue.textColor = .lightGray
        wrappedValue.font = .systemFont(ofSize: 28, weight: .regular)
        wrappedValue.sizeToFit()
    }
}

@TitleLabel(text: "The Matrix")
var titleLabel: UILabel

UserDefaults

UserPreferences is a property wrapper that helps to save and get values from UserDefaults.

@propertyWrapper
struct UserDefault<T> {
    let key: String
    let defaultValue: T

    init(_ key: String, defaultValue: T) {
        self.key = key
        self.defaultValue = defaultValue
    }

    var wrappedValue: T {
        get {
            return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
        }
        set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}

struct UserData {
    @UserDefault("hasVoted", defaultValue: false)
    static var hasVoted: Bool
}

UserData.hasVoted = true
print(UserData.hasSeenAppIntroduction) // prints false

Also you can extend UserDefault and save custom object.