@@ -119,6 +119,10 @@ extension ViewEnvironmentPropagating {
119
119
public var environment : ViewEnvironment {
120
120
var environment = environmentAncestor? . environment ?? . empty
121
121
122
+ for storedCustomization in customizations {
123
+ storedCustomization. customization ( & environment)
124
+ }
125
+
122
126
if let observing = self as? ViewEnvironmentObserving {
123
127
observing. customize ( environment: & environment)
124
128
}
@@ -167,7 +171,7 @@ extension ViewEnvironmentPropagating {
167
171
///
168
172
@_spi ( ViewEnvironmentWiring)
169
173
public func addEnvironmentNeedsUpdateObserver(
170
- _ onNeedsUpdate: @escaping ( ViewEnvironment ) -> Void
174
+ _ onNeedsUpdate: @escaping ViewEnvironmentUpdateObservation
171
175
) -> ViewEnvironmentUpdateObservationLifetime {
172
176
let object = ViewEnvironmentUpdateObservationKey ( )
173
177
needsUpdateObservers [ object] = onNeedsUpdate
@@ -176,6 +180,31 @@ extension ViewEnvironmentPropagating {
176
180
}
177
181
}
178
182
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
+ /// `cancel()` 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
+
179
208
/// The `ViewEnvironment` propagation ancestor.
180
209
///
181
210
/// This describes the ancestor that the `ViewEnvironment` is inherited from.
@@ -370,6 +399,7 @@ private enum ViewEnvironmentPropagatingNSObjectAssociatedKeys {
370
399
static var needsUpdateObservers = NSObject ( )
371
400
static var ancestorOverride = NSObject ( )
372
401
static var descendantsOverride = NSObject ( )
402
+ static var customizations = NSObject ( )
373
403
}
374
404
375
405
extension ViewEnvironmentPropagating {
@@ -432,3 +462,62 @@ extension ViewEnvironmentPropagating {
432
462
}
433
463
434
464
private class ViewEnvironmentUpdateObservationKey : NSObject { }
465
+
466
+ /// A closure that customizes the `ViewEnvironment` as it flows through a propagation node.
467
+ ///
468
+ public typealias ViewEnvironmentCustomization = ( inout ViewEnvironment ) -> Void
469
+
470
+ /// Describes the lifetime of a `ViewEnvironment` customization.
471
+ ///
472
+ /// This customization will be removed when `remove()` is called or the lifetime token is
473
+ /// de-initialized.
474
+ ///
475
+ /// ## SeeAlso ##
476
+ /// - `addEnvironmentCustomization(_:)`
477
+ ///
478
+ public final class ViewEnvironmentCustomizationLifetime {
479
+ /// Removes the observation.
480
+ ///
481
+ /// This is called in `deinit`.
482
+ ///
483
+ public func remove( ) {
484
+ onRemove ( )
485
+ }
486
+
487
+ private let onRemove : ( ) -> Void
488
+
489
+ init ( onRemove: @escaping ( ) -> Void ) {
490
+ self . onRemove = onRemove
491
+ }
492
+
493
+ deinit {
494
+ remove ( )
495
+ }
496
+ }
497
+
498
+ extension ViewEnvironmentPropagating {
499
+ fileprivate var customizations : [ StoredViewEnvironmentCustomization ] {
500
+ get {
501
+ objc_getAssociatedObject (
502
+ self ,
503
+ & AssociatedKeys. customizations
504
+ ) as? [ StoredViewEnvironmentCustomization ] ?? [ ]
505
+ }
506
+ set {
507
+ objc_setAssociatedObject (
508
+ self ,
509
+ & AssociatedKeys. customizations,
510
+ newValue,
511
+ . OBJC_ASSOCIATION_RETAIN_NONATOMIC
512
+ )
513
+ }
514
+ }
515
+ }
516
+
517
+ private final class StoredViewEnvironmentCustomization {
518
+ var customization : ViewEnvironmentCustomization
519
+
520
+ init ( customization: @escaping ViewEnvironmentCustomization ) {
521
+ self . customization = customization
522
+ }
523
+ }
0 commit comments