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?
- Add @
propertyWrapper
annotation in any struct or class.
- 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.