diff --git a/Source/Turbo/Navigator/Helpers/PathIdentifiable.swift b/Source/Turbo/Navigator/Helpers/PathIdentifiable.swift new file mode 100644 index 00000000..dc59f0de --- /dev/null +++ b/Source/Turbo/Navigator/Helpers/PathIdentifiable.swift @@ -0,0 +1,15 @@ +import UIKit + +/// A view controller may conform to `PathIdentifiable` to expose the URL it represents. +/// +/// `Navigator` uses this URL — instead of view-controller type equality — when deciding +/// whether a proposed visit refers to the page already on top of the stack or to the page +/// directly beneath it. Without this conformance, two instances of the same custom view +/// controller class but with different URLs are indistinguishable to the navigator and may +/// be incorrectly popped or replaced. +/// +/// `Visitable` inherits from this protocol, so existing visitable view controllers satisfy +/// it automatically via `initialVisitableURL`. +public protocol PathIdentifiable: AnyObject { + var initialVisitableURL: URL { get } +} diff --git a/Source/Turbo/Navigator/NavigationHierarchyController.swift b/Source/Turbo/Navigator/NavigationHierarchyController.swift index b69b50f6..6127fd7e 100644 --- a/Source/Turbo/Navigator/NavigationHierarchyController.swift +++ b/Source/Turbo/Navigator/NavigationHierarchyController.swift @@ -148,8 +148,8 @@ class NavigationHierarchyController { private func visitingSamePage(on navigationController: UINavigationController, with controller: UIViewController, via proposal: VisitProposal) -> Bool { - if let visitable = navigationController.topViewController as? Visitable { - return visitable.initialVisitableURL.isSameLocation(as: proposal.url, pathProperties: proposal.properties) + if let pathIdentifiable = navigationController.topViewController as? PathIdentifiable { + return pathIdentifiable.initialVisitableURL.isSameLocation(as: proposal.url, pathProperties: proposal.properties) } else if let topViewController = navigationController.topViewController { return topViewController.isMember(of: type(of: controller)) } @@ -162,8 +162,8 @@ class NavigationHierarchyController { guard navigationController.viewControllers.count >= 2 else { return false } let previousController = navigationController.viewControllers[navigationController.viewControllers.count - 2] - if let previousVisitable = previousController as? VisitableViewController { - return previousVisitable.initialVisitableURL.isSameLocation(as: proposal.url, pathProperties: proposal.properties) + if let pathIdentifiable = previousController as? PathIdentifiable { + return pathIdentifiable.initialVisitableURL.isSameLocation(as: proposal.url, pathProperties: proposal.properties) } return type(of: previousController) == type(of: controller) } diff --git a/Source/Turbo/Visitable/Visitable.swift b/Source/Turbo/Visitable/Visitable.swift index db0c0403..41e9fe50 100644 --- a/Source/Turbo/Visitable/Visitable.swift +++ b/Source/Turbo/Visitable/Visitable.swift @@ -10,11 +10,10 @@ public protocol VisitableDelegate: AnyObject { func visitableDidRequestRefresh(_ visitable: Visitable) } -public protocol Visitable: AnyObject { +public protocol Visitable: AnyObject, PathIdentifiable { var visitableViewController: UIViewController { get } var visitableDelegate: VisitableDelegate? { get set } var visitableView: VisitableView { get } - var initialVisitableURL: URL { get } var currentVisitableURL: URL { get } func visitableDidRender()