Skip to content

Commit ef80997

Browse files
committed
Simplify ViewEnvironmentUI protocols
1 parent 6935bf8 commit ef80997

7 files changed

+113
-101
lines changed

ViewEnvironmentUI/Sources/ViewEnvironmentCustomizing.swift

-30
This file was deleted.

ViewEnvironmentUI/Sources/ViewEnvironmentObserving.swift

+28-5
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@
1616

1717
import ViewEnvironment
1818

19-
/// `ViewEnvironmentObserving` allows an environment propagation node to observe updates to the
19+
/// `ViewEnvironmentPropagating` allows an environment propagation node to observe updates to the
2020
/// `ViewEnvironment` as it flows through the node hierarchy and have
2121
/// the environment applied to the node.
2222
///
2323
/// For example, for a `UIViewController` hierarchy observing `ViewEnvironment`:
2424
/// ```swift
2525
/// final class MyViewController:
26-
/// UIViewController, ViewEnvironmentObserving
26+
/// UIViewController, ViewEnvironmentPropagating
2727
/// {
2828
/// override func viewWillLayoutSubviews() {
2929
/// super.viewWillLayoutSubviews()
@@ -47,9 +47,18 @@ import ViewEnvironment
4747
/// - Important: `UIViewController` and `UIView` conformers _must_ call ``applyEnvironmentIfNeeded()-3bamq``
4848
/// in `viewWillLayoutSubviews()` and `layoutSubviews()` respectively.
4949
///
50-
/// - Tag: ViewEnvironmentObserving
51-
///
52-
public protocol ViewEnvironmentObserving: ViewEnvironmentCustomizing {
50+
public protocol ViewEnvironmentObserving: ViewEnvironmentPropagating {
51+
/// Customizes the `ViewEnvironment` as it flows through this propagation node to provide overrides to environment
52+
/// values. These changes will be propagated to all descendant nodes.
53+
///
54+
/// If you'd like to just inherit the environment from above, leave this function body empty.
55+
///
56+
/// - Important: `UIViewController` and `UIView` conformers _must_ call
57+
/// ``ViewEnvironmentObserving/applyEnvironmentIfNeeded()-8gr5k``in `viewWillLayoutSubviews()` and
58+
/// `layoutSubviews()` respectively.
59+
///
60+
func customize(environment: inout ViewEnvironment)
61+
5362
/// Consumers should apply the `ViewEnvironment` to their node when this function is called.
5463
///
5564
/// - Important: `UIViewController` and `UIView` conformers _must_ call ``applyEnvironmentIfNeeded()-3bamq``
@@ -66,11 +75,25 @@ public protocol ViewEnvironmentObserving: ViewEnvironmentCustomizing {
6675
///
6776
func applyEnvironmentIfNeeded()
6877

78+
/// Called when the environment has been set for needing update, but before it has been applied.
79+
///
80+
/// This may be called frequently when compared to ``apply(environment:)`` which should only be called
81+
/// when it's appropriate to apply the environment to the backing object (e.g. `viewWillLayoutSubviews`).
82+
///
6983
func environmentDidChange()
84+
85+
@_spi(ViewEnvironmentWiring)
86+
var _environmentOverride: ViewEnvironment { get }
7087
}
7188

7289
extension ViewEnvironmentObserving {
90+
public func customize(environment: inout ViewEnvironment) {}
91+
7392
public func apply(environment: ViewEnvironment) {}
7493

7594
public func environmentDidChange() {}
95+
96+
// Using SPI for this property will cause consumers to be unable to synthesize the default implementation if they do
97+
// not @_spi(ViewEnvironmentWiring) import ViewEnvironmentUI.
98+
public var _environmentOverride: ViewEnvironment { _defaultEnvironment }
7699
}

ViewEnvironmentUI/Sources/ViewEnvironmentPropagating.swift

+56-34
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,37 @@
1616

1717
import ViewEnvironment
1818

19+
/// `ViewEnvironmentPropagating` allows an environment propagation node to observe updates to the
20+
/// `ViewEnvironment` as it flows through the node hierarchy and have
21+
/// the environment applied to the node.
22+
///
23+
/// For example, for a `UIViewController` hierarchy observing `ViewEnvironment`:
24+
/// ```swift
25+
/// final class MyViewController:
26+
/// UIViewController, ViewEnvironmentPropagating
27+
/// {
28+
/// override func viewWillLayoutSubviews() {
29+
/// super.viewWillLayoutSubviews()
30+
///
31+
/// // You _must_ call this function in viewWillLayoutSubviews()
32+
/// applyEnvironmentIfNeeded()
33+
/// }
34+
///
35+
/// func apply(environment: ViewEnvironment) {
36+
/// // Apply values from the environment to your view controller (e.g. a theme)
37+
/// }
38+
///
39+
/// // If you'd like to override values in the environment you can provide them here. If you'd
40+
/// // like to just inherit the context from above there is no need to implement this function.
41+
/// func customize(environment: inout ViewEnvironment) {
42+
/// environment.traits.mode = .dark
43+
/// }
44+
/// }
45+
/// ```
46+
///
47+
/// - Important: `UIViewController` and `UIView` conformers _must_ call ``applyEnvironmentIfNeeded()-3bamq``
48+
/// in `viewWillLayoutSubviews()` and `layoutSubviews()` respectively.
49+
///
1950
public protocol ViewEnvironmentPropagating {
2051
/// Calling this will flag this node for needing to update the `ViewEnvironment`. For `UIView`/`UIViewController`,
2152
/// this will occur on the next layout pass (`setNeedsLayout` will be called on the caller's behalf).
@@ -39,6 +70,10 @@ public protocol ViewEnvironmentPropagating {
3970
/// ``ViewEnvironmentPropagatingObject/environmentAncestorOverride`` property. If no override is present, the
4071
/// return value will be `parent ?? presentingViewController`/`superview`.
4172
///
73+
/// If the value of the ancestor is `nil`, by default, ancestors will not call notify this node of needing an
74+
/// environment update as it changes. This allows a node to effectively act as a root node when needed (e.g.
75+
/// bridging from other propagation systems like WorkflowUI).
76+
///
4277
@_spi(ViewEnvironmentWiring)
4378
var environmentAncestor: ViewEnvironmentPropagating? { get }
4479

@@ -54,34 +89,6 @@ public protocol ViewEnvironmentPropagating {
5489
///
5590
@_spi(ViewEnvironmentWiring)
5691
var environmentDescendants: [ViewEnvironmentPropagating] { get }
57-
58-
/// The `ViewEnvironment` that is flowing through the propagation hierarchy.
59-
///
60-
/// If you'd like to provide overrides for the environment as it flows through a node, you should conform to
61-
/// `ViewEnvironmentObserving` and provide those overrides in `customize(environment:)`. E.g.:
62-
/// ```swift
63-
/// func customize(environment: inout ViewEnvironment) {
64-
/// environment.traits.mode = .dark
65-
/// }
66-
/// ```
67-
///
68-
/// By default, this property gets the environment by recursively walking to the root of the
69-
/// propagation path, and applying customizations on the way back down. You may override this
70-
/// property instead if you want to completely interrupt the propagation flow and replace the
71-
/// environment. You can get the default value that would normally be propagated by calling
72-
/// `_defaultViewEnvironment`.
73-
///
74-
/// If you'd like to update the return value of this variable and have those changes propagated through the
75-
/// propagation hierarchy, conform to `ViewEnvironmentObserving` and call ``setNeedsEnvironmentUpdate()`` and wait
76-
/// for the system to call `apply(context:)` when appropriate (e.g. on the next layout pass for
77-
/// `UIViewController`/`UIView` subclasses).
78-
///
79-
/// - Important: `UIViewController` and `UIView` conformers _must_ call
80-
/// ``ViewEnvironmentObserving/applyEnvironmentIfNeeded()-8gr5k`` in `viewWillLayoutSubviews()` and
81-
/// `layoutSubviews()` respectively.
82-
///
83-
@_spi(ViewEnvironmentWiring)
84-
var environment: ViewEnvironment { get }
8592
}
8693

8794
extension ViewEnvironmentPropagating {
@@ -99,7 +106,7 @@ extension ViewEnvironmentPropagating {
99106
/// propagation path, and applying customizations on the way back down. You may override this
100107
/// property instead if you want to completely interrupt the propagation flow and replace the
101108
/// environment. You can get the default value that would normally be propagated by calling
102-
/// `_defaultViewEnvironment`.
109+
/// `_defaultEnvironment`.
103110
///
104111
/// If you'd like to update the return value of this variable and have those changes propagated through the
105112
/// propagation hierarchy, conform to `ViewEnvironmentObserving` and call ``setNeedsEnvironmentUpdate()`` and wait
@@ -111,7 +118,7 @@ extension ViewEnvironmentPropagating {
111118
/// `layoutSubviews()` respectively.
112119
///
113120
public var environment: ViewEnvironment {
114-
_defaultViewEnvironment
121+
(self as? ViewEnvironmentObserving)?._environmentOverride ?? _defaultEnvironment
115122
}
116123

117124
/// The default `ViewEnvironment` returned by ``environment``.
@@ -122,15 +129,30 @@ extension ViewEnvironmentPropagating {
122129
/// You should only need to access this value if you are overriding ``environment``
123130
/// and want to conditionally return the default.
124131
@_spi(ViewEnvironmentWiring)
125-
public var _defaultViewEnvironment: ViewEnvironment {
126-
var environment = environmentAncestor?.environment
127-
?? .empty
132+
public var _defaultEnvironment: ViewEnvironment {
133+
var environment: ViewEnvironment = {
134+
guard let ancestor = environmentAncestor else {
135+
return .empty
136+
}
137+
138+
if let observing = ancestor as? ViewEnvironmentObserving {
139+
return observing.environment
140+
}
128141

129-
(self as? ViewEnvironmentCustomizing)?.customize(environment: &environment)
142+
return ancestor._defaultEnvironment
143+
}()
144+
145+
if let observing = self as? ViewEnvironmentObserving {
146+
observing.customize(environment: &environment)
147+
}
130148

131149
return environment
132150
}
133151

152+
/// Notifies all appropriate descendants that the environment needs update.
153+
///
154+
/// If a descendant's ancestor is `nil` it will not be notified of needing update.
155+
///
134156
@_spi(ViewEnvironmentWiring)
135157
public func setNeedsEnvironmentUpdateOnAppropriateDescendants() {
136158
for descendant in environmentDescendants {

ViewEnvironmentUI/Sources/ViewEnvironmentPropagatingObject.swift

+10-9
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,16 @@ public protocol ViewEnvironmentPropagatingObject: AnyObject, ViewEnvironmentProp
4545
func setNeedsApplyEnvironment()
4646
}
4747

48-
extension ViewEnvironmentObserving where Self: ViewEnvironmentPropagatingObject {
48+
extension ViewEnvironmentPropagating where Self: ViewEnvironmentPropagatingObject {
4949
public func applyEnvironmentIfNeeded() {
5050
guard needsEnvironmentUpdate else { return }
5151

5252
needsEnvironmentUpdate = false
5353

54-
apply(environment: environment)
54+
if let observing = self as? ViewEnvironmentObserving {
55+
let environment = observing.environment
56+
observing.apply(environment: environment)
57+
}
5558
}
5659
}
5760

@@ -216,17 +219,15 @@ extension ViewEnvironmentPropagatingObject {
216219

217220
if let observing = self as? ViewEnvironmentObserving {
218221
observing.environmentDidChange()
219-
}
220222

221-
if !needsUpdateObservers.isEmpty {
222-
let environment = self.environment
223+
if !needsUpdateObservers.isEmpty {
224+
let environment = observing.environment
223225

224-
for observer in needsUpdateObservers.values {
225-
observer(environment)
226+
for observer in needsUpdateObservers.values {
227+
observer(environment)
228+
}
226229
}
227-
}
228230

229-
if self is ViewEnvironmentObserving {
230231
setNeedsApplyEnvironment()
231232
}
232233

ViewEnvironmentUI/Sources/ViewEnvironmentPropagationNode.swift

+11-21
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import ViewEnvironment
2222
/// environment as it flows between two nodes.
2323
///
2424
@_spi(ViewEnvironmentWiring)
25-
public class ViewEnvironmentPropagationNode: ViewEnvironmentCustomizing, ViewEnvironmentObserving {
25+
public class ViewEnvironmentPropagationNode: ViewEnvironmentPropagatingObject, ViewEnvironmentObserving {
2626
public typealias EnvironmentAncestorProvider = () -> ViewEnvironmentPropagating?
2727

2828
public typealias EnvironmentDescendantsProvider = () -> [ViewEnvironmentPropagating]
@@ -47,8 +47,6 @@ public class ViewEnvironmentPropagationNode: ViewEnvironmentCustomizing, ViewEnv
4747
didSet { setNeedsEnvironmentUpdate() }
4848
}
4949

50-
private var needsEnvironmentUpdate: Bool = true
51-
5250
public init(
5351
environmentAncestor: @escaping EnvironmentAncestorProvider = { nil },
5452
environmentDescendants: @escaping EnvironmentDescendantsProvider = { [] },
@@ -63,20 +61,20 @@ public class ViewEnvironmentPropagationNode: ViewEnvironmentCustomizing, ViewEnv
6361
self.applyEnvironment = applyEnvironment
6462
}
6563

66-
public var environmentAncestor: ViewEnvironmentPropagating? { environmentAncestorProvider() }
67-
68-
public var environmentDescendants: [ViewEnvironmentPropagating] { environmentDescendantsProvider() }
69-
70-
public func customize(environment: inout ViewEnvironment) {
71-
customizeEnvironment(&environment)
64+
public var defaultEnvironmentAncestor: ViewEnvironmentPropagating? {
65+
environmentAncestorProvider()
7266
}
7367

74-
public func setNeedsEnvironmentUpdate() {
75-
needsEnvironmentUpdate = true
68+
public var defaultEnvironmentDescendants: [ViewEnvironmentPropagating] {
69+
environmentDescendantsProvider()
70+
}
7671

77-
environmentDidChange()
72+
public func setNeedsApplyEnvironment() {
73+
applyEnvironmentIfNeeded()
74+
}
7875

79-
setNeedsEnvironmentUpdateOnAppropriateDescendants()
76+
public func customize(environment: inout ViewEnvironment) {
77+
customizeEnvironment(&environment)
8078
}
8179

8280
public func environmentDidChange() {
@@ -85,14 +83,6 @@ public class ViewEnvironmentPropagationNode: ViewEnvironmentCustomizing, ViewEnv
8583
didChange(environment)
8684
}
8785

88-
public func applyEnvironmentIfNeeded() {
89-
guard needsEnvironmentUpdate else { return }
90-
91-
needsEnvironmentUpdate = false
92-
93-
apply(environment: environment)
94-
}
95-
9686
public func apply(environment: ViewEnvironment) {
9787
applyEnvironment(environment)
9888
}

WorkflowUI/Sources/Hosting/WorkflowHostingController.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ public final class WorkflowHostingController<ScreenType, Output>: UIViewControll
7676
.observeValues { [weak self] screen in
7777
self?.update(screen: screen)
7878
}
79+
80+
setNeedsEnvironmentUpdate()
7981
}
8082

8183
/// Updates the root Workflow in this container.
@@ -180,7 +182,7 @@ public final class WorkflowHostingController<ScreenType, Output>: UIViewControll
180182
}
181183
}
182184

183-
extension WorkflowHostingController: ViewEnvironmentCustomizing, ViewEnvironmentObserving {
185+
extension WorkflowHostingController: ViewEnvironmentObserving {
184186
public func customize(environment: inout ViewEnvironment) {
185187
customizeEnvironment(&environment)
186188
}

WorkflowUI/Sources/ViewControllerDescription/ViewControllerDescription.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ extension ViewControllerDescription {
213213
}
214214

215215
extension ViewControllerDescription {
216-
fileprivate struct PropagationNode: ViewEnvironmentCustomizing {
216+
fileprivate struct PropagationNode: ViewEnvironmentObserving {
217217
typealias EnvironmentAncestorProvider = () -> ViewEnvironmentPropagating?
218218

219219
typealias EnvironmentDescendantsProvider = () -> [ViewEnvironmentPropagating]
@@ -253,6 +253,10 @@ extension ViewControllerDescription {
253253
func setNeedsEnvironmentUpdate() {
254254
setNeedsEnvironmentUpdateOnAppropriateDescendants()
255255
}
256+
257+
func applyEnvironmentIfNeeded() {
258+
/// `apply(environment:)` is not implemented so do nothing.
259+
}
256260
}
257261
}
258262

0 commit comments

Comments
 (0)