Tab bar controllers are implemented by the
UITabBarController class. They allow a user of to
switch between multiple arbitrary view controllers by maintaining an
array of UIViewControllers
.
They can be used to allow the user to navigate between entirely different parts of your application, or they can be used to display two different views of the same backing data. Tab bar controllers also have the built-in ability to display a "More..." interface when more than 5 tabs are added. They can also let the user customize which tabs are shown by on the main tab bar when there are more than 5 tabs. An example of this behavior is found in the standard iOS "Music" app (previously "iTunes").
This guide covers common use cases for tab bar controllers. A more in depth guide by Apple can be found here
In order to demonstrate basic functionality of the UITabBarController
we will build a very basic clone of the standard iOS "Clock" app. Our
app will have only two tabs one to display the current time, and the
other will implement the stopwatch functionality.
We create a new "Single View Application" and add two subclasses of
UIViewController
via File -> New -> iOS -> Source -> Cocoa Touch
Class
. We create a ClockViewController
and a
StopwatchViewController
.
We open up our Main.storyboard
and go ahead and delete the
pregenerated view controller. Dragging a "Tab Bar Controller" from the
Object Library into the storyboard automatically creates two view
controllers that are already in the tab bar controller's array of view
controllers. We also set the the the tab bar controller to be our
application's root view controller selecting it and by ticking the Is
Initial View Controller
checkbox.
We can add more tabs by dragging a new view controller frm
the Object Library onto the storyboard and then control-dragging from
the tab bar controller to our the new view controller and then selecting
Relationship Segues -> view controllers
. This will add the new view
controller to the tab bar controller's array of view controllers. It
also automatically adds a tab bar item to our new view controller which
will alow us to configure the appearance of the button that represents
this view controller in the tab bar.
In our case we only needed two view controllers total, but we removed and readded one above to illustrate the technique.
We set the Custom Class
property of one of the view controllers in our
tab bar to ClockViewController
and the other to
StopwatchViewController
. Now we can design our view controllers and
connect @IBOutlets
as we would any other view controller.
We add a single label to the ClockViewController
. It will display the
current time. We add a label to the StopwatchViewController
top
display the elapsed time and add two buttons for "start" and
"stop/reset". We add @IBOutlets
for our two labels and add
@IBActions
to respond to each button being tapped.
Finally we can add the code to make our clock tick
class ClockViewController: UIViewController {
@IBOutlet weak var timeLabel: UILabel!
var timer: NSTimer?
let dateFormatter = NSDateFormatter()
func updateTime() {
timeLabel.text = dateFormatter.stringFromDate(NSDate())
}
override func viewDidLoad() {
dateFormatter.dateFormat = "hh:mm:ss"
updateTime()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "updateTime", userInfo: nil, repeats: true)
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
timer?.invalidate()
}
}
// ClockViewController.m
#import "ClockViewController.h"
@interface ClockViewController ()
@property (weak, nonatomic) IBOutlet UILabel *timeLabel;
@end
@implementation ClockViewController
NSTimer *clockTimer;
NSDateFormatter *clockDateFormatter;
- (void)updateTime{
self.timeLabel.text = [clockDateFormatter stringFromDate:[NSDate date]];
}
- (void)viewDidLoad {
[super viewDidLoad];
clockDateFormatter = [[NSDateFormatter alloc] init];
clockDateFormatter.dateFormat = @"hh:mm:ss";
[self updateTime];
}
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
clockTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateTime) userInfo:nil repeats:true];
}
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[clockTimer invalidate];
}
@end
We also need to add a stop watch controller:
import UIKit
class StopwatchViewController: UIViewController {
@IBOutlet weak var elapsedTimeLabel: UILabel!
let dateFormatter = NSDateFormatter()
var elapsedTimeAtStop: NSTimeInterval = 0
var dateAtStart: NSDate?
var timer: NSTimer?
override func viewDidLoad() {
dateFormatter.dateFormat = "mm:ss.S"
dateFormatter.timeZone = NSTimeZone(abbreviation: "UTC")
updateElapsedTime()
}
func updateElapsedTime() {
elapsedTimeLabel.text = dateFormatter.stringFromDate(dateForFormatter())
}
private func dateForFormatter() -> NSDate {
if let startDate = self.dateAtStart? {
let intervalSinceStart = NSDate().timeIntervalSinceDate(startDate)
let totalElapsedTime = elapsedTimeAtStop + intervalSinceStart
return NSDate(timeIntervalSince1970: totalElapsedTime)
}
return NSDate(timeIntervalSince1970: elapsedTimeAtStop)
}
@IBAction func startButtonTapped(sender: AnyObject) {
if dateAtStart == nil {
dateAtStart = NSDate()
timer = NSTimer.scheduledTimerWithTimeInterval(1.0/10.0, target: self, selector: "updateElapsedTime", userInfo: nil, repeats: true)
}
}
@IBAction func stopButtonTapped(sender: AnyObject) {
if let startDate = dateAtStart? { // stop
elapsedTimeAtStop += NSDate().timeIntervalSinceDate(startDate)
} else { // reset
elapsedTimeAtStop = 0
}
timer?.invalidate()
timer = nil
dateAtStart = nil
updateElapsedTime()
}
}
// StopwatchViewController.m
#import "StopwatchViewController.h"
@interface StopwatchViewController ()
@property (weak, nonatomic) IBOutlet UILabel *elapsedTimeLabel;
- (IBAction)startButtonTapped:(UIButton *)sender;
- (IBAction)stopButtonTapped:(UIButton *)sender;
@end
@implementation StopwatchViewController
NSDateFormatter *dateFormatter;
NSTimeInterval elapsedTimeAtStop = 0;
NSDate *dateAtStart = NULL;
NSTimer *timer;
- (void)viewDidLoad {
[super viewDidLoad];
dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = @"mm:ss.S";
dateFormatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"];
[self updateElapsedTime];
}
-(void)updateElapsedTime{
self.elapsedTimeLabel.text = [dateFormatter stringFromDate: [self dateForFormatter]];
}
-(NSDate*)dateForFormatter{
if(dateAtStart != NULL){
NSTimeInterval intervalSinceStart = [[NSDate date] timeIntervalSinceDate:dateAtStart];
NSTimeInterval totalElapsedTime = elapsedTimeAtStop + intervalSinceStart;
return [NSDate dateWithTimeIntervalSince1970:totalElapsedTime];
}
return [NSDate dateWithTimeIntervalSince1970:elapsedTimeAtStop];
}
- (IBAction)startButtonTapped:(UIButton *)sender {
if(dateAtStart == NULL){
dateAtStart = [NSDate date];
timer = [NSTimer scheduledTimerWithTimeInterval:1.0/10.0 target:self selector:@selector(updateElapsedTime) userInfo:nil repeats:true];
}
}
- (IBAction)stopButtonTapped:(UIButton *)sender {
if(dateAtStart != NULL){ //stop
elapsedTimeAtStop += [[NSDate date] timeIntervalSinceDate:dateAtStart];
}
else{ //reset
elapsedTimeAtStop = 0;
}
[timer invalidate];
timer = NULL;
dateAtStart = NULL;
[self updateElapsedTime];
}
@end
If you have 2 view controllers vc1 and vc2 created programmatically, then you can instantiate a UITabBarController
and initialize it like this:
let tabBarController = UITabBarController()
tabBarController.viewControllers = [vc1, vc2]
UITabBarController *tabBarController = [[UITabBarController alloc]init];
tabBarController.viewControllers = @[vc1, vc2];
Before we set up tab view controller programmatically, we need to remove the tab view controller that we added in the Interface Builder (IB).
In IB, remove the segues you have from tab bar view controller to both clock view controller and stop watch view controller. After that delete the tab view controller altogether from IB.
Your story board should look something like this now:
When the app starts up, our AppDelegate's function didFinishLaunchingWithOptions
gets called. This is where we can do any custom view controller set up.
This is sample code from AppDelegate.swift:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// create UIWindow with the same size as main screen
window = UIWindow(frame: UIScreen.mainScreen().bounds)
// create story board. Default story board will be named as Main.storyboard in your project.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
// create view controllers from storyboard
// Make sure you set Storyboard ID for both the viewcontrollers in
// Interface Builder -> Identitiy Inspector -> Storyboard ID
let clockViewController = storyboard.instantiateViewControllerWithIdentifier("ClockViewController")
let stopWatchViewController = storyboard.instantiateViewControllerWithIdentifier("StopWatchViewController")
// Set up the Tab Bar Controller to have two tabs
let tabBarController = UITabBarController()
tabBarController.viewControllers = [clockViewController, stopWatchViewController]
// Make the Tab Bar Controller the root view controller
window?.rootViewController = tabBarController
window?.makeKeyAndVisible()
return true
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// create UIWindow with the same size as main screen
self.window = [[UIWindow alloc]initWithFrame:UIScreen.mainScreen.bounds];
// create story board. Default story board will be named as Main.storyboard in your project.
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
// create view controllers from storyboard
// Make sure you set Storyboard ID for both the viewcontrollers in
// Interface Builder -> Identitiy Inspector -> Storyboard ID
ClockViewController *clockViewController = [storyboard instantiateViewControllerWithIdentifier:@"ClockViewController"];
StopWatchViewController *stopWatchViewController = [storyboard instantiateViewControllerWithIdentifier:@"StopWatchViewController"];
// Set up the Tab Bar Controller to have two tabs
UITabBarController *tabBarController = [[UITabBarController alloc]init];
tabBarController.viewControllers = @[clockViewController, stopWatchViewController];
// Make the Tab Bar Controller the root view controller
self.window.rootViewController = tabBarController;
[self.window makeKeyAndVisible];
return YES;
}
Make sure you provide story board id for both clock and stop watch view controllers in IB so that they can be initialized in code:
It is very common for a tab bar controller to have navigation controllers as root view controllers. If this is the case, you will want to assign the storyboard ID to the navigation controller instead of the view controller directly. You can access the top view controller in the navigation stack to do any additional configuration.
let nav = storyboard.instantiateViewController(withIdentifier: "ExampleNavigationController") as! UINavigationController
let vc = nav.topViewController as! ClockViewController
// perform any initial configuration of vc here...
UINavigationController *nav = [storyboard instantiateViewControllerWithIdentifier:@"ExampleNavigationController"];
ClockViewController *vc = (ClockViewController *)nav.topViewController;
// perform any initial configuration of vc here...
to be completed
The tab bar has limited space for displaying your custom items. If you add six or more
custom view controllers to a tab bar controller, the tab bar controller displays only
the first four items plus the standard More
item on the tab bar.
Tapping the More item brings up a standard interface for selecting the remaining items.
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window = UIWindow(frame: UIScreen.mainScreen().bounds)
let vc1 = setUpViewController("First", backgroundColor: UIColor.orangeColor())
let vc2 = setUpViewController("Second", backgroundColor: UIColor.purpleColor())
let vc3 = setUpViewController("Third", backgroundColor: UIColor.redColor())
let vc4 = setUpViewController("Fourth", backgroundColor: UIColor.greenColor())
let vc5 = setUpViewController("Fifth", backgroundColor: UIColor.blueColor())
let vc6 = setUpViewController("Sixth", backgroundColor: UIColor.yellowColor())
// Set up the Tab Bar Controller
let tabBarController = UITabBarController()
tabBarController.viewControllers = [vc1, vc2, vc3, vc4, vc5, vc6]
// Make the Tab Bar Controller the root view controller
window?.rootViewController = tabBarController
window?.makeKeyAndVisible()
return true
}
func setUpViewController(title: String, backgroundColor: UIColor) -> UIViewController {
let vc = UIViewController()
vc.view.backgroundColor = backgroundColor
vc.tabBarItem.title = title
vc.tabBarItem.image = UIImage(named: "star")
return vc
}
}
// AppDelegate.h
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
// AppDelegate.m
#import "AppDelegate.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc]initWithFrame:UIScreen.mainScreen.bounds];
UIViewController *vc1 = [self setUpViewController:@"First" withBackgroundColor:UIColor.orangeColor];
UIViewController *vc2 = [self setUpViewController:@"Second" withBackgroundColor:UIColor.purpleColor];
UIViewController *vc3 = [self setUpViewController:@"Third" withBackgroundColor:UIColor.redColor];
UIViewController *vc4 = [self setUpViewController:@"Fourth" withBackgroundColor:UIColor.greenColor];
UIViewController *vc5 = [self setUpViewController:@"Fifth" withBackgroundColor:UIColor.blueColor];
UIViewController *vc6 = [self setUpViewController:@"Sixth" withBackgroundColor:UIColor.yellowColor];
// Set up the Tab Bar Controller
UITabBarController *tabBarController = [[UITabBarController alloc]init];
tabBarController.viewControllers = @[vc1, vc2, vc3, vc4, vc5, vc6];
// Make the Tab Bar Controller the root view controller
self.window.rootViewController = tabBarController;
[self.window makeKeyAndVisible];
return YES;
}
- (UIViewController*) setUpViewController:(NSString*)title withBackgroundColor:(UIColor*)backgroundColor{
UIViewController *vc = [[UIViewController alloc]init];
vc.view.backgroundColor = backgroundColor;
vc.tabBarItem.title = title;
vc.tabBarItem.image = [UIImage imageNamed:@"star"];
return vc;
}
In More tab, user can tap on Edit button. That will allow user to reorder tabs so that they can pick the which 4 view controllers will appear on the tab view and which view controllers will be in More tab.
By default, the user is allowed to rearrange all items on the tab bar.
You can specify what view controllers user can rearrange by setting
the customizableViewControllers
property.
By default, all the items added to tab bar are also added to customizableViewControllers
.
If we don't want to allow user to change the view controllers for the first 2 tabs, then we can set customizableViewControllers to the other 4 view controllers:
let tabBarController = UITabBarController()
tabBarController.viewControllers = [vc1, vc2, vc3, vc4, vc5, vc6]
tabBarController.customizableViewControllers = [vc3, vc4, vc5, vc6]
UITabBarController *tabBarController = [[UITabBarController alloc]init];
tabBarController.viewControllers = @[vc1, vc2, vc3, vc4, vc5, vc6];
tabBarController.customizableViewControllers = @[vc3, vc4, vc5, vc6];
In More tab -> Edit, user can now only see and reorder 3rd, 4th, 5th, 6th tabs.
to be completed