ios – NavigationStack with path doesn’t work properly inside UIHostingController


When UIHostingController‘s rootView is wrapped in a NavigationStack, navigation by means of NavigationStack‘s path doesn’t work: Even though a value is appended to the NavigationPath, the corresponding view is not pushed.

Curiously enough, if NavigationStack is initialized with a non-empty path, it shows the stack correctly:

Incorrect behavior of NavigationStack inside UIHostingController

(On the animation, the screen with the “Show first” button is a UIViewController, which modally presents a SwiftUI view wrapped in a UIHostingController (the view with the button “Open next”.) “Open next” modifies path of the view, trying to push another SwiftUI view, with the “Next” text label.

This is a minimal reproducible example, stripped of all irrelevant code:

class HomeViewController: UIViewController {

  class NavigationModel: ObservableObject {

    enum Path {
      case second
    }

    @Published var navigationPath = NavigationPath()

  }

  @ObservedObject private var navigationModel = NavigationModel()

  override func viewDidLoad() {
    super.viewDidLoad()

    view.backgroundColor = .white

    let button = UIButton(type: .system)
    button.translatesAutoresizingMaskIntoConstraints = false
    button.setTitle("Show first", for: .normal)
    button.addTarget(self, action: #selector(presentSecondViewController), for: .touchUpInside)

    view.addSubview(button)
    NSLayoutConstraint.activate([button.centerXAnchor.constraint(equalTo: view.centerXAnchor), button.centerYAnchor.constraint(equalTo: view.centerYAnchor)])
  }

  @objc func presentSecondViewController() {
    let view = NavigationStack(path: $navigationModel.navigationPath) {
      Button("Open next") { self.navigationModel.navigationPath.append(NavigationModel.Path.second) }
        .navigationDestination(for: NavigationModel.Path.self) {
          switch $0 {
            case .second:
              SecondView()
          }
        }
    }

    let vc = UIHostingController(rootView: view)
    present(vc, animated: true)
  }

}

struct SecondView: View {
  var body: some View {
    Text("Next")
  }
}

However, the same approach works in the pure SwiftUI: If the root view modally presents another view wrapped in a NavigationStack, all modifications of the corresponding path result in the expected behavior:

Correct behavior of NavigationStack in pure SwiftUI

Here’s the code of the working example:

@main struct SwiftUIPlaygroundApp: App {

  class NavigationModel: ObservableObject {

    enum Path {
      case second
    }

    @Published var navigationPath = NavigationPath()

  }

  var body: some Scene {
    WindowGroup {
      Button("Show first") { firstShown = true }
        .sheet(isPresented: $firstShown) {
          NavigationStack(path: $navigationModel.navigationPath) {
            Button("Open next") { navigationModel.navigationPath.append(NavigationModel.Path.second) }
              .navigationDestination(for: NavigationModel.Path.self) {
                switch $0 {
                  case .second:
                    SecondView()
                }
              }
          }
        }
    }
  }
  @State var firstShown = false
  @ObservedObject var navigationModel = NavigationModel()

}

struct SecondView: View {
  var body: some View {
    Text("Next")
  }
}

How could I make it work properly inside UIHostingController?

Any guidance is appreciated.

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img