@@ -119,6 +119,10 @@ extension ViewEnvironmentPropagating {
119119 public var environment : ViewEnvironment {
120120 var environment = environmentAncestor? . environment ?? . empty
121121
122+ for storedCustomization in customizations {
123+ storedCustomization. customization ( & environment)
124+ }
125+
122126 if let observing = self as? ViewEnvironmentObserving {
123127 observing. customize ( environment: & environment)
124128 }
@@ -167,7 +171,7 @@ extension ViewEnvironmentPropagating {
167171 ///
168172 @_spi ( ViewEnvironmentWiring)
169173 public func addEnvironmentNeedsUpdateObserver(
170- _ onNeedsUpdate: @escaping ( ViewEnvironment ) -> Void
174+ _ onNeedsUpdate: @escaping ViewEnvironmentUpdateObservation
171175 ) -> ViewEnvironmentUpdateObservationLifetime {
172176 let object = ViewEnvironmentUpdateObservationKey ( )
173177 needsUpdateObservers [ object] = onNeedsUpdate
@@ -176,6 +180,31 @@ extension ViewEnvironmentPropagating {
176180 }
177181 }
178182
183+ /// Adds a `ViewEnvironment` customization to this node.
184+ ///
185+ /// These customizations will occur before the node's `customize(environment:)` in cases where
186+ /// this node conforms to `ViewEnvironmentObserving`, and will occur the order in which they
187+ /// were added.
188+ ///
189+ /// The customization will only be active for as long as the returned lifetime is retained or
190+ /// until `remove()` is called on it.
191+ ///
192+ @_spi ( ViewEnvironmentWiring)
193+ public func addEnvironmentCustomization(
194+ _ customization: @escaping ViewEnvironmentCustomization
195+ ) -> ViewEnvironmentCustomizationLifetime {
196+ let storedCustomization = StoredViewEnvironmentCustomization ( customization: customization)
197+ customizations. append ( storedCustomization)
198+ return . init { [ weak self] in
199+ guard let self,
200+ let index = self . customizations. firstIndex ( where: { $0 === storedCustomization } )
201+ else {
202+ return
203+ }
204+ self . customizations. remove ( at: index)
205+ }
206+ }
207+
179208 /// The `ViewEnvironment` propagation ancestor.
180209 ///
181210 /// This describes the ancestor that the `ViewEnvironment` is inherited from.
@@ -348,20 +377,26 @@ public typealias ViewEnvironmentUpdateObservation = (ViewEnvironment) -> Void
348377public final class ViewEnvironmentUpdateObservationLifetime {
349378 /// Removes the observation.
350379 ///
351- /// This is called in `deinit`.
380+ /// The observation is removed when the lifetime is de-initialized if this function was not
381+ /// called before then.
352382 ///
353383 public func remove( ) {
384+ guard let onRemove else {
385+ assertionFailure ( " Environment update observation was already removed " )
386+ return
387+ }
388+ self . onRemove = nil
354389 onRemove ( )
355390 }
356391
357- private let onRemove : ( ) -> Void
392+ private var onRemove : ( ( ) -> Void ) ?
358393
359394 init ( onRemove: @escaping ( ) -> Void ) {
360395 self . onRemove = onRemove
361396 }
362397
363398 deinit {
364- remove ( )
399+ onRemove ? ( )
365400 }
366401}
367402
@@ -370,6 +405,7 @@ private enum ViewEnvironmentPropagatingNSObjectAssociatedKeys {
370405 static var needsUpdateObservers = NSObject ( )
371406 static var ancestorOverride = NSObject ( )
372407 static var descendantsOverride = NSObject ( )
408+ static var customizations = NSObject ( )
373409}
374410
375411extension ViewEnvironmentPropagating {
@@ -432,3 +468,68 @@ extension ViewEnvironmentPropagating {
432468}
433469
434470private class ViewEnvironmentUpdateObservationKey : NSObject { }
471+
472+ /// A closure that customizes the `ViewEnvironment` as it flows through a propagation node.
473+ ///
474+ public typealias ViewEnvironmentCustomization = ( inout ViewEnvironment ) -> Void
475+
476+ /// Describes the lifetime of a `ViewEnvironment` customization.
477+ ///
478+ /// This customization will be removed when `remove()` is called or the lifetime token is
479+ /// de-initialized.
480+ ///
481+ /// ## SeeAlso ##
482+ /// - `addEnvironmentCustomization(_:)`
483+ ///
484+ public final class ViewEnvironmentCustomizationLifetime {
485+ /// Removes the customization.
486+ ///
487+ /// The customization is removed when the lifetime is de-initialized if this function was not
488+ /// called before then.
489+ ///
490+ public func remove( ) {
491+ guard let onRemove else {
492+ assertionFailure ( " Environment customization was already removed " )
493+ return
494+ }
495+ self . onRemove = nil
496+ onRemove ( )
497+ }
498+
499+ private var onRemove : ( ( ) -> Void ) ?
500+
501+ init ( onRemove: @escaping ( ) -> Void ) {
502+ self . onRemove = onRemove
503+ }
504+
505+ deinit {
506+ onRemove ? ( )
507+ }
508+ }
509+
510+ extension ViewEnvironmentPropagating {
511+ fileprivate var customizations : [ StoredViewEnvironmentCustomization ] {
512+ get {
513+ objc_getAssociatedObject (
514+ self ,
515+ & AssociatedKeys. customizations
516+ ) as? [ StoredViewEnvironmentCustomization ] ?? [ ]
517+ }
518+ set {
519+ objc_setAssociatedObject (
520+ self ,
521+ & AssociatedKeys. customizations,
522+ newValue,
523+ . OBJC_ASSOCIATION_RETAIN_NONATOMIC
524+ )
525+ }
526+ }
527+ }
528+
529+ private final class StoredViewEnvironmentCustomization {
530+ var customization : ViewEnvironmentCustomization
531+
532+ init ( customization: @escaping ViewEnvironmentCustomization ) {
533+ self . customization = customization
534+ }
535+ }
0 commit comments