Pop multiple view controllers from the navigation stack

In a couple of apps I’ve needed (what must be a common requirement) to move back through the navigation controller’s stack of views by more than a single view. All iOS developers will know how to move to the previous screen through navigationController?.popViewController(animated:) and many will also at least be aware of navigationController?.popToViewController(animated:)for moving back more than one view. I’ve always implemented this in a method like:

 func popBackTo(_ vcType: UIViewController.Type) {
      var targetVC : UIViewController?      
      if vcType == MyVC1.self {
         targetVC = navigationController?.viewControllers.first(where: {$0 is MyVC1})
      } else if vcType == MyVC2.self {
         targetVC = navigationController?.viewControllers.first(where: {$0 is MyVC2})
      } else {
         targetVC = navigationController?.viewControllers.first(where: {$0 is MyVC3})
      }
      if let targetVC = targetVC {
         navigationController?.popToViewController(targetVC, animated: true)
      }
   }

This works, made possible due to the navigation controller maintaining a hierarchy (stack) of view controllers, but has never looked elegant. The more view controllers it needs to deal with, the less elegant it gets.

I’ve always known that it should be possible to use the vcType parameter in the where clause directly, rather than having to check against each possible type of view controller individually, but I could never work out the syntax.

A Stack Overflow post I came across recently (https://stackoverflow.com/questions/28116598/swift-pass-type-as-parameter), while researching something completely different, caused the penny to drop. Like all such things it’s blindingly obvious once you see it. I’ve now refactored my application code to a far more elegant solution:

   func popTo<T>(_ vc: T.Type) {
      let targetVC = navigationController?.viewControllers.first{$0 is T}
      if let targetVC = targetVC {
         navigationController?.popToViewController(targetVC, animated: true)
      }
   }

The trick was to define the method parameter as a means of incorporating a generic type, but then ignoring it in favour of the generic type it enabled.

All that is required to jump back through the stack is to call the method passing in the appropriate view controller type: popTo(MyVC1.self).

The one caveat is that if you have used the same view controller type more than once in the navigation stack, and you don’t want the latest instance, then you’ll need to change the .first{$0 is T} to .filter{$0 is T} and then select the appropriate instance from the resulting array. This could be by indexing it directly according to the order it was added to the stack, or by further filtering using a unique property of the view controller such as title:

func popTo<T>(_ vc: T.Type) {
      let targetVC = navigationController?.viewControllers.filter{$0 is T}.filter{$0.title = "Home Screen"}
      if let targetVC = targetVC {
         navigationController?.popToViewController(targetVC, animated: true)
      }
   }

Leave a Reply