Ruby on Rails, iOS, Git...

iPhone SDK: UITabBarController, How to Save User Customized Tab Order

Posted by Tim Stephenson, RaddOnline® on Friday, February 27, 2009

Even with Apple's iPhone SDK, There's No Free Lunch.

With the UITabBarController Apple has provided tons of functionality for free. One
thing that the UITabBarController does not do is save the order of the
tabs when a user changes them, so we have to do that
task by ourselves.

UITabBarController Class: Quick Background

The UITabBarController class in the iPhone SDK enables users to move between multiple view controllers with a click of a tab at the bottom of the screen. If you have more than five tabs in your application, it also offers a More tab. When tapped, it displays the additional tabs in a table for users to select them. When in the More tab of the application, clicking on the "Edit" button brings up a view where users can customize the order of the tabs to suit their own needs.

While they can re-order the tabs, unless we take a few steps in our code, their settings won't be saved.

Overview: Saving Customized Tab Order

When the application quits, save the current order of the tabs to an array in the user defaults.

The next time it opens, retrieve the saved order of the tabs from the defaults.

Loop through the view controllers and place them into an array with the saved order.

Provide the newly ordered array to the UITabBarController for display.

I'm going to go step by step through the process with an example that does not use Interface Builder to define the UITabBarController only because it makes it a little easier to understand how the array of controllers is created. I did the example both ways so both example projects are attached.

UITabBarController Project Setup

I created a new project in Xcode using the Window Based Applicaiton template. Because I'm a little lazy, I opted to reuse the same controller and view in each of the tabs. The view has a single label in it that is changed to match the title of each tab.

Right click on the classes group and select add new file.

Select Cocoa Touch Classes, and then select UIViewController Subclass and give it a name. I named mine AllTabsViewController.

Right click on resources folder in Xcode and add a new file. This time select User Interfaces and then select View XIB. I named my nib file AllTabsView.xib.

Open the new nib file, select the File's Owner icon and then change the class to AllTabsViewController in the Identity inspector. You should see the nameLabel outlet in the Class Outlets portion of the screen.

Drag a label from the library onto the view. I centered mine and made it nice and big.

Control click on the File's Owner icon and drag from the nameLabel outlet to the label we added. Also drag from the view outlet to the view in our nib file.

When done, close InterfaceBuilder.

Start by Adding Tabs to the Tab Bar Controller

Next we'll set up the application to build the tabs and load the controllers.

Back in Xcode, open the AllTabsViewContrller.m file to finish the implementation of the view controller.

First, synthesize our property for the label:

@synthesize nameLabel;

While we're at it, let's go ahead and make sure it get's released to:

- (void)dealloc { [nameLabel release]; [super dealloc];}

The only other thing to do here is change the label when the view loads. Go to the viewDidLoad method and add the following code:

Now open the app delegete class header file, in my case it is called TabBarSaveStateAppDelegate.h. We need to make it a UITabBarController delegate and add a tabBarController porperty. When done it should look like this:

This code instantiates six view controllers and gives each a unique title. The title will appear on the tab, and on the label in the view itself.

It then adds each of the view controllers to an array called viewControllers that is a property of the UITabBarController class. Finally it adds the view to a subview of the window.

You should be able to build and run the application. Click on the More tab, edit the order of the tabs. If you close and re-open the application, you should notice that the order you set for the tabs is lost. The tabs will be in the default order. We'll take care of that next.

Saving the Tab Order When the Application Closes

Now that our sample is all set up, I'll take care of the problem I set out to solve in the first place. We'll be doing all this in the AppDelegate class. Open TabBarSaveStateAppDelegate.m again.

The application delegate provides a method that will be called when the application terminates called applicationWillTerminate:. We'll use it to save the state our our tabs. Add this code to the file:

We create two arrays. One that holds the current list of view controllers called tabOrderToSave, and one that stores an array of strings that identify each of our controllers, savedOrder. When I created the view controllers I gave each of them a title. We'll use that title to identify the controllers when the application opens.

You have to be careful here. If the title were being set during a viewWillAppear method for example, this would not work. The title would not be available. Another strategy would be to use a tag, or to explicitly store a property in the controller that could be used as an identifier. For this purpose, the title works fine.

The final line saves the array into the standard user defaults and gives it a key that we can use to recover it later.

Restoring the Order of the Tabs in the Tab Bar Controller

For this step, I'm going to add a helper method to the app delegate class.

Back in the TabBarSaveStateAppDelegate.h file, add this method to the interface.

What's going on? First we get a reference to the standard user defaults. The second line restores the array that was saved when the application closed. Then we set up two loops. The outer loop goes through the saved order, and the inner loops checks the title of the controller against the string that was saved into the savedOrder array. When it gets a match, the controller is added to a new array. Finally, the new array is assigned to the tabController.viewControllers property.

To finish up, just call the new method before the tab controller is added to the window's subview.

...

[self setTabOrderIfSaved];

[window addSubview:tabController.view];

// Override point for customization after application launch [window makeKeyAndVisible];}

You should now be able to build and run the application. Reorder the tabs and close the application. When you open it the next time, they should be in the custom order.

I did the same tasks using a interface builder. To create the project I used the UITabBarController project template. There were several differences.

Titles have to be set in Interface Builder. I gave each controller a title in interface builder by clicking on the tab in the MainWindow.xib file and entering the title in the in the title field view controller inspector.

The class for each controller has to be assigned in interface builder both in the main window file and in the second view file.

As I added tabs in interface builder, I set each tab to use the SecondView nib.

In the applicationWillTerminate: method I used the tabBarItem.title property for my identifier like this:

Yes, to do this or not is an interesting question. The YouTube and iPod apps use more than 5 tabs very effectively.

Nathan w
said on Friday, April 10, 2009:

This is great! I have been having problems with tabs and this has helped me a lot! Thank you!

Elliot
said on Wednesday, April 22, 2009:

Awesome piece of code, however I was wondering if its possible to set up one of the tab views to also have buttons that link and load other views (i.e the ones not visible on the tab bar controller) when the application is started. Kind of like a main menu?

Chad
said on Saturday, May 02, 2009:

Thank you very much! I added an additional loop at the end of setTabOrderIfSaved: that checks to see whether there are additional Controllers beyond those saved in defaults (useful during development):

Hi Tim. Extremely useful post. Is there an update for iPhone OS v3.1? This works great for me in v2.x, but not on OS v3.x. Thanks.

Jim
said on Friday, September 18, 2009:

Instead of title:
aViewController.title
you can use instead:
aViewController.tabBarItem.tag

and then you set the title dynamically for each view controller.

applewhy
said on Thursday, September 24, 2009:

superb!! Thanks for the tutorial.

I have been having problems with tab ordering and I just fix it. Thank you!! ;)

worked with 3.0 and 3.1 all fine :)

dev
said on Tuesday, October 20, 2009:

hello Tim,

Thank you for the wonderful tutorial, it really helped me. I have another query, basically when I click on any one of extra tab bar items that remain under the more tab, I am able to see its table view but when I further select a row in that table view, the app crashed instead of going to the detail view. However if I use the same tab bar item when it is not inside the more tab , it works as it is supposed to. Could you please guide me how to resolve this?

Gabriel Pachecoby
said on Saturday, December 19, 2009:

Thanks dude It's works perfectly , but I modified a lit by creating those tabs at IB and named then at viewDidLoad ,and made yours way to save then but calling the method atdidEndCustomizingViewControllers!
Cheers

Alan D.
said on Thursday, December 31, 2009:

MADE OF AWESOME! Thanks! I spent an hour or two trying to figure out how to do this, then I finally broke down and googled, which found me a page which pointed me to yours. ;-) 10 minutes later, my app stores its tab bar item order!

Vipul
said on Friday, February 12, 2010:

It worked ... Simple and easy way to implement this functionality

Antony Basta
said on Wednesday, July 14, 2010:

hello, i think this is a great tutorial. i made an app with the more tab in the UITabBar. And everything works. except your method of saving and displaying the saved contents of the tab bar don't work on iOS4. PLEASE PLEASE PLEASE post an update on how to imply this in 4.0

PLEASE!

Terry
said on Wednesday, July 28, 2010:

Antony,
I am also developing on iOS 4. In general, the code works just as expected. The only thing you have to change is the point at which the save gets done.

Think about it -- under iOS 4 apps typically never have "applicationWillTerminate" called. Instead they sleep. If you, like me, originally tried to test this by closing an app (which puts it to sleep) then killing the sleeping app, this results in the app stopping WITHOUT applicationWillTerminate ever being called.

So, basically, the updated tab order never gets saved!

So when should the save code get called? I added it to the uitabbarcontroller delegate "tabBarController:didEndCustomizingViewControllers:changed:" method as the perfect spot for it.

Note -- I'm not sure why, but I found that adding "[[NSUserDefaults standardUserDefaults] synchronize];" immediately after saving the values helped ensure that things got written out while debugging. I think exiting the app via the debugger might sometimes kill it too quickly for the values to get written out.

Terry

Xuan Chen
said on Friday, July 30, 2010:

This is a great tutorial. It helps me a lot. Thanks.

It works for me on iPod Touch with iOS4.0. I do not know why it does not work for some other users.

However there are two other issues: First, if I modified my application and add one more tab, it will not show the last tab. Second, if the user preference file is corrupted, the user will end up with a non-functional app.

I think the loading function needs a little modification. Here is what I did:

// assume all view controllers have been created and added to UITabBarController *tbc;

Basically it will add those view controllers in tbc but not in the user default preference after those in user default preference have been added.

victorvj
said on Wednesday, September 08, 2010:

Does anyone knows how to change put your own images in the tabbaritem in the non-interface builder version? I don't know how to change it. I have 3 view controllers and the code that controlls the tabbar is inside the appdelegate file