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/Examples/CaseStudiesTests/NavigationPathTests.swift b/Examples/CaseStudiesTests/NavigationPathTests.swift index 1dcc28214..f40a22c23 100644 --- a/Examples/CaseStudiesTests/NavigationPathTests.swift +++ b/Examples/CaseStudiesTests/NavigationPathTests.swift @@ -483,6 +483,59 @@ final class NavigationPathTests: XCTestCase { await assertEventuallyNoDifference(path.elements, [.eager(1), .eager("Hello"), .eager(true)]) } + @MainActor + func testPushMultipleFeaturesAtOnce_InitRegisteredNavigationDestination() async throws { + @UIBinding var path = UINavigationPath() + let nav = NavigationStackController(path: $path) { + InitRootViewController() + } + try await setUp(controller: nav) + + let root = nav.viewControllers[0] + withUITransaction(\.uiKit.disablesAnimations, true) { + root.traitCollection.push(value: 2) + root.traitCollection.push(value: "Hello") + root.traitCollection.push(value: true) + } + + await assertEventuallyEqual(nav.viewControllers.count, 4, timeout: 2) + await assertEventuallyNoDifference( + nav.values, + [2, "Hello", true] as [AnyHashable] + ) + await assertEventuallyNoDifference( + path.elements, + [.eager(2), .eager("Hello"), .eager(true)] + ) + } + + @MainActor + func testDeepLink_InitRegisteredNavigationDestination() async throws { + var initialPath = UINavigationPath() + initialPath.append(1) + initialPath.append("Hello") + initialPath.append(true) + @UIBinding var path = initialPath + + let nav = NavigationStackController(path: $path) { + UIViewController() + } + nav.navigationDestination(for: Int.self) { int in + InitIntegerViewController(value: int) + } + try await setUp(controller: nav) + + await assertEventuallyEqual(nav.viewControllers.count, 4, timeout: 2) + await assertEventuallyNoDifference( + nav.values, + [1, "Hello", true] as [AnyHashable] + ) + await assertEventuallyNoDifference( + path.elements, + [.eager(1), .eager("Hello"), .eager(true)] + ) + } + @MainActor func testRegisterNavigationDestinationTypeMultipleTimes_LastOneWins() async throws { @UIBinding var path = UINavigationPath() @@ -625,6 +678,43 @@ private final class BoolViewController: UIViewController, _ValueViewController { } } +private final class InitRootViewController: UIViewController { + override func viewDidLoad() { + super.viewDidLoad() + navigationDestination(for: Int.self) { int in + InitIntegerViewController(value: int) + } + } +} + +private final class InitIntegerViewController: UIViewController, _ValueViewController { + let value: Int + init(value: Int) { + self.value = value + super.init(nibName: nil, bundle: nil) + navigationDestination(for: String.self) { string in + InitStringViewController(value: string) + } + } + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +private final class InitStringViewController: UIViewController, _ValueViewController { + let value: String + init(value: String) { + self.value = value + super.init(nibName: nil, bundle: nil) + navigationDestination(for: Bool.self) { bool in + BoolViewController(value: bool) + } + } + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + private final class UserViewController: UIViewController, _ValueViewController { let value: User init(value: User) { diff --git a/Sources/UIKitNavigation/Navigation/NavigationStackController.swift b/Sources/UIKitNavigation/Navigation/NavigationStackController.swift index 4e2a54100..7207bfd9d 100644 --- a/Sources/UIKitNavigation/Navigation/NavigationStackController.swift +++ b/Sources/UIKitNavigation/Navigation/NavigationStackController.swift @@ -168,7 +168,9 @@ guard let destinationType = navigationID.elementType, let destination = destinations[DestinationType(destinationType)], - let (viewController, element) = destination(navigationID) + let (viewController, element) = withUITransaction(\.stackController, self, { + destination(navigationID) + }) else { return nil } @@ -364,7 +366,10 @@ 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( """ @@ -476,6 +481,17 @@ } } + private extension UITransaction { + var stackController: NavigationStackController? { + get { self[NavigationStackControllerKey.self] } + set { self[NavigationStackControllerKey.self] = newValue } + } + } + + private enum NavigationStackControllerKey: UITransactionKey { + static let defaultValue: NavigationStackController? = nil + } + private func typeName(_ type: Any.Type) -> String { #if CustomDump return String(customDumping: type)