57

I have a root view controller which isn’t set as the custom class for any of my view controllers on my storyboard. Instead, all of my view controllers are subclassing this class like so.

// RootViewController
class RootViewController: UIViewController, UITabBarDelegate { 

    // This is not getting executed on any of the view controllers
    func tabBar(tabBar: UITabBar, didSelectItem item: UITabBarItem) {
        print("ddd")
    }
}

// Subclassing it 
class TopStoriesViewController: RootViewController {

}

But I seem to be struggling with doing something when a tabbaritem is pressed on the view controller which is subclassing the rootviewcontroller, i.e, the message is not being printed.

Suragch
  • 364,799
  • 232
  • 1,155
  • 1,198
Tunds
  • 1,607
  • 2
  • 11
  • 27

6 Answers6

92

You don't want your view controller's base class to be a UITabBarDelegate. If you were to do that, all of your view controller subclasses would be tab bar delegates. What I think you want to do is to extend UITabBarController, something like this:

class MyTabBarController: UITabBarController, UITabBarControllerDelegate {

then, in that class, override viewDidLoad and in there set the delegate property to self:

self.delegate = self

Note: This is setting the tab bar controller delegate. The tab bar has it's own delegate (UITabBarDelegate), which the tab bar controller manages, and you are not allow to change.

So, now this class is both a UITabBarDelegate (because UITabBarController implements that protocol), and UITabBarControllerDelegate, and you can override/implement those delegate's methods as desired, such as:

// UITabBarDelegate
override func tabBar(tabBar: UITabBar, didSelectItem item: UITabBarItem) {
    print("Selected item")
}

// UITabBarControllerDelegate
func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {
    print("Selected view controller")
}

I'm guessing you're probably more interested in the latter. Check out the documentation to see what each of these delegates provide.

Last thing, in your storyboard (assuming you are using storyboards), set your tab bar controller's class to MyTabBarController in the Identity Inspector, and you're good to go.

Swift 3/4

// UITabBarDelegate
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
    print("Selected item")
}

// UITabBarControllerDelegate
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
    print("Selected view controller")
}
Blind Ninja
  • 1,020
  • 13
  • 27
mbeaty
  • 1,419
  • 11
  • 8
  • Thanks for the well-rounded answer. If let's say there is a method in my ViewController I want to implement when user hits some tab bar, either I would have to turn my VC into TabBarDelegate, which you mentioned correctly would render all subclasses of my VC tabbardelegates, which is not useful at all. Other way if what you've mentioned. But then I would be only left with using protocols or some other way to send message to my VC from new TabBarController if I want that method in the VC to be implemented, isn't it? – Manganese Dec 12 '17 at 06:47
  • 1
    @Manganese This comes down to an architectural decision based on your requirements. As with most things, there will be multiple ways to implement what you want to do. If you can give more detail on what you're trying to do, I'd be happy to share my thoughts. Do you want every VC to implement the method you are referring to? Protocols may be useful here, but other options are likely available based on your requirements. – mbeaty Dec 14 '17 at 01:39
  • Great, basically I have two tab bars - one is my main VC and another is Settings VC. Settings are such as reset game levels, audio off/ on etc. Main VC is where user is expected to be most of the times, unless s/he wants to change some settings. I want my main VC to know where a certain setting was changed. Hence, I created a class to store type property which changes in change in Settings View attributes. From there, I use the result to configure my main VC, so that as soon as user navigates to main VC, settings would have taken effect. – Manganese Dec 14 '17 at 09:35
  • For now, I made my VC a delegate: `extension ViewController: UITabBarControllerDelegate` and performed a function as soon as user goes to 2nd item (Settings - tag set as 2) in the tab bar. `func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) { if viewController.tabBarItem.tag == 2 { // do something } }` – Manganese Dec 14 '17 at 09:36
  • 1
    Thanks! Why isn't it necessary to call `super.tabBar(tabBar, didSelect: item)` at the beginning? – Stephan Boner Nov 21 '18 at 15:41
  • I have the same question as SSB95, why don't we need to call super here? In fact calling super causes it to crash (but i have no idea why) – psilencer Jul 03 '19 at 19:57
59

Doing it this way caused me an error

Changing the delegate of a tab bar managed by a tab bar controller is not allowed

This is what I did and it worked

  1. In you ViewController you inherit UITabBarControllerDelegate
  2. Set delegate in a viewDidLoad
  3. Add a function

Example:

class MyClass: UIViewController, UITabBarControllerDelegate {

   func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
        let tabBarIndex = tabBarController.selectedIndex
        if tabBarIndex == 0 {
            //do your stuff
        }
   }

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

}
Lance Samaria
  • 11,429
  • 8
  • 67
  • 159
Gulz
  • 1,613
  • 15
  • 15
10

Here is a version of @mbeaty's answer with a little more context. It is adapted from my fuller answer here.

import UIKit

class MyTabBarController: UITabBarController, UITabBarControllerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        // tell our UITabBarController subclass to handle its own delegate methods
        self.delegate = self
    }

    // called whenever a tab button is tapped
    func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {

        if let firstVC = viewController as? FirstViewController {
            firstVC.doSomeAction()
        }

        if viewController is FirstViewController {
            print("First tab")
        } else if viewController is SecondViewController {
            print("Second tab")
        }
    }

    // alternate method if you need the tab bar item
    override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
        // ...
    }
}

Set this as the Custom class of your Tab View Controller in IB.

enter image description here

Alternate solution

  • Just do something in the viewDidLoad method of the tab's view controller. See this answer.
Suragch
  • 364,799
  • 232
  • 1,155
  • 1,198
7

Swift 5

To detect if a specific TarBarItem has been selected I use this on my custom TabBarController:

    class MainTabBarController: UITabBarController, UITabBarControllerDelegate {

        override func viewDidLoad() {
            super.viewDidLoad()

            self.delegate = self

        }


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

        let selectedIndex = tabBarController.viewControllers?.firstIndex(of: viewController)!
        if selectedIndex == 0 {
            print("first tab bar was selected") 
        } else {
            //do whatever
        }
    }

}
Maruta
  • 917
  • 8
  • 19
2

I used this code, because I need to know that the same view controller is selected.

import UIKit

protocol CustomTabBarControllerDelegate {
    func onTabSelected(isTheSame: Bool)
}

class CustomTabBarController: UITabBarController, UITabBarControllerDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()
        self.delegate = self
    }
    
    func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
        (viewController as? CustomTabBarControllerDelegate)?.onTabSelected(isTheSame: selectedViewController == viewController)
        return true
    }
}

First tab view contoller

import UIKit

class Tab1ViewController: UIViewController, CustomTabBarControllerDelegate {
    func onTabSelected(isTheSame: Bool) { 
        print("Tab1ViewController onTabSelected")
        //do something
    }
}

Second tab view contoller

import UIKit

class Tab2ViewController: UIViewController, CustomTabBarControllerDelegate {
    func onTabSelected(isTheSame: Bool) { 
        print("Tab2ViewController onTabSelected")
        //do something
    }
}

Don't forget to set CustomTabBarController as custom class to your TabBarController in your storyboard and to implement CustomTabBarControllerDelegate protocol by all your tab view controllers.

If you use UINavigationController it can not work if UINavigationController is not storyboard entry point. Make sure that you have storyboard structure like below.

The structure of storyboard

Community
  • 1
  • 1
Zhebzhik Babich
  • 901
  • 8
  • 21
  • This code is not working because you did not setup customDelegate – ShadeToD Aug 01 '19 at 07:05
  • There is not delegate connection between your CustomTabBarController and Tab1 or Tab2 , so the method onTabSelected will not be called. – ShadeToD Aug 01 '19 at 07:24
  • TabBarController has access to all view controllers which has coonection with it via storyboard relationship. – Zhebzhik Babich Aug 01 '19 at 07:32
  • This method does not work, there is no delegate that connect CustomTabBarController with one of your ViewControllers. You can check that onTabSelected is never called. – ShadeToD Aug 01 '19 at 07:34
  • I am looking for the solution to fix it, because i also need it in my code. I want to send delegate message to one of my ViewControllers( tableview, collectionview ... ) and trigger some functions. – ShadeToD Aug 01 '19 at 07:35
  • Just use the solutions above then, because I use this code in my project. – Zhebzhik Babich Aug 01 '19 at 07:39
  • Well .. it does not work for me. I got the same code 1:1. I have changed my tabbar class to CustomTabBarController. "shouldSelect" is working fine , but the "onTabSelected" is never called. – ShadeToD Aug 01 '19 at 07:42
  • OK i found my problem , this code works perfectly fine as long as you do not have a UINavigationController. I just need to fix it a little. Sorry for the problem :) – ShadeToD Aug 01 '19 at 07:55
  • Sorry, but I have UINavigationController o_O. Do you mean that UINavigationController has to be storyboard entry point and then must go to UITabBarController? – Zhebzhik Babich Aug 01 '19 at 08:04
  • I have TabBarController as a entry point in storyboard. It has 4 tabs and these are UICollectionViewController or UITableViewController. All my tabs have UINavigationController so the hierarchy looks like this ( TabBarController -> ( UINavigationController -> UITableViewController ) 4 times , because i have 4 tabs – ShadeToD Aug 01 '19 at 08:13
  • Then may be it better to look at this answer for you. https://stackoverflow.com/a/47861294/6055194 – Zhebzhik Babich Aug 01 '19 at 08:17
  • I do not want to use viewDidAppear because i have NavigationContoller. User will be able to tap on TableViewCell and see more details and if he will tap on back button the the method will be always triggered. – ShadeToD Aug 01 '19 at 08:21
  • So the solution for the problem is: if u have NavigationController in every ViewControllers that are inside TabBarController u can get access to that controller by using this if let firstNC = viewController as? UINavigationController { if let firstVC = firstNC.viewControllers.first as? QuestsTableVC { print("inside my table view controller") } print("inside navigation controller") } – ShadeToD Aug 01 '19 at 08:27
1

Swift 5 Easy way Enjoy

//MARK:- it will work in 
class TabBar: UITabBarController, UITabBarControllerDelegate {

}

Code

override func viewDidLoad() {
    super.viewDidLoad()

    self.selectedIndex = 1
    self.title = "Measure"

    self.delegate = self        
}

//MARK:-  UITabBarDelegate
override func tabBar(tabBar: UITabBar, didSelectItem item: UITabBarItem) {
    print("Selected item")
}

//MARK:- UITabBarControllerDelegate
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
    let selectedIndex = tabBarController.viewControllers?.firstIndex(of: viewController)!
    if selectedIndex == 0 {
        self.title = "History"
    }
    else if selectedIndex == 1{
        self.title = "Measure"
    }
    else if selectedIndex == 2 {
        self.title = "Setting"
    } else {
        //do whatever
    }
}
Shakeel Ahmed
  • 3,089
  • 24
  • 24