How can I change the order of the UINavigationController elements programmatically?

In my TabBarViewController, I create a navigation and present it modally.

func viewDidLoad(){ super.viewDidLoad(); //Create a present this view controller in viewDidLoad self.navController = UINavigationController() self.presentViewController(self.navController, animated: true, completion: nil) } //This method gets called when TabBarVC gets a NSNotification func showMessageForUser(user_id: Int){ let mvc = self.storyboard?.instantiateViewControllerWithIdentifier("MessagesViewController") as! MessagesViewController mvc.user_id = user_id //set this user //display this to the user. self.navController.pushViewController(mvc, animated: true) } 

This is pretty straight forward. However, I would like to do this:

  • If user_id is already on the stack, move it to the end of the stack so that it is visible to the user. No duplicates. How can i do this?
  • Just create a new instance of the view controller and add it to the top of the stack. I think I already do this above.

All back buttons should work as expected after reordering.

+5
source share
3 answers

You can use setViewControllers(_:animated:) to redistribute the view controller stack. You do not need to do anything special for the back button to work correctly. The navigation controller sets the back button based on the second element in the viewControllers array (if there is a second element) and updates the back button whenever it updates the viewControllers array.

This is how I do it. First, add a UIViewController method to ask if it is a view controller for a specific userId . Since most view controllers are not (and cannot be) the correct view controller, it simply returns false :

 extension UIViewController { func isViewControllerForUserId(userId: Int) -> Bool { return false } } 

Then we will override this method in MessagesViewController to return true , if necessary:

 extension MessagesViewController { override func isViewControllerForUserId(userId: Int) -> Bool { return self.userId == userId } } 

Now, to show the view controller for a specific user, we are looking for the navigation controller stack for an existing view controller. The action we take depends on whether we find this:

 func showMessageForUserId(userId: Int) { if let index = navController.viewControllers.indexOf({ $0.isViewControllerForUserId(userId) }) { navController.moveToTopOfNavigationStack(viewControllerAtIndex: index) } else { pushNewViewControllerForUserId(userId) } } 

If we did not find it, we will create a new view controller and click it:

  private func pushNewViewControllerForUserId(userId: Int) { let vc = self.storyboard?.instantiateViewControllerWithIdentifier("MessagesViewController") as! MessagesViewController vc.userId = userId self.navController.pushViewController(vc, animated: true) } 

If we find it, we will move it to the top of the navigation stack using this method:

 extension UINavigationController { func moveToTopOfNavigationStack(viewControllerAtIndex index: Int) { var stack = viewControllers if index == stack.count - 1 { // nothing to do because it already on top return } let vc = stack.removeAtIndex(index) if (reorderingIsBuggy) { setViewControllers(stack, animated: false) } stack.append(vc) setViewControllers(stack, animated: true) } private var reorderingIsBuggy: Bool { // As of iOS 9.3 beta 3, `UINavigationController` drops the prior top-of-stack // when you use `setViewControllers(_:animated:)` to move a lower item to the // top with animation. The workaround is to remove the lower item from the stack // without animation, then add it to the top of the stack with animation. This // makes it display a push animation instead of a pop animation and avoids // dropping the prior top-of-stack. return true } } 
+4
source

Here's the showMessageForUser (_ :) method, replace yours with this, it works like a charm.

 func showMessageForUser(user_id: Int){ var mvcItem: MessagesViewController? var controllers: [UIViewController] = (self.navController?.viewControllers)! var matchindex: Int? = -1 for (index, viewItem) in controllers.enumerate() { if (viewItem is MessagesViewController) { if viewItem.user_id == user_id { matchindex = index break } } } if matchindex != -1 { //jus making sure a matching index was found mvcItem = (controllers.removeAtIndex(matchindex!)) as? MessagesViewController } if mvcItem != nil { self.navController?.viewControllers = controllers self.navController?.pushViewController(mvcItem!, animated: true) } else { let mvc = self.storyboard?.instantiateViewControllerWithIdentifier("MessagesViewController") as! MessagesViewController mvc.user_id = user_id //set this user //display this to the user. self.navController.pushViewController(mvc, animated: true) } } 
0
source

Just to make the code easier, I will create my own UINavigationController.

Something like that:

 final class MessagesNavigationController: UINavigationController { // Option 1: If you want to dismiss everything that was in the midle, you can just do this: func showMessageForUser(user_id: Int){ guard let messagesViewController = findMessagesViewController(withUserId: user_id) else { let mvc = self.storyboard?.instantiateViewControllerWithIdentifier("MessagesViewController") as! MessagesViewController mvc.user_id = user_id pushViewController(mvc, animated: true) return } popToViewController(messagesViewController, animated: false) } private func findMessagesViewController(withUserId userId: Int) -> MessagesViewController? { for viewController in viewControllers { guard let messagesViewController = viewController as? MessagesViewController where messagesViewController.user_id == userId else { continue } return messagesViewController } return nil } // Option 2: If you want to move the viewcontroller from the position it currently is, to the end // but keeping everything in the middle, you can do this: func showMessageForUser2(user_id: Int){ guard let index = findMessagesViewControllerIndex(withUserId: user_id) else { let mvc = self.storyboard?.instantiateViewControllerWithIdentifier("MessagesViewController") as! MessagesViewController mvc.user_id = user_id pushViewController(mvc, animated: true) return } // This is the main change let viewController = viewControllers.removeAtIndex(index) pushViewController(viewController, animated: false) } private func findMessagesViewControllerIndex(withUserId userId: Int) -> Int? { for (index, viewController) in viewControllers.enumerate() { guard let messagesViewController = viewController as? MessagesViewController where messagesViewController.user_id == userId else { continue } return index } return nil } } 

So your TabBarController will look something like this:

 var navController: MessagesNavigationController! override func viewDidLoad(){ super.viewDidLoad(); navController = MessagesNavigationController() presentViewController(self.navController, animated: true, completion: nil) } func showMessageForUser(user_id: Int){ navController.showMessageForUser(user_id) } 
0
source

Source: https://habr.com/ru/post/1243017/


All Articles