3

Is there some way to implement popToViewController(vc) equivalent in SwiftUI? For example if I have the following flow:

View1 -> View2 -> View3 -> View4

How can I pop from View4 directly to View2 considering that we do not have control over the navigation stack ?

Milen Valchev
  • 167
  • 1
  • 7
  • 1
    popTo and popToRoot is not available now. You may refer https://stackoverflow.com/a/57513566/1930006 and find some workarounds – Alfred Woo Nov 12 '19 at 07:11
  • Maybe @Enviroment variable helps. – Mojtaba Hosseini Nov 12 '19 at 20:39
  • Take a look at this question https://stackoverflow.com/q/57700532/1291872 I created an open source navigation stack for SwiftUI (https://github.com/biobeats/swiftui-navigation-stack), you can use it to pop to a specific view. – superpuccio Feb 04 '20 at 12:08

3 Answers3

0

As I'm doing R&D on some functionaities with SwiftUI. I'm facing issue with pop to specific view. After R&D I got something. You can use this for pop to specific view View.

  1. First you've to create a extension for UINavigationController. check this below mentioned code

    import UIKit
    
    extension UINavigationController {
    
        func popToViewController(ofClass: AnyClass, animated: Bool = true) {
            if let vc = viewControllers.filter({$0.isKind(of: ofClass)}).last {
                popToViewController(vc, animated: animated)
            }
        }
    
        func popViewControllers(viewsToPop: Int, animated: Bool = true) {
            if viewControllers.count > viewsToPop {
                let vc = viewControllers[viewControllers.count - viewsToPop - 1]
                popToViewController(vc, animated: animated)
            }
        }
    
        func popBack<T: UIViewController>(toControllerType: T.Type) {
            if var viewControllers: [UIViewController] = self.navigationController?.viewControllers {
                viewControllers = viewControllers.reversed()
                for currentViewController in viewControllers {
                    if currentViewController .isKind(of: toControllerType) {
                        self.navigationController?.popToViewController(currentViewController, animated: true)
                        break
                    }
                }
            }
        }
    }
    
  2. Get UINavigationController instance by using this code.

    let window = UIApplication.shared.connectedScenes
        .filter { $0.activationState == .foregroundActive }
        .map { $0 as? UIWindowScene }
        .compactMap { $0 }
        .first?.windows
        .filter { $0.isKeyWindow }
        .first
    let navigation = window?.rootViewController?.children.first as? UINavigationController
    
  3. Once you get the UINavigationController instance you can easily pop to specific view using this code.

    navigation.popViewControllers(viewsToPop: 2)
    
Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
V.Kumar
  • 139
  • 2
  • 5
0

Make it Simple!!

Add an extension to the UINavigationController

extension UINavigationController {
func popToViewController(ofClass: AnyClass, animated: Bool = true) {
  if let vc = viewControllers.last(where: { $0.isKind(of: ofClass) }) {
    popToViewController(vc, animated: animated)
  }
}

Add below code to go specific SwiftUI View. You have to mention SampleView place to your view.

navigationController.popToViewController(ofClass: HostingController<SampleView>.self)

That's it!

Yano
  • 585
  • 3
  • 18
0
// Do not change, it must be same with NavigationBarTitle
enum ViewIdentifiers: String {
    case homeView = "Home"
    case detailView = "Detail Page"
}    

struct HomeView: View {

 var body: some View {
   VStack {
      NavigationLink {
          DetailView()
      } label: {
         Text("Go To Detail")
     }
   }
   .navigationBarTitle(ViewIdentifiers.homeView.rawValue, displayMode: .inline)
 }
}


struct DetailView: View {

 var body: some View {
   VStack {
       Text("Detail")
         .onTapGesture { UIApplication.shared.popToView(ViewIdentifiers.homeView) }
   }
   .navigationBarTitle("Detail Page", displayMode: .inline)
 }
}

extension UIApplication {

  var keyWindow: UIWindow? {
    return UIApplication.shared.connectedScenes
        .filter { $0.activationState == .foregroundActive }
        .first(where: { $0 is UIWindowScene })
        .flatMap({ $0 as? UIWindowScene })?.windows
        .first(where: \.isKeyWindow)
  }
 

 func getNavigationController() -> UINavigationController? {
    guard let window = keyWindow else { return nil }
    guard let rootViewController = window.rootViewController else { return nil }
    guard let navigationController = findNavigationController(viewController: rootViewController) else { return nil }
    return navigationController
  }
  
  private func findNavigationController(viewController: UIViewController?) -> UINavigationController? {
    guard let viewController = viewController else {
        return nil
    }
    
    if let navigationController = viewController as? UINavigationController {
        return navigationController
    }
    
    for childViewController in viewController.children {
        return findNavigationController(viewController: childViewController)
    }
    
    return nil
 }

 func popToView(_ identifier: ViewIdentifiers) {
    guard let navigationController = getNavigationController() else {
        return
    }
    
    for vc in navigationController.children {
        if vc.navigationItem.title == identifier.rawValue {
            navigationController.popToViewController(vc, animated: true)
            break
        }
    }
  }

}