How to Create a Custom Property Wrappers in SwiftUI?

Introduction

SwiftUI's property wrappers elevate your properties beyond simple data storage. They grant you the ability to add functionality, interact with the view lifecycle, and manage data in a structured way. This article unveils how to construct custom property wrappers and explores their potential through practical examples.

How do we create a property wrapper?

  1. Add @propertyWrapper annotation in any struct or class.
  2. Provide a stored property wrappedValue. Here is where we can add our custom logic.
@propertyWrapper
struct RemoveWhiteSpace {
    var wrappedValue: String {
        didSet { wrappedValue = wrappedValue.trimmingCharacters(in: .whitespaces) }
    }
    init(wrappedValue: String) {
        self.wrappedValue = wrappedValue.trimmingCharacters(in: .whitespaces)
    }
}

To use the above custom property wrapper, we just need to add @RemoveWhiteSpace to the property we want.

struct User {
    @RemoveWhiteSpace var fullName: String
}

let user = User(fullName: "John Doe        ")
print(user.fullName)

// Output : John Doe

Let's create a property wrapper for persisting data using UserDefaults. This wrapper will automatically save and load values:

@propertyWrapper
struct UserDefaultsWrapper<Value: Codable> {
  let key: String
  private var wrappedValue: Value

  init(key: String) {
    self.key = key
    self.wrappedValue = UserDefaults.standard.object(forKey: key) as? Value ?? // Set default value
  }
  var wrappedValue: Value {
    get { return self.wrappedValue }
    set {
      UserDefaults.standard.set(newValue, forKey: key)
      // Optionally, handle errors or notifications here
    }
  }
}

In this example

  • The wrapper accepts a key string to identify the data in UserDefaults.
  • The initializer retrieves the data from UserDefaults using the provided key and sets a default value if no data exists.
  • The wrappedValue acts as both a getter and setter. When retrieved, it returns the stored value. When assigned a new value, it updates UserDefaults and can optionally perform additional actions like error handling.

Usage Example

@StateObject
private var userSettings = UserDefaultsWrapper<Settings>(key: "user_settings")

Here, we create a UserDefaultsWrapper instance named userSettings that holds a Settings struct. Any changes to userSettings.wrappedValue will automatically persist in UserDefaults.

Exploring Possibilities

Custom property wrappers offer a wide range of functionalities beyond data persistence. Here are some additional examples:

  • Validation Wrapper: This wrapper can ensure data adheres to specific rules. Imagine a NameWrapper that only accepts values with a minimum character length.
  • Environment Wrapper: Access the SwiftUI environment within your wrapper. A DebugModeWrapper could conditionally enable debug features based on an environment variable.

Conclusion

By leveraging custom property wrappers, you can streamline data management, enforce validation rules, and tailor your views based on the environment, all within a clean and organized structure.


Similar Articles