Skip to content

Commit 6cd16c1

Browse files
committed
Move bridging to ViewControllerDescription
1 parent 8c2c207 commit 6cd16c1

File tree

2 files changed

+97
-19
lines changed

2 files changed

+97
-19
lines changed

WorkflowUI/Sources/Screen/ScreenViewController.swift

+9-19
Original file line numberDiff line numberDiff line change
@@ -44,36 +44,25 @@ open class ScreenViewController<ScreenType: Screen>: UIViewController {
4444
ScreenType.self
4545
}
4646

47-
private var _environment: ViewEnvironment
47+
private var previousEnvironment: ViewEnvironment
4848

4949
public required init(screen: ScreenType, environment: ViewEnvironment) {
5050
self.screen = screen
51-
self._environment = environment
51+
self.previousEnvironment = environment
5252
super.init(nibName: nil, bundle: nil)
53-
54-
let ancestor = ViewEnvironmentPropagationNode(
55-
environmentDescendants: { [weak self] in
56-
[self].compactMap { $0 }
57-
},
58-
customizeEnvironment: { [weak self] environment in
59-
guard let self else { return }
60-
environment = self._environment
61-
}
62-
)
63-
64-
environmentAncestorOverride = { ancestor }
6553
}
6654

6755
@available(*, unavailable)
6856
public required init?(coder aDecoder: NSCoder) { fatalError() }
6957

70-
public final func update(screen: ScreenType, environment: ViewEnvironment) {
58+
public final func update(screen: ScreenType) {
7159
let previousScreen = self.screen
7260
self.screen = screen
73-
let previousEnvironment = self.environment
74-
_environment = environment
61+
62+
let previousEnvironment = self.previousEnvironment
63+
self.previousEnvironment = environment
64+
7565
screenDidChange(from: previousScreen, previousEnvironment: previousEnvironment)
76-
setNeedsEnvironmentUpdate()
7766
}
7867

7968
open func screenDidChange(from previousScreen: ScreenType, previousEnvironment: ViewEnvironment) {}
@@ -90,9 +79,10 @@ extension ScreenViewController {
9079
) -> ViewControllerDescription {
9180
ViewControllerDescription(
9281
performInitialUpdate: performInitialUpdate,
82+
environment: environment,
9383
type: self,
9484
build: { self.init(screen: screen, environment: environment) },
95-
update: { $0.update(screen: screen, environment: environment) }
85+
update: { $0.update(screen: screen) }
9686
)
9787
}
9888
}

WorkflowUI/Sources/ViewControllerDescription/ViewControllerDescription.swift

+88
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#if canImport(UIKit)
1818

1919
import UIKit
20+
@_spi(ViewEnvironmentWiring) import ViewEnvironmentUI
2021

2122
/// A ViewControllerDescription acts as a "recipe" for building and updating a specific `UIViewController`.
2223
/// It describes how to _create_ and later _update_ a given view controller instance, without creating one
@@ -52,6 +53,8 @@ public struct ViewControllerDescription {
5253
private let build: () -> UIViewController
5354
private let update: (UIViewController) -> Void
5455

56+
private let environment: ViewEnvironment
57+
5558
/// Constructs a view controller description by providing closures used to
5659
/// build and update a specific view controller type.
5760
///
@@ -69,6 +72,7 @@ public struct ViewControllerDescription {
6972
/// - update: Closure that updates the given view controller
7073
public init<VC: UIViewController>(
7174
performInitialUpdate: Bool = true,
75+
environment: ViewEnvironment,
7276
type: VC.Type = VC.self,
7377
build: @escaping () -> VC,
7478
update: @escaping (VC) -> Void
@@ -77,6 +81,8 @@ public struct ViewControllerDescription {
7781

7882
self.kind = .init(VC.self)
7983

84+
self.environment = environment
85+
8086
self.build = build
8187

8288
self.update = { untypedViewController in
@@ -96,6 +102,8 @@ public struct ViewControllerDescription {
96102
if performInitialUpdate {
97103
// Perform an initial update of the built view controller
98104
update(viewController: viewController)
105+
} else {
106+
configureAncestor(for: viewController, with: environment)
99107
}
100108

101109
return viewController
@@ -126,8 +134,44 @@ public struct ViewControllerDescription {
126134
"""
127135
)
128136

137+
configureAncestor(for: viewController, with: environment)
138+
129139
update(viewController)
130140
}
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+
}
131175
}
132176

133177
extension ViewControllerDescription {
@@ -168,4 +212,48 @@ extension ViewControllerDescription {
168212
}
169213
}
170214

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+
171259
#endif

0 commit comments

Comments
 (0)