Issues upgrading ios13.1 to 13.2 - NavigationLink - tried to pop to a view controller that doesn't exist

260 Views Asked by At

I built an app using both UIKit and swiftUI.

It worked fine with ios13.1 but with ios 13.2 I have bugs:

I'm showing a SwiftUI View in a UIViewController (using HostingController). This view is composed with elements wrapped in NavigationLink. When clicking on this element, the next view doesn't show, although the navbar is ok, and when clicking back, the app crashes with:

<Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Tried to pop to a view controller that doesn't exist.'>

Is it a bug related to ios 13.2 ? Any clue how to fix it ?

Here is my code:

UIController :

< class MyController: UIViewController {

var delegate: MenuItemsDelegate?

let vc = UIHostingController(rootView: MyView_UI())

override func viewDidLoad() {
    super.viewDidLoad()

    if #available(iOS 13.0, *) {
        view.backgroundColor = BACKGROUND_COLOR_D
    } else {
        view.backgroundColor = PALE_GREY
    }

    setupViews()
    setNavigationBar()
}

 func setupViews() {
    addChild(vc)
    view.addSubview(vc.view)
    vc.didMove(toParent: self)
    setupConstraints()
 }


 func setupConstraints() {
    vc.view.translatesAutoresizingMaskIntoConstraints = false

     [
        vc.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
          vc.view.centerXAnchor.constraint(equalTo: view.centerXAnchor),
          vc.view.widthAnchor.constraint(equalTo: view.widthAnchor),
        vc.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),

         ].forEach {$0.isActive = true }

 }


func setNavigationBar() {
    title = ""
    navigationItem.leftBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "slidingNotif").withRenderingMode(.alwaysOriginal),  style: .plain, target: self, action: #selector(handleMenuToggle))
    setNavigationRightBarButtons()
}

@objc func handleMenuToggle() {
    delegate?.handleMenuToggle(forMenuOption: nil)
}

} >

MyView_UI: < var body: some View {

    ScrollView{
        VStack(spacing: 15) {
            HStack(alignment: .center, spacing: 20) {
                NavigationLink(destination: SecondView_UI(some param )){
                    ThirdView_UI(some param), height: 150)
                }
                NavigationLink(destination: SecondView_UI(some param)){
                    ThirdView_UI(some, height: 150)
                }
            }
                .buttonStyle(PlainButtonStyle())
       //autres HStack(...)

   }
 }

}

>

MyView_UI and SecondView_UI are showing fine, but app crashes when going back

1

There are 1 best solutions below

0
On

The issue is a bug in the bridge between UIKit and SwiftUI. It is somehow related to this, but here the cause is that the UIHostingController is embedded, as a child, within another UIViewController. Unfortunately, this also occur if you use some standard container view controller, like UIPageViewController.

Here are some logs, right before the crash:

// we have ViewController0 in the navigation stack
// here we push ViewController1, that embeds UIHostingController, in our case - EmbeddedHostingController
pushViewController(_:animated:)
viewController: ViewController1
viewControllers: [ViewController0]
viewControllers: [ViewController0, ViewController1]

// here we tap a NavigationLink
// we can see that the pushed view controller is DestinationHostingController, coming from the framework
pushViewController(_:animated:)
viewController: DestinationHostingController<AnyView>
viewControllers: [ViewController0, ViewController1]
viewControllers: [ViewController0, ViewController1, DestinationHostingController<AnyView>]

// here we tap back in the navigation bar
// the DestinationHostingController is being popped, as expected
popViewController(animated:)
viewControllers:[ViewController0, ViewController1, DestinationHostingController<AnyView>]
super.result: DestinationHostingController<AnyView>
viewControllers: [ViewController0, ViewController1]

// this is the wrong part - the framework is triying to pop our embedded hosting controller, but because it is not part of the navigation stack - we get a crash
popToViewController(_:animated:)
viewController: EmbeddedHostingController
viewControllers: [ViewController0, ViewController1]

A valid workaround for this would be to use custom UINavigationController subclass, override popToViewController and return nil when the viewController is of UIHostingController type.

There is another weird crash, that can sometimes occur - the navigation controller is trying to pop itself.

Here is an example solution:

private protocol HostingController {}
extension UIHostingController: HostingController {}

class RootNavigationController: UINavigationController {
  override func popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]? {
    if #unavailable(iOS 13.3) {
      if #available(iOS 13.2, *) {
        if viewController is HostingController, viewController.parent !== self {
          return nil
        }

        if viewController === self {
          return nil
        }
      }
    }

    return super.popToViewController(viewController, animated: animated)
  }
}