19
votes

I've been having some trouble with something I thought might be easy. I have a table in my root view controller, when a row is selected I push a new view and from there I go to another tab.

My question is how do I make sure that as soon as the user taps the first tab the navigation controller will pop to root?

14

14 Answers

27
votes

Following delegate is called while each tab is selected on tabbar.

-(void) tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController

Put following code inside this delegate method.

if ([viewController isKindOfClass:[UINavigationController class]]) 
    {
        [(UINavigationController *)viewController popToRootViewControllerAnimated:NO];
    }

its working fine on my app.

11
votes

For Swift lovers:

import UIKit

class YourTabBarControllerHere: UITabBarController,
UITabBarControllerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        self.delegate = self;
    }

    func tabBarController(tabBarController: UITabBarController,
        didSelectViewController viewController: UIViewController) {
            if let vc = viewController as? UINavigationController {
                vc.popViewControllerAnimated(animated: false);
            }
    }
}

Edit: Swift 3 update, thanks to @Justin Oroz for pointing that out.

4
votes

In Swift 3.1

Add UITabBarControllerDelegate to your TabBar Class:

class YourClass: UITabBarController, UITabBarControllerDelegate {

After:

override func tabBar(tabBar: UITabBar, didSelectItem item: UITabBarItem) {

let yourView = self.viewControllers![self.selectedIndex] as! UINavigationController
yourView .popToRootViewControllerAnimated(false) 

}

3
votes

What you are trying to do sounds a little bit odd. Have you read the Human Interface Guidelines on combining UINavigationControllers and UITabBarControllers?

However, what you need to do is detect the selection of the tab by setting a delegate for your UITabBarController and implementing the tabBarController:didSelectViewController: delegate method. In this method you need to pop back to the root view controller using UINavigationController's popToRootViewControllerAnimated: method.

2
votes
[self.navigationController popToRootViewControllerAnimated:NO];
1
votes

Swift 4.2

The solution that works for me is to subclass the UITabBarController and add the two delegate functions as follows:

import UIKit

class MyCustomTabBarController: UITabBarController, UITabBarControllerDelegate {
    var previousSelectedTabIndex:Int = 0

override func viewDidLoad() {
    super.viewDidLoad()
    self.delegate = self
}

func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
    self.previousSelectedTabIndex = tabBarController.selectedIndex
}


override func tabBar(_ tabBar: UITabBar, didSelect item:
    UITabBarItem) {
    let vc = self.viewControllers![previousSelectedTabIndex] as! UINavigationController
    vc.popToRootViewController(animated: false)

}

}

Make sure you set animated to false otherwise you will get

 Unbalanced calls to begin/end appearance transitions for the targeted ViewController
1
votes

Try this.

class TabBarClass: UITabBarController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
        let vc = self.viewControllers![selectedIndex] as! UINavigationController
        vc.popToRootViewController(animated: false)
    }
}
1
votes

Swift 5.1 Answer:

  class YourTabBarName: UITabBarController, UITabBarControllerDelegate
    {

     override func viewDidLoad()
        {
            super.viewDidLoad()
            self.delegate = self
        }


     func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController)
        {
            if let vc = viewController as? UINavigationController
            { vc.popToRootViewController(animated: false) }
        }
    }
0
votes

First, you should create subclass of UITabbarController and add Observer:

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.tabBar addObserver:self forKeyPath:@"selectedItem" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
}

When tabbar is selected, We will process in method:

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    if ([keyPath isEqualToString:@"selectedItem"] && [object isKindOfClass:[UITabBar class]]){
        UITabBar *bar = (UITabBar *)object; // The object will be the bar we're observing.
        // The change dictionary will contain the previous tabBarItem for the "old" key.
        UITabBarItem *wasItem = [change objectForKey:NSKeyValueChangeOldKey];
        NSUInteger was = [bar.items indexOfObject:wasItem];
        // The same is true for the new tabBarItem but it will be under the "new" key.
        UITabBarItem *isItem = [change objectForKey:NSKeyValueChangeNewKey];
        NSUInteger is = [bar.items indexOfObject:isItem];
        if (is == was) {
           UIViewController *vc = self.viewControllers[is];
            if ([vc isKindOfClass:[UINavigationController class]]) {
                [(UINavigationController *)vc popToRootViewControllerAnimated:YES];
            }
        }
    }
}
0
votes

The UTabController suggests a different UX for letting a user "pop to root". When switching back to a tab, it keeps the full UINav stack from before. If they tap the bar item a second time (tapping the selected tab), only then does it pop to root. That's all automatic. Some apps, like instagram, allow a third tap to scroll to top.

I'd suggest sticking with the defaults as that's what users will be expecting.

0
votes

The below had worked for me .This code in swift 3:

1> subclass UITabbarController and implement two below method with one iVAr:
class MyTabBarController: UITabBarController ,UITabBarControllerDelegate { var previousSelectedTabIndex : Int = -1 }

2> set the tabbar delegate in viewdidLoad

override func viewDidLoad() {    
    super.viewDidLoad()
    self.delegate = self    // you must do it}  

func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {

    self.previousSelectedTabIndex = tabBarController.selectedIndex
}     
func tabBarController(_ tabBarController: UITabBarController,
                               shouldSelect viewController: UIViewController) -> Bool {    

    if  self.previousSelectedTabIndex == tabBarController.selectedIndex {
        let   nav  =  viewController as! UINavigationController // mine in nav_VC
        for vc in nav.childViewControllers {
            if vc is YUOR_DESIRED_VIEW_CONTROLLER {
            nav.popToViewController(vc, animated: true)
            return false// IT WONT LET YOU GO TO delegate METHOD
            }
        }
    }
 return true
}    

tabBarController.selectedIndex give you the selected tab

In tabBarController_shouldSelect_viewController method you can set your desired view controller with some easy calculation.
if you are not getting the above code play with both above method and you come to know how both working together

0
votes

Use selected view controller to popToRootViewController. Basically you need to cast this instance.

Swift

((selectedViewController) as! UINavigationController).popToRootViewController(animated: false)
//
// I just added extra line so the scroll bar won't annoy you.
0
votes

Xcode 11.5, Swift 5:

You don't need to use two delegate methods. Only one is enough:

extension CustomTabBarController: UITabBarControllerDelegate {
    func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
        if tabBarController.viewControllers?.firstIndex(of: viewController) == tabBarController.selectedIndex,
            let navigationController = viewController as? UINavigationController {
            navigationController.popToRootViewController(animated: true)
        }
    
        return true
    }
}
0
votes
//create a tabbar controller class set it to your TabbarController in storyboard
import UIKit

class MyTabbarViewController: UITabBarController,UITabBarControllerDelegate{

    override func viewDidLoad() {
        super.viewDidLoad()
        self.delegate = self
    }
    
 
    func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController)
    {
        if let vc = viewController as? UINavigationController
        { vc.popToRootViewController(animated: false) }
    }

  

}