Exploring All Property Wrappers in SwiftUI
Tags: swift, swiftui, ios, beginners
Published: 2024-02-25 16:00:00

In this article, we will explore several property wrappers that are frequently used and crucial in SwiftUI development. This article aims to provide an overview of the main functions and usage considerations of these property wrappers, rather than a detailed usage guide.


---

Don’t miss out on the latest updates and excellent articles about Swift, SwiftUI, Core Data, and SwiftData. Subscribe to [**Fatbobman’s Swift Weekly**](https://weekly.fatbobman.com) and receive weekly insights and valuable content directly to your inbox.

---


> The property wrappers in Swift language and the birth of SwiftUI occurred in the same year. SwiftUI fully leverages this feature, offering developers a series of property wrappers that greatly simplify the development process. In this series of four articles, we have thoroughly reviewed all the property wrappers provided by SwiftUI as of iOS 17, aiming to help developers use SwiftUI more efficiently and conveniently. We hope this content provides valuable guidance and assistance when using SwiftUI.

- [@AppStorage, @SceneStorage, @FocusState, @GestureState, @ScaledMetric](https://fatbobman.com/en/posts/exploring-swiftui-property-wrappers-2/)
- [@FetchRequest, @SectionedFetchRequest, @Query, @Namespace, @Bindable](https://fatbobman.com/en/posts/exploring-swiftui-property-wrappers-3/)
- [@UIApplicationDelegateAdaptor, @AccessibilityFocusState, @FocusedObject, @FocusedValue, @FocusedBinding](https://fatbobman.com/en/posts/exploring-swiftui-property-wrappers-4/)

## 1 @State

`@State` is one of the most commonly used property wrappers in SwiftUI, primarily used for managing private data within a view. It's particularly suited for storing value-type data such as strings, integers, enums, or struct instances.

- `@State` is used to manage a view's private state.
- It's mainly used for storing value-type data (lifespan consistent with the view).

### 1.1 Typical Use Cases

- `@State` is the ideal choice when a view update is triggered by changes in data within the view.
- It's commonly used for simple UI component state management, such as switch states, text input, etc.
- If data does not require complex cross-view sharing, `@State` can simplify state management.

### 1.2 Considerations

- Try to use `@State` only within the view, and consider it as a private property of the view, even if not explicitly marked as `private`.
- `@State` provides a two-way data binding pipeline for wrapped data, accessible using the `$` prefix.
- `@State` is not suitable for storing large amounts of data or complex data models; in these cases, `@StateObject` or other state management solutions are more appropriate.

- Property wrappers are essentially structs. The `@` prefix is used to wrap other data; without `@`, it represents its own type. For more details, refer to [John Sundell](https://www.swiftbysundell.com/articles/property-wrappers-in-swift/) and [Antoine van der Lee](https://www.avanderlee.com/swift/property-wrappers/), or read [@State Research in SwiftUI](https://fatbobman.com/en/posts/swiftui-state/).
- When assigning values in the constructor, access the raw value of `@State` using an underscore `_` for assignment.

```swift
@State var name: String
init(text: String) {
    // Assign to the underscore version, wrapping it with the State type itself
    _name = State(wrappedValue: text)
}
```

- `@State` variables can only be assigned once in the view's constructor, and subsequent adjustments should be made within the view's `body`. See [How to Avoid Repeating SwiftUI View Updates](https://fatbobman.com/en/posts/avoid_repeated_calculations_of_swiftui_views/).
- If there's no need to modify the value in the current view or in child views (through `@Binding`), there's no need to use `@State`.

- In some cases, `@State` is also used to store non-value types, such as reference types, to ensure their uniqueness and lifespan.

```swift
@State var textField: UITextField?
TextField("", text: $text)
    .introspect(.textField, on: .iOS(.v17)) {
        // Holding the UITextField instance
        self.textField = $0
    }
```

- `@State` in the Observation framework ensures that `@Observable` instances have a lifespan no shorter than the view itself. For detailed information, see [A Deep Dive Into Observation: A New Way to Boost SwiftUI Performance](https://fatbobman.com/en/posts/mastering-observation/).
- `@State` is thread-safe and can be modified on non-main threads.

```swift
@State var text: String = ""
Button("Change") {
    // No need to switch back to the main thread
    Task.detached {
        text = "hi"
    }
}
```

> Don’t miss out on the latest updates and excellent articles about Swift, SwiftUI, Core Data, and SwiftData. Subscribe to [Fatbobman’s Swift Weekly](https://fatbobman.substack.com) and receive weekly insights and valuable content directly to your inbox.


## 2 @Binding

`@Binding` is a property wrapper in SwiftUI used for implementing two-way data binding. It creates a two-way connection between a value (like a Bool) and the UI elements that display and modify these values.

- `@Binding` does not hold the data directly but provides a wrapper for read and write access to other data sources.
- It allows UI elements to directly modify the data and reflect these changes.

### 2.1 Typical Use Cases

- `@Binding` is mainly used with UI components that support two-way data binding, such as in combination with `TextField`, `Stepper`, `Sheet`, and `Slider`.
- It is suitable for situations where you need to directly modify the data in a parent view from a child view.

### 2.2 Considerations

- Use `@Binding` cautiously; it's unnecessary if a child view only needs to respond to data changes without modifying them.
- In complex view hierarchies, passing `@Binding` through multiple levels can make data flow hard to track, in which case other state management methods should be considered.
- Ensure the data source for `@Binding` is reliable, as an incorrect data source can lead to inconsistencies or application crashes. Since `@Binding` is just a conduit, it doesn’t guarantee that the corresponding data source will exist when called.

- Developers can customize Binding by providing `get` and `set` methods.

```swift
let binding = Binding<String>(
    get: { text },
    // Limit the length of the string
    set: { text = String($0.prefix(10)) }
)
```

* Creating extensions for `Binding` type can greatly enhance development efficiency and flexibility. For more, read: [SwiftUI Binding Extensions](https://betterprogramming.pub/swiftui-binding-extensions-b6a9f27d2858).	

```swift
// Convert a Binding<V?> to a Binding<Bool>
extension Binding {
    static func isPresented<V>(_ value: Binding<V?>) -> Binding<Bool> {
        Binding<Bool>(
            get: { value.wrappedValue != nil },
            set: {
                if !$0 { value.wrappedValue = nil }
            }
        )
    }
}
```

- In the Observation framework, `@Bindable` can be used to create a corresponding `Binding` interface for `@Observable` instances. For details, see [A Deep Dive Into Observation: A New Way to Boost SwiftUI Performance](https://fatbobman.com/en/posts/mastering-observation/).
- When declaring constructor parameters, the wrapped value type of `Binding` (return type of the `get` method) needs to be explicitly specified, like `Binding<String>`.

- `@Binding` is not an independent data source. It's merely a reference to already existing data. The view is only updated when the `get` method reads values that can trigger a view update (like @State, @StateObject), which is crucial for custom `Binding`.

```swift
struct Test: View {
    let a = A()
    var body: some View {
        let binding = Binding<String>(
            get: { a.name },
            set: { a.name = $0 }
        )
        // Although A conforms to the ObservableObject protocol, since it's not associated with the view using StateObject, the Binding created for its properties will also not trigger a view update
        Text(binding.wrappedValue)
        TextField("input:", text: binding)
    }

    class A: ObservableObject {
        @Published var name: String = ""
    }
}
```

## 3 @StateObject

`@StateObject` is a property wrapper in SwiftUI for managing instances of objects conforming to the `ObservableObject` protocol. It ensures these instances have a lifecycle that is at least as long as the current view's lifecycle.

- `@StateObject` is specifically used for managing instances that conform to the `ObservableObject` protocol.
- The annotated object instance remains unique throughout the entire lifecycle of the view, meaning it won't be recreated even if the view updates.

### 3.1 Typical Use Cases

- `@StateObject` is typically used at the top of the view hierarchy to create and maintain `ObservableObject` instances.
- It's commonly used for data models or business logic that need to persist throughout the entire lifecycle of the view.
- Compared to `@State`, `@StateObject` is more suitable for managing complex data models and their associated logic.

### 3.2 Considerations

- The conditions triggering a view update with `@StateObject` include assigning values to properties marked with `@Published` (regardless of whether the new value is different from the old) and invoking the `objectWillChange` publisher.
- Use `@StateObject` only in views that must respond to changes in instance properties. If you only need to read data without observing changes, consider other options.

- Introducing `@StateObject` implies that all related operations occur on the main thread (as SwiftUI implicitly adds `@MainActor` to the view), including asynchronous operations. Code that needs to run on a non-main thread should be separated from the view code.

```swift
struct B: View {
    // Using StateObject is equivalent to adding @MainActor to the current view
    @StateObject var store = Store()
    var body: some View {
        Button("Main Thread") {
            Task.detached {
                await printThreadName()
                // output <_NSMainThread: 0x60000170c000>{number = 1, name = main}
            }
        }
    }
    
    func printThreadName() async {
        print(Thread.current)
    }
}
```

- If an instance is created in a context where the view's lifespan is guaranteed (such as at the app level), and there is no need to respond to changes in that instance's properties at the current level, `@StateObject` may not be necessary.

```swift
struct DemoApp: App {
    // Since the lifespan of the view at this level is consistent with the application, and if there's no need to respond to changes in 'store', StateObject is not required
    let store = Store()

    var body: some Scene {
        WindowGroup {
            Test()
                .environmentObject(store)
        }
    }
}
```

## 4 @ObservedObject

`@ObservedObject` is a property wrapper in SwiftUI used to create a connection between a view and instances of `ObservableObject`, mainly for introducing external `ObservableObject` instances during the view's lifespan.

- `@ObservedObject` does not own the observed instance and does not guarantee its lifespan.
- `@ObservedObject` can switch its associated instance during the view's lifespan.

### 4.1 Typical Use Cases

* Often used in conjunction with `@StateObject`, where a parent view creates an instance using `@StateObject`, and a child view introduces this instance through `@ObservedObject`, responding to changes in the instance.

- Suitable for scenarios where dynamic switching of instances is needed. For example, in a `NavigationSplitView`, selecting different instances in the sidebar dynamically changes the data source in the detail view. For more details, please read [StateObject and ObservedObject](https://fatbobman.com/en/posts/stateobject_and_observedobject/).

```swift
// Define a data model conforming to the ObservableObject protocol
class DataModel: ObservableObject, Identifiable {
    let id = UUID()
}

struct MyView: View {
    @State private var items = [DataModel(), DataModel()]

    var body: some View {
        VStack {
            // Switch the DataModel instance associated with MySubView
            Button("Replace Model") {
                items.reverse()
            }
            MySubView(model: items.first!)
        }
    }
}

// Subview
struct MySubView: View {
    // Introduce an external ObservableObject instance with @ObservedObject
    @ObservedObject var model: DataModel 

    var body: some View {
        VStack {
            // Display the UUID of the current DataModel instance
            // When the 'items' array in MyView changes, the displayed UUID here will update, showcasing the dynamic switching capability of @ObservedObject
            Text(model.id.uuidString)
        }
    }
}
```

- Used in views to introduce `ObservableObject` instances whose lifespans are ensured by external frameworks or code, such as introducing `NSManagedObject` instances from Core Data.

### 4.2 Considerations

- In iOS 13, due to the absence of `@StateObject`, `@ObservedObject` was the only choice, which could lead to [unexpected results](https://fatbobman.com/en/posts/stateobject/) due to the inability to guarantee the lifespan of the instance. To avoid such issues, one could hold the instance using `@State` in a higher-level view (where stability isn't a concern), and then introduce it in the view where it's used via `@ObservedObject`.
- When introducing instances of `ObservableObject` provided by third parties, it's crucial to ensure that the object referenced by `@ObservedObject` is available throughout the entire lifespan of the view. Otherwise, it might lead to runtime errors.

## 5 @EnvironmentObject

`@EnvironmentObject` is a property wrapper used in SwiftUI to create a connection between the current view and an `ObservableObject` instance passed down through the environment from a higher-level view. It provides a convenient way to introduce shared data across different view hierarchies without explicitly passing it through each view's constructor.

### 5.1 Typical Use Cases

- Ideal for sharing the same data model across multiple views, such as user settings, themes, or application states.
- Suitable for building complex view hierarchies where multiple views need access to the same `ObservableObject` instance.

### 5.2 Considerations

- Before using `@EnvironmentObject`, ensure that the corresponding instance has been provided upstream in the view hierarchy (using the `.environmentObject` modifier). Failure to do so will result in a runtime error.
- The conditions that trigger view updates for `@EnvironmentObject` are the same as those for `@StateObject` and `@ObservedObject`.

* Like `@ObservedObject`, `@EnvironmentObject` supports dynamically switching the associated instance.

```swift
struct MyView: View {
    @State private var items = [DataModel(), DataModel()]
    var body: some View {
        VStack {
            Button("Replace Model") {
                // Switch the instance associated with the child view MySubView
                items.reverse()
            }
            MySubView()
                .environmentObject(items.first!)
        }
    }
}

struct MySubView: View {
    @EnvironmentObject var model: DataModel // Dynamically switch associated instance
    var body: some View {
        VStack {
            Text(model.id.uuidString)
        }
    }
}
```

- Only introduce `@EnvironmentObject` when necessary, as it can trigger unnecessary view updates. Often, multiple views from different levels observe and respond to the same instance, and proper optimization is required to avoid performance degradation in the application. This is a reason why many developers are wary of `@EnvironmentObject`.
- In a view hierarchy, only one instance of the same type of environment object is effective.

```swift
@StateObject var a = DataModel()
@StateObject var b = DataModel()

MySubView()
    .environmentObject(a) // The one closer to the view is effective
    .environmentObject(b)
```

## 6 @Environment

`@Environment` is a property wrapper used by views to read, respond to, and invoke specific values from the environment. It allows views to access data, instances, or methods provided by SwiftUI or the app environment.

### 6.1 Typical Use Cases

- When needing to access and respond to environment values provided by the system or higher-level views, such as interface style (dark/light mode), device orientation, or font size (typically corresponding to value types).
- When accessing and invoking SwiftUI's ModelContext (corresponding to reference types).
- When using methods provided by the system, such as `dismiss` or `openURL` (encapsulated via the struct's `callAsFunction` method).

### 6.2 Considerations

* Compared to the complex logic handled by instances provided by `@EnvironmentObject`, data introduced by `@Environment` typically has more specific functionality.
* Developers can create custom environment values by defining a custom `EnvironmentKey`. Like system-provided environment values, they can define various types (value types, Bindings, reference types, methods). For more details, see [Custom SwiftUI Environment Values Cheatsheet](https://www.fivestars.blog/articles/custom-environment-values-cheatsheet/).

```swift
public struct ContainerEnvironmentKey: EnvironmentKey {
    // Default value for the example environment key
    public static var defaultValue = ContainerEnvironment(containerName: "Default")
}

public extension EnvironmentValues {
    var overlayContainer: ContainerEnvironment {
        get { self[ContainerEnvironmentKey.self] }
        set { self[ContainerEnvironmentKey.self] = newValue }
    }
}
```

* In SwiftUI, the definition style similar to `EnvironmentKey` is used in many ways. Once mastered, it's easy to grasp others, such as `PreferenceKey` (for child-to-parent view communication), `FocusedValueKey` (for values based on focus), and `LayoutValueKey` (for child-to-layout container communication).
* Due to the presence of default values, `@Environment` won't cause an app crash due to missing values, but this can also lead to developers forgetting to inject values.
* Unlike `@EnvironmentObject`, lower-level views cannot modify the `EnvironmentValue` values passed down from ancestor views.
* Multiple properties of the same type but with different names can be created in `EnvironmentValue` by defining different `EnvironmentKeys`.

## Summary

- `@StateObject`, `@ObservedObject`, and `@EnvironmentObject` are specifically used for associating instances that conform to the `ObservableObject` protocol.
- While `@StateObject` can sometimes replace `@ObservedObject` and offer similar functionality, they each have unique use cases. `@StateObject` is typically used for creating and maintaining instances, whereas `@ObservedObject` is for introducing and responding to already existing instances.
- In environments with iOS 17+ where applications primarily rely on the Observation and SwiftData frameworks, the use of these three property wrappers might be relatively less frequent.
- `@State` and `@Environment` are not limited to storing value types but can also be used for other types.
- `@Environment` provides a relatively safer method to introduce environmental data because it can offer default values through `EnvironmentValue`. This reduces the risk of application crashes due to missing data injections.
- In the context of the Observation framework, `@State` and `@Environment` become the primary property wrappers. Whether it's value types or `@Observable` instances, both can be introduced into views through these wrappers.
- Custom Bindings offer great flexibility, allowing developers to implement complex logic between data sources and UI components that depend on Bindings with concise code.

Each property wrapper has its unique use cases and advantages. Choosing the right tool is crucial for building efficient and maintainable SwiftUI applications. As often mentioned in software development, no single tool is a panacea, but using them appropriately can greatly enhance our development efficiency and application quality.

- [@AppStorage, @SceneStorage, @FocusState, @GestureState, @ScaledMetric](https://fatbobman.com/en/posts/exploring-swiftui-property-wrappers-2/)
- [@FetchRequest, @SectionedFetchRequest, @Query, @Namespace, @Bindable](https://fatbobman.com/en/posts/exploring-swiftui-property-wrappers-3/)
- [@UIApplicationDelegateAdaptor, @AccessibilityFocusState, @FocusedObject, @FocusedValue, @FocusedBinding](https://fatbobman.com/en/posts/exploring-swiftui-property-wrappers-4/)