4:51 pm | by olifarago

This is the much overdue followup to Part 1 which can be found here:

http://www.rumexit.co.uk/2010/07/how-to-customise-the-tab-bar-uitabbar-in-an-iphone-application-part-1-of-2/

We had subclassed UITabBarController and hidden the existing buttons.  Now all we need to do is replace then and create the functionality.

To download a demo project demoing the below click here: https://github.com/rumex/RXCustomTabBar

3. Add My Own Items

Now we are going to create our new buttons. You need to create your four buttons in both a selected and unselected state and add them to your project. Assuming a portrait orientation you are looking at 320px wide in total and 50px high. I am going to assume that our new buttons are 80px X 50px each.

So, back to XCode.

Let’s create a new method called addCustomElements.

First we need to declare our four new UIButtons:

UIButton *btn1;
UIButton *btn2;
UIButton *btn3;
UIButton *btn4;

And declare our new method:

-(void) addCustomElements;

So now our CustomTabBar.h looks like this:

@interface CustomTabBar : UITabBarController {
	UIButton *btn1;
	UIButton *btn2;
	UIButton *btn3;
	UIButton *btn4;
}

-(void) hideTabBar;
-(void) addCustomElements;
@end

In our CustomTabBar.m we are going to create our addCustomElements method.

-(void)addCustomElements
{
	// Initialise our two images
	UIImage *btnImage = [UIImage imageNamed:@"NavBar_01.png"];
	UIImage *btnImageSelected = [UIImage imageNamed:@"NavBar_01_s.png"];

	btn1 = [UIButton buttonWithType:UIButtonTypeCustom]; //Setup the button
        btn1.frame = CGRectMake(0, 430, 80, 50); // Set the frame (size and position) of the button)
	[btn1 setBackgroundImage:btnImage forState:UIControlStateNormal]; // Set the image for the normal state of the button
	[btn1 setBackgroundImage:btnImageSelected forState:UIControlStateSelected]; // Set the image for the selected state of the button
	[btn1 setTag:0]; // Assign the button a "tag" so when our "click" event is called we know which button was pressed.
	[btn1 setSelected:true]; // Set this button as selected (we will select the others to false as we only want Tab 1 to be selected initially

        // Now we repeat the process for the other buttons
	btnImage = [UIImage imageNamed:@"NavBar_02.png"];
	btnImageSelected = [UIImage imageNamed:@"NavBar_02_s.png"];
	btn2 = [UIButton buttonWithType:UIButtonTypeCustom];
        btn2.frame = CGRectMake(80, 430, 160, 50);
	[btn2 setBackgroundImage:btnImage forState:UIControlStateNormal];
	[btn2 setBackgroundImage:btnImageSelected forState:UIControlStateSelected];
	[btn2 setTag:1];

	btnImage = [UIImage imageNamed:@"NavBar_03.png"];
	btnImageSelected = [UIImage imageNamed:@"NavBar_03_s.png"];
	btn3 = [UIButton buttonWithType:UIButtonTypeCustom];
        btn3.frame = CGRectMake(160, 430, 80, 50);
	[btn3 setBackgroundImage:btnImage forState:UIControlStateNormal];
	[btn3 setBackgroundImage:btnImageSelected forState:UIControlStateSelected];
	[btn3 setTag:2];

	btnImage = [UIImage imageNamed:@"NavBar_04.png"];
	btnImageSelected = [UIImage imageNamed:@"NavBar_04_s.png"];
	btn4 = [UIButton buttonWithType:UIButtonTypeCustom];
        btn4.frame = CGRectMake(240, 430, 80, 50);
	[btn4 setBackgroundImage:btnImage forState:UIControlStateNormal];
	[btn4 setBackgroundImage:btnImageSelected forState:UIControlStateSelected];
	[btn4 setTag:3];

        // Add my new buttons to the view
	[self.view addSubview:btn1];
	[self.view addSubview:btn2];
	[self.view addSubview:btn3];
	[self.view addSubview:btn4];

        // Setup event handlers so that the buttonClicked method will respond to the touch up inside event.
	[btn1 addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
	[btn2 addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
	[btn3 addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
	[btn4 addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
}

Now we need to call this method in viewDidAppear. Our viewDidAppear should now look like this:

- (void)viewDidAppear:(BOOL)animated {
    [super viewWillAppear:animated];

	[self hideTabBar];
	[self addCustomElements];
}

We are now done. If we run up our application we will see our new custom buttons at the bottom of the screen. Unfortunately they still don’t do anything so lets now build in the functionality to allow the buttons the change tab.

4. Making it all Work

Let’s make a new method called selectTab that take an integer parameter called tabID to tell it which tab was selected.

In our CustomTabBar.h let’s declare it.

.....
-(void) hideTabBar;
-(void) addCustomElements;
-(void) selectTab:(int)tabID;
......

Now in our CustomTabBar.m we need to add it. We are going to do three things.

  • We are going to use a switch command to see which btnID was passed.
  • We are going to set the selected state of each button according to which btnID was passed.
  • Finally we will change the selectedIndex parameter or out TabBarController which will actually change our tab.

So the code will look like this:

	- (void)selectTab:(int)tabID
	{
		switch(tabID)
		{
			case 0:
				[btn1 setSelected:true];
				[btn2 setSelected:false];
				[btn3 setSelected:false];
				[btn4 setSelected:false];
				break;
			case 1:
				[btn1 setSelected:false];
				[btn2 setSelected:true];
				[btn3 setSelected:false];
				[btn4 setSelected:false];
				break;
			case 2:
				[btn1 setSelected:false];
				[btn2 setSelected:false];
				[btn3 setSelected:true];
				[btn4 setSelected:false];
				break;
			case 3:
				[btn1 setSelected:false];
				[btn2 setSelected:false];
				[btn3 setSelected:false];
				[btn4 setSelected:true];
				break;
		}	

		self.selectedIndex = tabID;

	}

So now we have a method that will change the state of the buttons and the selected page depending on which btnID is passed to it.

Finally we need to create the buttonClicked method that we set to be called on the TouchUpInside event of our custom buttons.

All it will do is get the tag number of the button and then call our selectTab method passing the tag number as a parameter.

- (void)buttonClicked:(id)sender
{
	int tagNum = [sender tag];
	[self selectTab:tagNum];
}

And that should be all there is to it. If you run this up you will now have a working custom tab bar.

NOTE:
The only functionality this doesn’t reproduce is if you press the built in tab bar button of the currently selected tab it will pop any navigation controller back to the root (if there is one on the page). To replicate this on our fake tab bar we could replace the line there we set the selectedIndex of the tab controller with this code:

		if (self.selectedIndex == tabID) {
			UINavigationController *navController = (UINavigationController *)[self selectedViewController];
			[navController popToRootViewControllerAnimated:YES];
		} else {
			self.selectedIndex = tabID;
		}

This checks to see if the tab of the button you are pressing is currently selected. If it is then it will pop the navController on that page back to root, alternatively it will just select the tab. But be careful, this is not a complete solution. This assumes that all of your tab has a navigation controller. If you put this on a page that didn’t and then you press the tab bar button twice it would crash out so if some of your pages have a controller and some don’t that you either want to code in a test to see if one is present, or you could simply hard code additional conditions to the if clause which checks which tab is active and only allow the navcontroller code to run for tabs you know have them.

Here you can download a project based on this method: https://github.com/rumex/RXCustomTabBar

UPDATE:

Many people have asked how to hide the new custom UITabBar, see the follow up tutorial for more information: http://www.rumex.it/2011/07/hiding-your-new-custom-tab-bar-uitabbar-in-an-iphone-application/

46 Responses to “How To Customise the Tab Bar (UITabBar) in an iPhone Application (Part 2 of 2)”

  1. Dan says:

    Hi,
    Great tutorial!
    Though I was wandering, how would you hide the tabbar for certain views?

    For instance, before I would set this;
    controller.hidesBottomBarWhenPushed = YES;
    before I push in the controller I want the tab bar hidden on.

    How ever this no longer works. A workaround or help with this would be great!
    Thanks

  2. olifarago says:

    Dan,

    Are you trying to hide a built in tab bar for certain views or a custom tab bar?

    Oli

  3. Dan says:

    Hi Oli, thanks for getting back to me quickly.

    Basically,

    I’m using your code for the custom tab bar.
    The first view that loads is my main menu, this view should NOT have the tab bar at the bottom, but basically has menu buttons. When the user presses one of the menu buttons, it then goes to the relevant view and then shows the custom tab bar at the bottom.
    And it should also, whenever the user goes back to the “Home/Main Menu” hide the tab bar.

    Hope that makes sense? I’m sure there is a better/more elegant way of doing this – than how I currently have.

    Kind Regards,
    Dan

    • olifarago says:

      Dan,

      I am afraid I am off on holiday today until after the new year but what I would do is in the CustomTabBar class create a method that you can call to show and hide the custom buttons.

      Then in ViewWillAppear on the view controllers in question you can set the tabbar to visible or hidden.

      If you like, in the new year I can upgrade my example to demonstrate that but that is the approach I would take.

      Oli

      • Aline says:

        Hey,
        First: Thanks a million times for your tutorial! I just found it after i tried to accomplish that for 3 days or so..

        I think Dan wanted to do the exact same thing as I try to do now. I have a view where I would need the Tabbar to be hidden, which I did with

        self.hidesBottomBarWhenPushed = YES;

        inside the initWithNibName when that view was called. Of course it now won’t do, what it did before. I also had the idea of creating a method inside customTabBar.h and call it where the line above was called before. I imported CustomTabBar.h into the view controller where I need it, and implemented the method inside CustomTabBar.m as follows

        - (void) hideAlsoCustomTabBar {
        btn1.hidden = YES;
        btn1.enabled = NO;
        }

        just as a dummy to see if it’s doing anything at all. The method get’s called, but the btn1 won’t be hidden nor disabled. Could you tell me what I’m doing wrong?
        Thank you very much!

  4. Thanks a lot. One thing I hated was that when a tab was selected, if selected again it would show a highlight. To stop this (and act more like the real tab bar) I prevented user interaction for the selected tab so it prevents it from being selected while “selected”.

    I added the following line below the line that sets the first tab as the selected tab:
    btn1.userInteractionEnabled = NO; //Prevent selection of selected tab

    and revised the select tab method to the following:

    - (void)selectTab:(int)tabID
    {
    switch(tabID)
    {
    case 0:
    [btn1 setSelected:true];
    [btn2 setSelected:false];
    [btn3 setSelected:false];
    [btn4 setSelected:false];

    btn1.userInteractionEnabled = NO;
    btn2.userInteractionEnabled = YES;
    btn3.userInteractionEnabled = YES;
    btn4.userInteractionEnabled = YES;

    break;
    case 1:
    [btn1 setSelected:false];
    [btn2 setSelected:true];
    [btn3 setSelected:false];
    [btn4 setSelected:false];

    btn1.userInteractionEnabled = YES;
    btn2.userInteractionEnabled = NO;
    btn3.userInteractionEnabled = YES;
    btn4.userInteractionEnabled = YES;

    break;
    case 2:
    [btn1 setSelected:false];
    [btn2 setSelected:false];
    [btn3 setSelected:true];
    [btn4 setSelected:false];

    btn1.userInteractionEnabled = YES;
    btn2.userInteractionEnabled = YES;
    btn3.userInteractionEnabled = NO;
    btn4.userInteractionEnabled = YES;

    break;
    case 3:
    [btn1 setSelected:false];
    [btn2 setSelected:false];
    [btn3 setSelected:false];
    [btn4 setSelected:true];

    btn1.userInteractionEnabled = YES;
    btn2.userInteractionEnabled = YES;
    btn3.userInteractionEnabled = YES;
    btn4.userInteractionEnabled = NO;

    break;
    }

    self.selectedIndex = tabID;

    }

    Hope you guys find it useful and thanks again Oli for an awesome tutorial!

    • olifarago says:

      Victor, this is a very good point.

      Good solution. The only problem with disabling the user interaction is if someone was using the add-on functionality of selecting a selected tab pop the root view controller this would break that I think.

      Another option would be to set the adjustsImageWhenHighlighted property of each UIButton to NO in the method where you create the buttons. This would stop a highlight appearing always (whether selected or not) but would always keep the button active. This seems to be the way the built in UITabBar works.

      Thanks for the feedback.

    • olifarago says:

      Actually. Victor’s comment has made me notice something else.

      In this example we are using the touchupinside event. Testing the built in bar further it seems to be more like the touchdowninside event or similar as it doesn’t wait for the touch to end before changing selection.

  5. resident says:

    Thanks for the tutorial.

    I have a question. I try to use this tutorial in my app. In my app the UITabBarController aren’t in appDelegate class are in other class, then in this other class don’t call viewDidAppear method of CustomTabBar.

    How I can Fix it?

    Thanks,

  6. nate says:

    Great tutorial. I have a question with something I am working on maybe you might be able to shed some light on it as I am stuck.

    My goal is to keep the same UINavigation bar at the top at all times in my UITabBarController and have just the view change depending on what tab is modified. From interface builder I created the first tab with its navigation controller and it looks as it should and has a uinavigationbar at the top, a view and a uitabbar at the bottom. I have added a button that when clicked needs to add a button item to the uitabbar while adding a different view but not an additional controller.

    I am having some issue understanding how to add custom uitabbaritems that can switch only the rootviewcontroller view of the uinavigationcontroller. I think I am close just might be missing something simple if someone might be able to shed some light on it for me. I have posted some code from when the IBAction button that gets called on the uinavigationcontroller.

    (void)add:(id)sender {

    ListTableViewController *pageTableViewController;
    NSMutableArray *listOfViewControllers = [[NSMutableArray alloc] init];

    pageTableViewController = [[ListTableViewController alloc] init];
    pageTableViewController.title = [[NSString alloc] initWithFormat:@”%d”,pageNumber++];
    [listOfViewControllers addObject:pageTableViewController];
    [pageTableViewController.title release];
    [self.tabBarController setViewControllers:listOfViewControllers animated:YES];

    }

    I tried playing with creating a UINavigationController and using the initWithRootViewController but it seems to kinda work but adds a navigationcontroller to each tab rather than having one universal one and just switching out the controllers view.

  7. Jesse says:

    Apple specifically states in the docs that UITabbarController isn’t intended for subclassing. Have you had an app which uses this technique accepted to the app store?

    You might be able to get around it by adding a tabbar to a UIViewController and implementing all the interaction from scratch.

    • olifarago says:

      Yes, I have had this accepted in two App Store applications both of which have also been featured as New and Noteworthy. So they don’t seem to mind at the moment.

  8. Yuval says:

    THANK YOU. This was so very very very helpful.

  9. Andy says:

    Hi,
    First thank you for your great tutorial!
    Would you be able to show how to implement hiding and showing the custom tabbar like:
    controller.hidesBottomBarWhenPushed = YES; ?

    Thanks again, great work!
    Andy

  10. Mr.Black says:

    Hi…I really appreciate your GREAT!! tutorial!!

    Thanks a lot.

  11. Andy Lake says:

    Hi, thank you very much. Clean and very useful code.

    Good luck with your projects.

  12. nautanki says:

    Oh God.. Thank you very much.. looking for this one from past 3 days.. saved me from another night out.. once again Thanks a lot

    Only question : There is constarint that UITabBarController is not supposed to subclas.. any messup with Apple Code Approval…

    • olifarago says:

      My pleasure. No, as I mention in the article, although you are not supposed to subclass the UITabBarController, I have successfully used this method in two applications which are currently live on the App Store,

      Good luck!

  13. peasoup says:

    Hey!
    Thanks for that great tut, really helpful and easy to understand!
    As some others I’d like to hide also the custom tabbar in some views. I tried to use a method in the CustomTabBar class, but although it get’s called, it has no effect (it works if I call it from the CustomTabBar.m in viewDidAppear itself. It get’s called from other ViewControllers, I tested that, but has no effect than). I tried to call it in initWithNibName, viewDidLoad and viewWillAppear, none of them gets the buttons to be hidden, but all to reach the method..
    Any idea how to get that done?
    Thank you very much!

  14. [...] this tutorial I set up a custom TabBar. Unfortunately the tutorial won’t describe how to hide the [...]

  15. Klas says:

    Thanks for posting this just what I was looking for!

    Just curious, any particular reason why you use setBackgroundImage instead of setImage?

    I was trying to increase the touch area of my buttons (I only use three with rather small icons) by expanding the frame of each button like this:

    btn1.frame = CGRectMake(x, y, iconWidth, iconHeight);
    btn1.frame = CGRectInset(btn1.frame, -20, -5);

    When using setBackgroundImage the image would stretch out to fit the expanded frame but when using setImage it worked as intended, increasing the touch area but not the button image size. Gave me a bit of a headache for a while so just thought I’d post it here in case anyone else runs in to the same problem.

    A side effect of using setImage though seems to be that the image is forcibly highlighted on touch down. I’ve tried setting adjustsImageWhenHighlighted = NO
    on it but to no effect.

    • olifarago says:

      Hi Klas,

      No there is no reason why you couldn’t just use setImage. Not sure why I used setBackgroundImage to be honest!

      Oli

  16. [...] from where we left off at the end of the last tutorial, we add a new method to our RXCustomTabBar class called hideNewTabBar. To do this we will add the [...]

  17. Domenico says:

    Hi, Thanks for the tutorial. i’m using it to modify the existing tabBarController of my app.
    i have a question: in my app i use the tabBarControllerDelegate: method didSelectViewController and didEndCustomizingViewControllers, but seems that the new CostumTabBarController doesn’t work with it.
    Maybe because the tabBar is hidden. there some workaround to fix it??
    thanks Domenico

  18. Domenico says:

    Sorry, in the last post i was wrong, the methods, that i would like to use, are didSelectViewController and shouldSelectViewController, but should be the same

  19. Dan says:

    Hi,
    I’m Dan from one of the original comments here. Just wanted to say big thanks for the tab bar and the help you’ve provided by explaining everything. I actually worked out my original problem before you got back to me, only came across this thread today and noticed you’re reply – but glad to see I’ve implemented it the same way as yourself in part 2.
    I’ve used the tab bar in a number of apps as well, and they all work great!

    Thought i’d post a follow up with my gratitude.

    Thanks very much and all the best.
    Dan

  20. Domenico says:

    hi, i have a question. i use your awesome tab bar costum for my app.
    now i have a trouble with more than five tab bar items.
    moreNavigationController seems to create some problem.
    the navigationController (es. tag 5) appears, but the navigation bar is blue and not black and i don’t manage to click the own table.
    could you help me
    thanks Domenico

    • olifarago says:

      Domenico, I am afraid this is not something I have tried and do not have time to try just at the moment. Perhaps try posing a question over at Stack overflow.

      Sorry I can’t be more help,
      Oli

  21. hunk says:

    Hi, nice tutorial, I have a problem with ios5 , when I add a navcontroller to a view, when they click a button back on the view menu navigation is not encouraged ([self.navigationController popViewControllerAnimated:YES];). someone has solved this?

    Note, pushviewcontroller work fine.

  22. hacees says:

    Hello, very useful tutorial. I’ve tried to use this without directly adding to the main window but it didn’t work. My case is I have a view controller that presents modally the second view controller which contains the tab bar that should be customized but really didn’t work for me. It’ll always show the default tab bar from apple. Has anybody tried it? Thanks..

  23. Esben says:

    I’ve got this working – thanks for at really good tutorial.
    Now what i have stumbled into is when tabbing one item on index above 4, the More button is showed in the upper left corner.
    How do I get rid of that?
    Also it seems like when i have more than five tabs, the UITabBarController pushes a navigationcontroller with the last views to enable the More view.

    I don’t want that ince i have a scrollable tabbar. How can i get rid of the More button?
    Should i remove the More viewcontroller on the navigation stack?

  24. [...] i wanted to build a tabbar that has more than 5 items and is scrollable and found this article. Easy done by subclassing the UITabBarController and hide the existing [...]

  25. Andrew awlcs says:

    For the animation problem, look in viewDidAppear method of the rumexCustomTabBar

    - (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated]; //[super viewWillAppear:animated]; change this and you will have the animations again

    [self hideTabBar];
    [self addCustomElements];
    }

  26. Tassos says:

    I have noticed the following problem when using the custom tabbar on an App with View Navigation. The animation sliding effects when you are hiding back from a detail view to the master view is not working. The animation works when you oush the detail view

  27. Soniya says:

    Thanks. I have already created my tab bar but i didnt get how to pop to root view controller on clicking same tab. Your code works for me in that. Thanks a lot

    • olifarago says:

      You would need to put some code in the button press event that checked if the corresponding tab was already visible, and if so would call PoptoRootViewController.


Leave a Reply

Videos, Slideshows and Podcasts by Cincopa Wordpress Plugin