Skip to content

Commit 10c5a29

Browse files
committed
[Prototype] Update WorkflowHostinController on layout if ancestor hierarchy has changed
1 parent 95ddd1e commit 10c5a29

File tree

3 files changed

+85
-5
lines changed

3 files changed

+85
-5
lines changed

ViewEnvironmentUI/Sources/ViewEnvironmentPropagating.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,27 @@ extension ViewEnvironmentPropagating {
272272
objc_setAssociatedObject(self, &AssociatedKeys.descendantsOverride, newValue, .OBJC_ASSOCIATION_RETAIN)
273273
}
274274
}
275+
276+
@_spi(ViewEnvironmentWiring)
277+
public typealias EnvironmentAncestorPath = ViewEnvironmentPropagatingAncestorPath
278+
279+
@_spi(ViewEnvironmentWiring)
280+
public var environmentAncestorPath: EnvironmentAncestorPath {
281+
var path = EnvironmentAncestorPath()
282+
283+
if let first = environmentAncestor {
284+
for node in sequence(first: first, next: \.environmentAncestor) {
285+
path.nodes.append(ObjectIdentifier(node))
286+
}
287+
}
288+
289+
return path
290+
}
291+
}
292+
293+
@_spi(ViewEnvironmentWiring)
294+
public struct ViewEnvironmentPropagatingAncestorPath: Equatable {
295+
var nodes: [ObjectIdentifier] = []
275296
}
276297

277298
/// A closure that is called when the `ViewEnvironment` needs to be updated.

WorkflowUI/Sources/Hosting/WorkflowHostingController.swift

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ public final class WorkflowHostingController<ScreenType, Output>: WorkflowUIView
4141

4242
private let (lifetime, token) = Lifetime.make()
4343

44+
private var ancestorPath: EnvironmentAncestorPath?
45+
4446
public init<W: AnyWorkflowConvertible>(
4547
workflow: W,
4648
customizeEnvironment: @escaping CustomizeEnvironment = { _ in },
@@ -79,7 +81,7 @@ public final class WorkflowHostingController<ScreenType, Output>: WorkflowUIView
7981
.observeValues { [weak self] screen in
8082
guard let self = self else { return }
8183

82-
self.update(screen: screen, environment: self.environment)
84+
self.update(screen: screen, ancestorPath: self.environmentAncestorPath)
8385
}
8486
}
8587

@@ -92,7 +94,10 @@ public final class WorkflowHostingController<ScreenType, Output>: WorkflowUIView
9294
fatalError("init(coder:) has not been implemented")
9395
}
9496

95-
private func update(screen: ScreenType, environment: ViewEnvironment) {
97+
private func update(screen: ScreenType, ancestorPath: EnvironmentAncestorPath) {
98+
self.ancestorPath = ancestorPath
99+
100+
let environment = environment
96101
let previousRoot = rootViewController
97102

98103
update(child: \.rootViewController, with: screen, in: environment)
@@ -120,7 +125,11 @@ public final class WorkflowHostingController<ScreenType, Output>: WorkflowUIView
120125

121126
override public func viewWillLayoutSubviews() {
122127
super.viewWillLayoutSubviews()
123-
applyEnvironmentIfNeeded()
128+
129+
let ancestorPath = environmentAncestorPath
130+
if ancestorPath != self.ancestorPath {
131+
update(screen: workflowHost.rendering.value, ancestorPath: ancestorPath)
132+
}
124133
}
125134

126135
override public func viewDidLayoutSubviews() {
@@ -181,7 +190,7 @@ extension WorkflowHostingController: ViewEnvironmentObserving {
181190
}
182191

183192
public func environmentDidChange() {
184-
update(screen: workflowHost.rendering.value, environment: environment)
193+
update(screen: workflowHost.rendering.value, ancestorPath: environmentAncestorPath)
185194
}
186195
}
187196

WorkflowUI/Tests/WorkflowHostingControllerTests.swift

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,57 @@ class WorkflowHostingControllerTests: XCTestCase {
224224
XCTAssertEqual(environment[ScreenKey.self], true)
225225
}
226226
}
227+
228+
func test_environment_updates_on_layout_in_new_hierarchy() {
229+
var changedEnvironments: [ViewEnvironment] = []
230+
let hostingController = WorkflowHostingController(
231+
workflow: EnvironmentObservingWorkflow(
232+
value: "first",
233+
onEnvironmentDidChange: { changedEnvironments.append($0) }
234+
)
235+
)
236+
237+
let root1 = UIViewController()
238+
let container = UIViewController()
239+
root1.addChild(container)
240+
root1.view.addSubview(container.view)
241+
container.didMove(toParent: root1)
242+
243+
container.addChild(hostingController)
244+
container.view.addSubview(hostingController.view)
245+
hostingController.didMove(toParent: container)
246+
247+
XCTAssertEqual(changedEnvironments.count, 1)
248+
249+
hostingController.view.setNeedsLayout()
250+
hostingController.view.layoutIfNeeded()
251+
252+
XCTAssertEqual(changedEnvironments.count, 2)
253+
254+
hostingController.view.setNeedsLayout()
255+
hostingController.view.layoutIfNeeded()
256+
257+
XCTAssertEqual(changedEnvironments.count, 2)
258+
259+
container.willMove(toParent: nil)
260+
container.view.removeFromSuperview()
261+
container.removeFromParent()
262+
263+
let root2 = UIViewController()
264+
root2.addChild(container)
265+
root2.view.addSubview(container.view)
266+
container.didMove(toParent: root2)
267+
268+
hostingController.view.setNeedsLayout()
269+
hostingController.view.layoutIfNeeded()
270+
271+
XCTAssertEqual(changedEnvironments.count, 3)
272+
273+
hostingController.view.setNeedsLayout()
274+
hostingController.view.layoutIfNeeded()
275+
276+
XCTAssertEqual(changedEnvironments.count, 3)
277+
}
227278
}
228279

229280
fileprivate struct SubscribingWorkflow: Workflow {
@@ -303,7 +354,6 @@ fileprivate struct EnvironmentObservingWorkflow: Workflow {
303354
}
304355

305356
fileprivate final class EnvironmentCustomizingViewController: UIViewController, ViewEnvironmentObserving {
306-
307357
var customizeEnvironment: (inout ViewEnvironment) -> Void
308358

309359
init(customizeEnvironment: @escaping (inout ViewEnvironment) -> Void) {

0 commit comments

Comments
 (0)