Change iPhone/iPod app orientation within a UITabBarController

September 4th, 2008

The documentation for the official iPhone SDK is less than stellar, and as a result I’ve encountered lots of frustrating little problems. In particular, I wasn’t able to get my app to change its orientation when the phone was rotated. UIViewController has a method shouldAutorotateToInterfaceOrientation: that gets called when the iPhone/iPod orientation is changed by the user, and overriding this method to return YES should cause your view to be rotated. You may have implemented this method and to your frustration, nothing happened when you rotated the device. The trick is that Cocoa only calls shouldAutorotateToInterfaceOrientation: on the top most UIViewController. Meaning if your program lives inside a UITabBarController (possibly with a UINavigationController inside some of the tab bar items), the UIViewController that the user is currently interacting with, is not necessarily the one receiving the method call.

In my app, everything lives inside a UITabBarController, so that’s what was receiving the shouldAutorotateToInterfaceOrientation: call. In order to get orientation changes to work you need to subclass UITabBarController (contrary to Apple doc recommendations) and override the method so you can return YES when it’s called. Below is some code for a simple UITabBarController subclass used inside a program that supports a horizontal view.

RotatingTabBarAppDelegate.h

#import <UIKit/UIKit.h>

#import "RotatingTabBarController.h"

/*
 Nothing special implemented in our delegate for this example.
 */

@class RotatingTabBarAppViewController;

@interface RotatingTabBarAppAppDelegate : NSObject <UIApplicationDelegate> {
    IBOutlet UIWindow *window;
}

@property (nonatomic, retain) UIWindow *window;

@end

RotatingTabBarAppDelegate.m

#import "RotatingTabBarAppAppDelegate.h"

@implementation RotatingTabBarAppAppDelegate

@synthesize window;

/*
 We’ll programmatically lay out a simple GUI for this demonstration.
 */

- (void)applicationDidFinishLaunching:(UIApplication *)application {    
    
    // Just make two empty view controllers for fun
    UIViewController *tab1 = [[UIViewController alloc] init];
    tab1.tabBarItem = [[UITabBarItem alloc] initWithTabBarSystemItem:UITabBarSystemItemTopRated tag:0];

    UIViewController *tab2 = [[UIViewController alloc] init];
    tab2.tabBarItem = [[UITabBarItem alloc] initWithTabBarSystemItem:UITabBarSystemItemSearch tag:1];
    
    // Now create an instance of our rotating tab bar controller
    RotatingTabBarController *tbc = [[RotatingTabBarController alloc] init];
    // Add the two view controllers to the tab bar
    [tbc setViewControllers:[NSArray arrayWithObjects:tab1, tab2, nil]];
    
    // Add the tab bar controller’s view to the window
    [window addSubview:tbc.view];
    
    // Make our program visible
    [window makeKeyAndVisible];
}

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

@end

RotatingTabBarController.h

#import <UIKit/UIKit.h>

/*
 The subclass doesn’t need any new methods or members.
 */

@interface RotatingTabBarController : UITabBarController {

}

@end

RotatingTabBarController.m

#import "RotatingTabBarController.h"

@implementation RotatingTabBarController

/*
 Just override this single method to return YES when we want it to.
 */

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Always returning YES means the view will rotate to accomodate any orientation.
    return YES;
}

@end

The one thing this doesn’t address, is when you only want certain views in your program to rotate. In that case you just need to make your UITabBarController ask the currently visible view controller in the program if the app should rotate, and return that BOOL.

Comments

  1. dandle
    September 19th, 2008 at 06:24 | #1

    Thanks for this tutorial, it was really simple, straightforward and very helpful!

    However, how can i change the frame of a table view so that it fills the screen when it rotates, and also is there a way of hiding the tab bar so that the table view can fill the whole screen when rotated?

    Thanks

  2. September 24th, 2008 at 22:39 | #2

    @dandle

    UIViewController has:
    @property(nonatomic) BOOL hidesBottomBarWhenPushed

    Just set that to YES and the tab bar will disappear.

    As for filling the screen, view frames automatically get changed/updated when the phone is rotated.

  3. Rust
    October 20th, 2008 at 13:33 | #3

    ah, wish that were true. Some of my UITableViews get re-framed. Others do not. I have no idea why as they are set up with resizing masks and such. The documentation is certainly less than stellar. I also will add that I think some of it is wrong. In addition, I believe the sdk platform is full of bugs.

    Thanks for this insight. I may try to crawl down this path to accomplish what seems like such a simple, simple thing – hide the tab bar when a certain view is focused so that the user cannot navigate to the table views that refuse to resize themselves in landscape. Grrrrrrr.

    :) I am happy.. happy happy happy. Think happy thoughts

  4. QP
    December 13th, 2008 at 14:25 | #4

    Everywhere I’ve seen references the “hidesBottomBarWhenPushed” but as far as I can tell this is just a myth. This value seems to only be recognized for UINavigationControllers. No one that I’ve seen has been able to demonstrate working code with UIViewControllers and UITabBarControllers.

    Very frustrating and the propagation of this stock answer (no offense intended) has just complicated finding a solution.

    If you (or anyone else out there) can demonstrate this working in code, then there are hundreds of threads out there that would benefit from your insight!

    QP

  5. December 17th, 2008 at 17:57 | #5

    @QP

    Here is the Apple doc for hidesBottomBarWhenPushed.

    If you would like to see it in action, I recommend looking at my Prayer Book app in the app store. When you select a prayer, I set hidesBottomBarWhenPushed to YES in my UIViewController subclass’ init method, and the tab bar disappears as expected when pushed.

  6. SamJ
    January 7th, 2009 at 09:07 | #6

    Hi,

    Thank you for the tutorial. It was really helpful. However, I am facing one problem. I have two tabs on my controller. What I did was I clicked the second tab and changed the orientation. But when I click the first tab, the orientation is still the original one. I am not able to understand why. Also, how can I catch the event when the user selects a tab? I tried applying the UITabBarDelegate protocol, set the tabbar delegate to the appdelegate class and have coded the
    -(void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item

    method in the appdelegate class. But this method is not getting called.

  7. Adam Freeman
    January 23rd, 2009 at 17:39 | #7

    This is yet another lame thing about the iphone sdk. I figured that out too but only after hours of frustration. Wish I had read this first …

  8. some joe
    February 4th, 2009 at 03:55 | #8

    UITabBarController calls shouldAutorotateToInterfaceOrientation: on all of its child view controllers in order, breaking as soon as one returns NO. If all your view controllers return YES for a given orientation, it will autorotate.

    It seems a little limiting that the UITabBarController can’t rotate to the best supported orientation when a tab button item is selected, but there it is.

  9. February 8th, 2009 at 17:03 | #9

    This post fixed an issue I wasted a few hours on last night, I should have googled earlier :(

    Thanks for the clear and concise directions….

  10. Vic
    July 17th, 2009 at 06:30 | #10

    I implemented RotatingTabBarController in my app. It works fine for change of orientation. But it causes a new problem.

    I have more than 5 tab pages. iPhone automatically adds the “More” button (handled by moreNavigationController). The “Mote” view shows an Edit button on top right. It is supposed to allow users to rearrange the tab icons. But the edit button no longer works.

    Is there a way to fix that?

  11. August 12th, 2009 at 19:46 | #11

    Greetings! I’ve got this working like a charm … except for the cases where the view controller in question originated from within the MoreNavigationController. Then things go all haywire.

    See this (over-descriptive) thread on Stack Overflow for the full scoop. Any ideas? :(

    http://stackoverflow.com/questions/1269704/uitabbarcontroller-moreviewcontroller-and-the-holy-grail-of-device-rotation

  12. August 13th, 2009 at 07:26 | #12

    Bug filed with Apple. Includes minimal test case app. :(

    http://openradar.appspot.com/7139857

Comments are closed.