Skip to content

Commit

Permalink
Track VCs being popped from stack from UIKit
Browse files Browse the repository at this point in the history
Tapping the back button or interactively popping via gesture can
conflict with observation when the view controller being popped to
executes a mutation in its `will`- or `didAppear`. While this isn't
common in vanilla UIKit, in TCA sending even a no-op action in these
lifecycle hooks can lead to immediately re-pushing the view controller
that was just popped.

This commit does what it can to detect when the back button was pushed
or the interactive pop gesture was invoked so that if/when the pop is
committed, we can update the path more eagerly, avoiding the
re-presentation.
  • Loading branch information
stephencelis committed Oct 18, 2024
1 parent 54d8d0d commit 71a99ce
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 1 deletion.
11 changes: 10 additions & 1 deletion Sources/SwiftNavigation/UINavigationPath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public struct UINavigationPath: Equatable {
public var elements: [Element] = []

@_spi(Internals)
public enum Element: Equatable {
public enum Element: Hashable {
case eager(AnyHashable)
case lazy(Lazy)

Expand Down Expand Up @@ -52,6 +52,15 @@ public struct UINavigationPath: Equatable {
return CodableRepresentation.Element(eager) == lazy
}
}

public func hash(into hasher: inout Hasher) {
switch self {
case let .eager(value), let .lazy(.element(value)):
hasher.combine(value)
case let .lazy(.codable(value)):
hasher.combine(value.decode())
}
}
}

/// The number of elements in this path.
Expand Down
39 changes: 39 additions & 0 deletions Sources/UIKitNavigation/Navigation/NavigationStackController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
}
}
}
private var didPop: Set<UINavigationPath.Element> = []
private let pathDelegate = PathDelegate()
private var root: UIViewController?

Expand Down Expand Up @@ -62,6 +63,10 @@

super.delegate = pathDelegate

interactivePopGestureRecognizer?.addTarget(
self, action: #selector(interactivePopGestureRecognizerAction)
)

if #available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) {
traitOverrides.push = UIPushAction { [weak self] value in
self?._push(value: value)
Expand Down Expand Up @@ -154,6 +159,17 @@
}
}

@objc private func interactivePopGestureRecognizerAction(
_ gesture: UIScreenEdgePanGestureRecognizer
) {
guard
gesture.state == .began,
let last = path.last,
!viewControllers.compactMap(\.navigationID).contains(last)
else { return }
didPop.insert(last)
}

fileprivate func viewController(
for navigationID: UINavigationPath.Element
) -> UIViewController? {
Expand Down Expand Up @@ -310,6 +326,29 @@
}
}

extension NavigationStackController: UINavigationBarDelegate {
public func navigationBar(
_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem
) -> Bool {
if let navigationID = viewControllers
.first(where: { $0.navigationItem == item })?
.navigationID
{
didPop.insert(navigationID)
}
return true
}

public func navigationBar(_ navigationBar: UINavigationBar, didPop item: UINavigationItem) {
path.removeAll(where: { didPop.contains($0) })
didPop.removeAll()
}

public func navigationBar(_ navigationBar: UINavigationBar, didPush item: UINavigationItem) {
didPop.removeAll()
}
}

extension UIViewController {
@available(iOS, deprecated: 17, renamed: "traitCollection.push")
@available(macOS, deprecated: 14, renamed: "traitCollection.push")
Expand Down

0 comments on commit 71a99ce

Please # to comment.