Description
When using navigationDestination(item:content:), navigating back multiple screens with UINavigationController.popToRootViewController(animated:) does not correctly reset the navigation state.
Specifically, UIKitNavigation_viewDidDisappear is not called for intermediate screens, which causes their navigation state to remain active.
As a result, it becomes impossible to navigate to those screens again.
Proposed Solution
Instead of relying on:
UIKitNavigation_viewDidDisappear
isMovingFromParentViewController
I propose using:
This method is reliably called when a view controller is removed from its parent, including cases where multiple view controllers are popped at once.
Reproduction
- A minimal reproduction project.
https://github.com/sk409/SwiftUINavigationSample
- The main code of the project
import SwiftUI
import UIKitNavigation
struct ContentView: UIViewControllerRepresentable {
@State var a = A()
func makeUIViewController(context: Context) -> some UIViewController {
UINavigationController(rootViewController: ViewControllerA(a: a))
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
}
@Observable
class A {
var b: B?
}
class ViewControllerA: UIViewController {
@UIBindable var a: A
init(a: A) {
self.a = a
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton()
button.setTitle("To B", for: .normal)
button.setTitleColor(.black, for: .normal)
button.frame = view.frame
button.addAction(
.init { [weak self] _ in
guard let self else { return }
a.b = .init()
},
for: .touchUpInside
)
view.addSubview(button)
navigationDestination(item: $a.b) {
ViewControllerB(b: $0)
}
}
}
@Observable
class B {
var c: C?
}
class ViewControllerB: UIViewController {
@UIBindable var b: B
init(b: B) {
self.b = b
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton()
button.setTitle("To C", for: .normal)
button.setTitleColor(.black, for: .normal)
button.frame = view.frame
button.addAction(
.init { [weak self] _ in
guard let self else { return }
b.c = .init()
},
for: .touchUpInside
)
view.addSubview(button)
navigationDestination(item: $b.c) {
ViewControllerC(c: $0)
}
}
}
@Observable
class C {}
class ViewControllerC: UIViewController {
let c: C
init(c: C) {
self.c = c
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton()
button.setTitle("Back to A", for: .normal)
button.setTitleColor(.black, for: .normal)
button.frame = view.frame
button.addAction(
.init { [weak self] _ in
guard let self else {
return
}
navigationController?.popToRootViewController(animated: true)
},
for: .touchUpInside
)
view.addSubview(button)
}
}
#Preview {
ContentView()
}
Please let me know if you need any additional details or adjustments to the reproduction setup.
Checklist
Expected behavior
_UIKitNavigation_onDismiss should be called for all popped view controllers, including intermediate ones, so that their navigation state is properly reset.
Actual behavior
Only the last visible screen receives _UIKitNavigation_onDismiss.
Intermediate screens do not, leaving their navigation state uncleared.
Steps to reproduce
- Navigate from A → B → C
- Call
popToRootViewController(animated:) to return from C to A
_UIKitNavigation_onDismiss is not called on B
Because A’s state is not reset, navigating to B again does not work as expected.
SwiftUI Navigation version information
2.6.0
Destination operating system
iOS 16, iOS 17, iOS 18
Xcode version information
Xcode 16.2
Swift Compiler version information
swift-driver version: 1.115.1 Apple Swift version 6.0.3 (swiftlang-6.0.3.1.10 clang-1600.0.30.1)
Target: arm64-apple-macosx15.0
Description
When using
navigationDestination(item:content:), navigating back multiple screens withUINavigationController.popToRootViewController(animated:)does not correctly reset the navigation state.Specifically,
UIKitNavigation_viewDidDisappearis not called for intermediate screens, which causes their navigation state to remain active.As a result, it becomes impossible to navigate to those screens again.
Proposed Solution
Instead of relying on:
UIKitNavigation_viewDidDisappearisMovingFromParentViewControllerI propose using:
didMove(toParent:)This method is reliably called when a view controller is removed from its parent, including cases where multiple view controllers are popped at once.
Reproduction
https://github.com/sk409/SwiftUINavigationSample
Please let me know if you need any additional details or adjustments to the reproduction setup.
Checklist
mainbranch of this package.Expected behavior
_UIKitNavigation_onDismissshould be called for all popped view controllers, including intermediate ones, so that their navigation state is properly reset.Actual behavior
Only the last visible screen receives
_UIKitNavigation_onDismiss.Intermediate screens do not, leaving their navigation state uncleared.
Steps to reproduce
popToRootViewController(animated:)to return from C to A_UIKitNavigation_onDismissis not called on BBecause A’s state is not reset, navigating to B again does not work as expected.
SwiftUI Navigation version information
2.6.0
Destination operating system
iOS 16, iOS 17, iOS 18
Xcode version information
Xcode 16.2
Swift Compiler version information