Created
April 2, 2026 19:25
-
-
Save moyerr/375a6d1ada83088a7583be9440bbb281 to your computer and use it in GitHub Desktop.
An environment action for mutating state owned by an ancestor view
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /// An action that updates a value stored in the environment. | |
| /// | |
| /// ## Overview | |
| /// | |
| /// Use an `Updater` to allow descendant views to modify a value owned by | |
| /// an ancestor view, without passing Bindings or specific update actions for each property. | |
| /// The updater accepts a ``WritableKeyPath`` and a new value, then applies | |
| /// the change through a closure you provide at initialization. | |
| /// | |
| /// ### Setting up the updater | |
| /// | |
| /// Create an `Updater` in the view that owns the state and place it in the environment: | |
| /// | |
| /// ```swift | |
| /// @State private var settings = SettingsValues() | |
| /// @Namespace private var id | |
| /// | |
| /// var body: some View { | |
| /// ContentView() | |
| /// .environment(\.settings, settings) | |
| /// .environment(\.updateSettings, Updater(id: id) { transform in | |
| /// transform(&settings) | |
| /// }) | |
| /// } | |
| /// ``` | |
| /// | |
| /// The `id` parameter should be a stable and unique identifier tied to the lifetime | |
| /// of the view that owns the state being modified. The `update` closure receives a | |
| /// transform function that mutates the root value in place. | |
| /// | |
| /// ### Using the updater | |
| /// | |
| /// Read the updater from the environment and call it with a key path and | |
| /// value: | |
| /// | |
| /// ```swift | |
| /// @Environment(\.settings.notificationsEnabled) private var notificationsEnabled | |
| /// @Environment(\.updateSettings) private var updateSettings | |
| /// | |
| /// var body: some View { | |
| /// Toggle( | |
| /// "Enable Notifications", | |
| /// isOn: Binding( | |
| /// get: { notificationsEnabled }, | |
| /// set: { updateSettings(\.notificationsEnabled, $0) } | |
| /// ) | |
| /// ) | |
| /// } | |
| /// ``` | |
| struct Updater<Root>: Hashable, Identifiable { | |
| let id: AnyHashable | |
| private let update: ((inout Root) -> Void) -> Void | |
| /// Creates an updater with the given identity and mutation handler. | |
| /// | |
| /// - Parameters: | |
| /// - id: A stable, unique identifier for this updater, preferably tied | |
| /// to the lifetime of the view that owns the state being updated. | |
| /// - update: A closure that receives a transform function. Call the | |
| /// transform with a mutable reference to the root value to apply | |
| /// the change. | |
| init( | |
| id: some Hashable, | |
| update: @escaping ((inout Root) -> Void) -> Void | |
| ) { | |
| self.id = AnyHashable(id) | |
| self.update = update | |
| } | |
| /// Updates the property at the given key path to the specified value. | |
| /// | |
| /// - Parameters: | |
| /// - keyPath: A writable key path from `Root` to the property to update. | |
| /// - value: The new value to assign. | |
| func callAsFunction<Value>(_ keyPath: WritableKeyPath<Root, Value>, _ value: Value) { | |
| let transform: (inout Root) -> Void = { root in | |
| root[keyPath: keyPath] = value | |
| } | |
| update(transform) | |
| } | |
| /// Updates the root value with the specified value. | |
| /// | |
| /// - Parameter root: The new root value to assign. | |
| func callAsFunction(_ root: Root) { | |
| let transform: (inout Root) -> Void = { $0 = root } | |
| update(transform) | |
| } | |
| func hash(into hasher: inout Hasher) { | |
| hasher.combine(id) | |
| hasher.combine(ObjectIdentifier(Root.self)) | |
| } | |
| static func == (lhs: Self, rhs: Self) -> Bool { | |
| lhs.id == rhs.id | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment