Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I reorder my UINavigationController items programatically?

In my TabBarViewController, I'm creating a navigation and presenting 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 the user_id is already in the stack, move it to the end of the stack so that it's visible to the user. No duplicates. How can I do this?
  • Else, just create a new instance of the view controller and add it to the top of the stack. I think I'm already doing this above.

All the "back" buttons should work as expected after reordering.

like image 901
TIMEX Avatar asked Sep 26 '22 09:09

TIMEX


1 Answers

You can use setViewControllers(_:animated:) to rearrange the view controller stack. You don't have to do anything special to make the back button work correctly. The navigation controller sets up the back button based on the second item in its viewControllers array (if there is a second item), and updates the back button whenever it updates the viewControllers array.

Here's how I'd do this. First, we add a method to UIViewController to ask whether it's the view controller for a specific userId. Since most view controllers are not (and cannot be) the correct view controller, it just returns false:

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

Then we override this method in MessagesViewController to return true when appropriate:

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

Now, to show the view controller for a specific user, we search the navigation controller's stack for an existing view controller. The action we take depends on whether we find it:

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

If we didn't find it, we make a new view controller and push 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 did find it, we move it to the top of the navigation stack with this method:

extension UINavigationController {

    func moveToTopOfNavigationStack(viewControllerAtIndex index: Int) {
        var stack = viewControllers
        if index == stack.count - 1 {
            // nothing to do because it's 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
    }

}
like image 88
rob mayoff Avatar answered Oct 12 '22 12:10

rob mayoff