Skip to content

Commit 8810208

Browse files
committed
[feat]: add imperative environment customization support
1 parent eb3f60b commit 8810208

File tree

1 file changed

+90
-1
lines changed

1 file changed

+90
-1
lines changed

ViewEnvironmentUI/Sources/ViewEnvironmentPropagating.swift

+90-1
Original file line numberDiff line numberDiff line change
@@ -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+
/// `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+
179208
/// The `ViewEnvironment` propagation ancestor.
180209
///
181210
/// This describes the ancestor that the `ViewEnvironment` is inherited from.
@@ -370,6 +399,7 @@ private enum ViewEnvironmentPropagatingNSObjectAssociatedKeys {
370399
static var needsUpdateObservers = NSObject()
371400
static var ancestorOverride = NSObject()
372401
static var descendantsOverride = NSObject()
402+
static var customizations = NSObject()
373403
}
374404

375405
extension ViewEnvironmentPropagating {
@@ -432,3 +462,62 @@ extension ViewEnvironmentPropagating {
432462
}
433463

434464
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

Comments
 (0)