Learn How to Use Property Wrappers in Swift in 10 minutes

A quick and simple guide to saving more than one property in struct, class or enum.

July 20, 2020
By
Nishan Niraula

Swift 5.1 has introduced an interesting concept called Property Wrappers. As the name suggests, it creates a wrapper around any plain old properties in struct, class or enum and enhances their abilities. Let’s dive right into code.

Almost every app out there saves some kind of information in UserDefaults. The common approach to do that would be:

{% c-block language="swift" %}
struct AppSettings {
   // Update the respective value in UserDefault
   static var isDarkModeEnabled = false {
       didSet {
           UserDefaults.standard.setValue(isDarkModeEnabled, forKey:
"is_dark_mode_enabled")
       }
   }
}
{% c-block-end %}

This is the easiest approach and it works fine. But what if we have more than 1 property to save? Here’s where the magic of property wrappers come in.

How it works:

Property Wrapper simply wraps the original property. Inside this wrapper, you can specify what you want to do when the user tries to set the value or read the value from the original property.

In our case, we will store the value in UserDefaults when the user sets the value on the original property. Similarly, when the user tries to read the value, we will return the stored value from the same UserDefaults. Let’s create our very first Property Wrapper.

{% c-block language="swift" %}
@propertyWrapper
struct Store {

   // Key for UserDefaults
   var key: String

   // This is the special variable required in Property Wrapper.
   var wrappedValue: Bool {
       set { UserDefaults.standard.set(newValue, forKey: key) }
       get { UserDefaults.standard.value(forKey: key) as? Bool ?? false }
   }

   init(key: String) {
       self.key = key
   }
}

// Now we can simplify our AppSettings struct as
struct AppSettings {
   @Store(key: "is_dark_mode_enabled") static var isDarkModeEnabled: Bool
}

// value `true` is automatically stored in user defaults with key `is_dark_mode_enabled`
AppSettings.isDarkModeEnabled = true
{% c-block-end %}

Let’s break down the steps:

  1. First we create a struct called Store. `@propertyWrapper` keyword declares that this struct is actually a property wrapper.
  2. `wrappedValue` is a special variable required in Property Wrapper. When we try to read the value of the property, it actually returns the value provided by this `wrappedValue` variable. Similarly, when we try to set the value of the original property, it actually sets the value for this `wrappedValue`.
  3. Since we need some `key` to store the value in UserDefaults, we ask for that during initialization.
  4. Using property wrapper is really easy. Just add @Store prefix to any property. That’s it.

So, where’s the magic?

The magic happens in this line.

{% c-line %}AppSettings.isDarkModeEnabled = true{% c-line-end %}

Whenever we set the value, it automatically gets stored in UserDefaults with the key “is_dark_mode_enabled”. When we try to read the value, it returns the stored value from the same UserDefaults.

But…
Our Property Wrapper can work with only Boolean property at this time. Also the default wrapped value is always false. Let’s change that.

{% c-block language="swift" %}
// We make our Property Wrapper Generic.
@propertyWrapper
struct Store<T> {

   // This is the key that user can provide
   var key: String
   // The default value for the property
   var defaultValue: T

   // Now it returns Generic value instead of Bool
   var wrappedValue: T {
       set { UserDefaults.standard.set(newValue, forKey: key) }
       get { UserDefaults.standard.value(forKey: key) as? T ?? defaultValue }
   }

   // We ask both key and default value during initialization.
   init(key: String, defaultValue: T) {
       self.key = key
       self.defaultValue = defaultValue
   }
}

// Usage:
struct AppSettings {
   @Store(key: "app_user_name", defaultValue: "") static var userName: String
   @Store(key: "is_dark_mode_enabled", defaultValue: false) static var
isDarkModeEnabled: Bool
}

AppSettings.userName = "Ironman"
AppSettings.isDarkModeEnabled = false
{% c-block-end %}

Here we ask the user to provide both key as well as default value when using this Property Wrapper. We use this `key` to save or retrieve value in UserDefaults. We return the defaultValue, if nothing is found in UserDefaults.

With the power of Generics, now this PropertyWrapper can be used with any type of property. And our 10 minutes is up.

You can learn more about the Property Wrapper from
https://docs.swift.org/swift-book/LanguageGuide/Properties.html

Join the Amity team!

We’re always seeking ambitious, passionate and community-driven candidates to join us.