From 7ac47b63e5e0e9b7838b1b5ec27bd075c01c114c Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 16 Oct 2024 10:09:09 -0700 Subject: [PATCH] Support eager navigation destination resolution Currently we steer folks to using `viewDidLoad` for all observation, but this laziness can lead to issues with deep-linking in type-erased navigation stacks, where a destination may declare a sub-destination in its `viewDidLoad`, causing the first drill-down to occur lazily over multiple animated steps instead of a single one. This behavior can be seen in the type-erased navigation case study in the repository. This PR is a proof of concept to show that the library can populate a "current navigation stack" in the UI transaction so that a destination's initializer can declare its dependent destinations, fixing the case study behavior. --- .../ErasedNavigationStackController.swift | 16 +++++++-------- .../NavigationStackController.swift | 20 +++++++++++++++++-- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/Examples/CaseStudies/UIKit/ErasedNavigationStackController.swift b/Examples/CaseStudies/UIKit/ErasedNavigationStackController.swift index c77aae3e3..e2eb3fb37 100644 --- a/Examples/CaseStudies/UIKit/ErasedNavigationStackController.swift +++ b/Examples/CaseStudies/UIKit/ErasedNavigationStackController.swift @@ -81,6 +81,10 @@ private class NumberFeatureViewController: UIViewController { self.number = number super.init(nibName: nil, bundle: nil) title = "Feature \(number)" + + navigationDestination(for: String.self) { string in + StringFeatureViewController(string: string) + } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") @@ -89,10 +93,6 @@ private class NumberFeatureViewController: UIViewController { super.viewDidLoad() view.backgroundColor = .systemBackground - navigationDestination(for: String.self) { string in - StringFeatureViewController(string: string) - } - let numberButton = UIButton( type: .system, primaryAction: UIAction { [weak self] _ in @@ -137,6 +137,10 @@ private class StringFeatureViewController: UIViewController { self.string = string super.init(nibName: nil, bundle: nil) title = "Feature '\(string)'" + + navigationDestination(for: Bool.self) { bool in + BoolFeatureViewController(bool: bool) + } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") @@ -145,10 +149,6 @@ private class StringFeatureViewController: UIViewController { super.viewDidLoad() view.backgroundColor = .systemBackground - navigationDestination(for: Bool.self) { bool in - BoolFeatureViewController(bool: bool) - } - let numberButton = UIButton( type: .system, primaryAction: UIAction { [weak self] _ in diff --git a/Sources/UIKitNavigation/Navigation/NavigationStackController.swift b/Sources/UIKitNavigation/Navigation/NavigationStackController.swift index 54cc23f48..feda12692 100644 --- a/Sources/UIKitNavigation/Navigation/NavigationStackController.swift +++ b/Sources/UIKitNavigation/Navigation/NavigationStackController.swift @@ -338,14 +338,19 @@ ) return } - stackController.path.append(.lazy(.element(value))) + withUITransaction(\.stackController, stackController) { + stackController.path.append(.lazy(.element(value))) + } } public func navigationDestination( for data: D.Type, destination: @escaping (D) -> UIViewController ) { - guard let navigationController = navigationController ?? self as? UINavigationController + guard + let navigationController = UITransaction.current.stackController + ?? navigationController + ?? self as? UINavigationController else { reportIssue( """ @@ -442,4 +447,15 @@ } } } + + private extension UITransaction { + var stackController: NavigationStackController? { + get { self[NavigationStackControllerKey.self] } + set { self[NavigationStackControllerKey.self] = newValue } + } + } + + private enum NavigationStackControllerKey: UITransactionKey { + static let defaultValue: NavigationStackController? = nil + } #endif