17
17
#if canImport(UIKit)
18
18
19
19
import UIKit
20
+ @_spi ( ViewEnvironmentWiring) import ViewEnvironmentUI
20
21
21
22
/// A ViewControllerDescription acts as a "recipe" for building and updating a specific `UIViewController`.
22
23
/// It describes how to _create_ and later _update_ a given view controller instance, without creating one
@@ -52,6 +53,8 @@ public struct ViewControllerDescription {
52
53
private let build : ( ) -> UIViewController
53
54
private let update : ( UIViewController ) -> Void
54
55
56
+ private let environment : ViewEnvironment
57
+
55
58
/// Constructs a view controller description by providing closures used to
56
59
/// build and update a specific view controller type.
57
60
///
@@ -69,6 +72,7 @@ public struct ViewControllerDescription {
69
72
/// - update: Closure that updates the given view controller
70
73
public init < VC: UIViewController > (
71
74
performInitialUpdate: Bool = true ,
75
+ environment: ViewEnvironment ,
72
76
type: VC . Type = VC . self,
73
77
build: @escaping ( ) -> VC ,
74
78
update: @escaping ( VC ) -> Void
@@ -77,6 +81,8 @@ public struct ViewControllerDescription {
77
81
78
82
self . kind = . init( VC . self)
79
83
84
+ self . environment = environment
85
+
80
86
self . build = build
81
87
82
88
self . update = { untypedViewController in
@@ -96,6 +102,8 @@ public struct ViewControllerDescription {
96
102
if performInitialUpdate {
97
103
// Perform an initial update of the built view controller
98
104
update ( viewController: viewController)
105
+ } else {
106
+ configureAncestor ( for: viewController, with: environment)
99
107
}
100
108
101
109
return viewController
@@ -126,8 +134,44 @@ public struct ViewControllerDescription {
126
134
"""
127
135
)
128
136
137
+ configureAncestor ( for: viewController, with: environment)
138
+
129
139
update ( viewController)
130
140
}
141
+
142
+ private func configureAncestor( for viewController: UIViewController , with environment: ViewEnvironment ) {
143
+ guard let ancestorOverride = viewController. environmentAncestorOverride else {
144
+ establishAncestorOverride ( for: viewController, with: environment)
145
+ return
146
+ }
147
+
148
+ let currentAncestor = ancestorOverride ( )
149
+ guard currentAncestor is PropagationNode else {
150
+ // Do not override the VC's ancestor if it was overridden by something outside of the
151
+ // `ViewControllerDescription`'s management of this node.
152
+ // The view controller we're managing, or the container it's contained in, needs to manage this in a special
153
+ // way.
154
+ return
155
+ }
156
+
157
+ // We must nil this out first or we'll hit an assertion which protects against overriding the ancestor when
158
+ // some other system has already attempted to provide an override.
159
+ viewController. environmentAncestorOverride = nil
160
+ establishAncestorOverride ( for: viewController, with: environment)
161
+ }
162
+
163
+ private func establishAncestorOverride( for viewController: UIViewController , with environment: ViewEnvironment ) {
164
+ let ancestor = PropagationNode (
165
+ environmentAncestor: { nil } ,
166
+ environmentDescendants: { [ weak viewController] in
167
+ [ viewController ] . compactMap { $0 }
168
+ } ,
169
+ customizeEnvironment: { $0 = environment }
170
+ )
171
+ viewController. environmentAncestorOverride = { ancestor }
172
+
173
+ ancestor. setNeedsEnvironmentUpdate ( )
174
+ }
131
175
}
132
176
133
177
extension ViewControllerDescription {
@@ -168,4 +212,48 @@ extension ViewControllerDescription {
168
212
}
169
213
}
170
214
215
+ extension ViewControllerDescription {
216
+ fileprivate struct PropagationNode : ViewEnvironmentCustomizing {
217
+ typealias EnvironmentAncestorProvider = ( ) -> ViewEnvironmentPropagating ?
218
+
219
+ typealias EnvironmentDescendantsProvider = ( ) -> [ ViewEnvironmentPropagating ]
220
+
221
+ var environmentAncestorProvider : EnvironmentAncestorProvider {
222
+ didSet { setNeedsEnvironmentUpdate ( ) }
223
+ }
224
+
225
+ var environmentDescendantsProvider : EnvironmentDescendantsProvider {
226
+ didSet { setNeedsEnvironmentUpdate ( ) }
227
+ }
228
+
229
+ var customizeEnvironment : ( inout ViewEnvironment ) -> Void {
230
+ didSet { setNeedsEnvironmentUpdate ( ) }
231
+ }
232
+
233
+ private var needsEnvironmentUpdate : Bool = true
234
+
235
+ init (
236
+ environmentAncestor: @escaping EnvironmentAncestorProvider = { nil } ,
237
+ environmentDescendants: @escaping EnvironmentDescendantsProvider = { [ ] } ,
238
+ customizeEnvironment: @escaping ( inout ViewEnvironment ) -> Void = { _ in }
239
+ ) {
240
+ self . environmentAncestorProvider = environmentAncestor
241
+ self . environmentDescendantsProvider = environmentDescendants
242
+ self . customizeEnvironment = customizeEnvironment
243
+ }
244
+
245
+ var environmentAncestor : ViewEnvironmentPropagating ? { environmentAncestorProvider ( ) }
246
+
247
+ var environmentDescendants : [ ViewEnvironmentPropagating ] { environmentDescendantsProvider ( ) }
248
+
249
+ func customize( environment: inout ViewEnvironment ) {
250
+ customizeEnvironment ( & environment)
251
+ }
252
+
253
+ func setNeedsEnvironmentUpdate( ) {
254
+ setNeedsEnvironmentUpdateOnAppropriateDescendants ( )
255
+ }
256
+ }
257
+ }
258
+
171
259
#endif
0 commit comments