Skip to content
Draft
16 changes: 8 additions & 8 deletions Examples/CaseStudies/UIKit/ErasedNavigationStackController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
Expand Down Expand Up @@ -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")
Expand All @@ -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
Expand Down
90 changes: 90 additions & 0 deletions Examples/CaseStudiesTests/NavigationPathTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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) {
Expand Down
20 changes: 18 additions & 2 deletions Sources/UIKitNavigation/Navigation/NavigationStackController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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(
"""
Expand Down Expand Up @@ -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)
Expand Down