SwiftUI property wrappers
Let's dive into the fascinating world of SwiftUI property wrappers.
These powerful constructs allow you to manage data, state, and environment information in your SwiftUI views. 🚀
Understanding SwiftUI Property Wrappers
State: Things the view creates (not owns, because Views don't own anything…).
For example: @State
, @StateObject
, @AppStorage
…
Can also more generally mean “the state of the app”, which beyond the view’s state, would also include the view’s dependencies, as well as other parts of the app’s state that don’t affect the view. The meaning of the term through the document usually means the former definition (ie, things the view creates), but can change based on the context.
Dependencies: Things the view receives and doesn’t create.
For example: @Binding
, let
, @Environment
, @EnvironmentObject
, @ObservedObject
…
Also anything passed to a custom init if there is one.
Property wrappers are a fundamental part of SwiftUI, helping reduce boilerplate code and enabling seamless data flow between views. Let's explore some of the most commonly used property wrappers:
-
@State
: This property wrapper allows you to manage small amounts of value type data locally within a view. When the state changes, SwiftUI automatically updates the view. Use it for UI-related state, such as toggles, counters, or text input fields. -
@Binding
: When you need to share data between different views,@Binding
comes to the rescue. It refers to value type data owned by another view. Changes made locally to the binding propagate back to the original data source. Think of it as a two-way communication channel. -
@ObservedObject
: Use this property wrapper to refer to an instance of an external class that conforms to theObservableObject
protocol. It's perfect for managing reference type data (e.g., network requests, Core Data objects) that doesn't belong to the view itself. -
@Published
: Attached to properties inside anObservableObject
,@Published
tells SwiftUI to refresh any views that use this property when it changes. It's like a signal to update the UI when the underlying data updates. -
@Environment
: This wrapper lets you read data from the system environment, such as color schemes, accessibility options, and trait collections. You can also add your own custom keys to the environment. It doesn't own its data but provides access to system-level information. -
@AppStorage
: If you need to read and write values fromUserDefaults
,@AppStorage
is your friend. It owns its data and simplifies persistent storage for simple values like user preferences or settings. -
@FetchRequest
: When working with Core Data, use@FetchRequest
to start a fetch request for a specific entity. It owns its data and provides a convenient way to retrieve managed objects from your data store. -
@ScaledMetric
: This wrapper reads the user's Dynamic Type setting and scales numbers based on an original value you provide. It's useful for adapting font sizes and other metrics to the user's preferences.
Custom Property Wrappers
You can also create your own custom property wrappers to encapsulate specific behavior or data management logic. By defining a property wrapper type, you can reuse it across your codebase and ensure consistent behavior wherever it's used.
Here's a simple example of a custom property wrapper that logs changes to a property:
@propertyWrapper
struct LogChanges<Value> {
var wrappedValue: Value {
didSet {
print("Value changed to \(wrappedValue)")
}
}
init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
}
}
struct ContentView: View {
@LogChanges
var count = 0
var body: some View {
VStack {
Button("Increment") {
count += 1
}
Text("Count: \(count)")
}
}
}
In this example, the LogChanges
property wrapper prints a message whenever the count
property changes. You can create custom property wrappers to add additional functionality or constraints to your properties.
Example 2: Mimicing @Published
Here's another example that mimics the behavior of @Published
:
@propertyWrapper
struct Store<Value> :DynamicProperty {
let storage: State<Value>
init(wrappedValue value: Value) {
self.storage = State<Value>(initialValue: value)
}
public var wrappedValue: Value {
get { storage.wrappedValue }
nonmutating set { storage.wrappedValue = newValue }
}
public var projectedValue: Binding<Value> {
storage.projectedValue
}
}
struct ContentView: View {
@Store
var isOn = false
var body: some View {
VStack {
Text(isOn ? "yes" : "Nope")
Toggle(isOn: $isOn, label: {
Text("IsOn?")
})
}
.padding()
}
}
Conclusion
SwiftUI property wrappers are like magic spells that empower your views. By choosing the right one for each situation, you'll create elegant, responsive interfaces. Happy coding! 🌟
Read more
- A Guide to UI Testing in Swift • 15 minutes reading time.
- A Guide to UI Testing in Swift • 15 minutes reading time.
- Simplifying App Onboarding with OnboardingKit • 5 minutes reading time.
Share
Share Mastodon Twitter LinkedIn Facebook