@@ -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
+ /// 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
+
179
208
/// The `ViewEnvironment` propagation ancestor.
180
209
///
181
210
/// This describes the ancestor that the `ViewEnvironment` is inherited from.
@@ -348,20 +377,26 @@ public typealias ViewEnvironmentUpdateObservation = (ViewEnvironment) -> Void
348
377
public final class ViewEnvironmentUpdateObservationLifetime {
349
378
/// Removes the observation.
350
379
///
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.
352
382
///
353
383
public func remove( ) {
384
+ guard let onRemove else {
385
+ assertionFailure ( " Environment update observation was already removed " )
386
+ return
387
+ }
388
+ self . onRemove = nil
354
389
onRemove ( )
355
390
}
356
391
357
- private let onRemove : ( ) -> Void
392
+ private var onRemove : ( ( ) -> Void ) ?
358
393
359
394
init ( onRemove: @escaping ( ) -> Void ) {
360
395
self . onRemove = onRemove
361
396
}
362
397
363
398
deinit {
364
- remove ( )
399
+ onRemove ? ( )
365
400
}
366
401
}
367
402
@@ -370,6 +405,7 @@ private enum ViewEnvironmentPropagatingNSObjectAssociatedKeys {
370
405
static var needsUpdateObservers = NSObject ( )
371
406
static var ancestorOverride = NSObject ( )
372
407
static var descendantsOverride = NSObject ( )
408
+ static var customizations = NSObject ( )
373
409
}
374
410
375
411
extension ViewEnvironmentPropagating {
@@ -432,3 +468,68 @@ extension ViewEnvironmentPropagating {
432
468
}
433
469
434
470
private 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