Working with Orientation Changes on the iPhone

// May 1st, 2009 // auto-rotation, code, iphone, iphone sdk, orientation changes, programming, sample code, viewcontroller

Update: It looks like Apple has change the way shouldAutoRotateToInterfaceOrientation works in iPhone SDK 3.0. Here is my updated post on how to do this differently.

One of the features with which I had to get fairly intimate with on the iPhone over the last couple of months was working with orientation changes. At first my code was completely incorrect and it wasn’t immediately obvious to me how to get my application to reorient itself properly. This was mainly due to the structure of my code. In this post I am going to explain a simple, memory efficient way of working with UIViewController and interface orientation changes.

1. The Basics

Before we get into the nitty gritty let’s first have a recap of how interface orientation changes work and how you should respond to them. Firstly, if you want your view to auto-rotate you need to override the method shouldAutorotateToInterfaceOrientation: and return YES. If you want to only allow auto-rotation under a certain condition you can put the test for this condition in this method too.

This is pretty much the most basic thing you need to do to allow for auto-rotation but there are additional methods you can override that can be very useful. These methods are

• willRotateToInterfaceOrientation

• didRotateFromInterfaceOrientation

• willAnimateFirstHalfOfRotationToInterfaceOrientation

• willAnimateSecondHalfOfRotationFromInterfaceOrientation

The first 2 methods are very useful for doing pre- and post-processing of your rotation. You could perhaps initialize a view controller or add some views to your current view in the willRotateToInterfaceOrientation. The second 2 are pretty much self explanatory. If you want to perform additional operations during that particular phase of the rotation you can implement them too.

Another very useful code example for when working with view controller orientations is:

if(UIInterfaceOrientationIsLandscape(interfaceOrientation)){
    //do some processing…
}else if(UIInterfaceOrientationIsPortrait(interfaceOrientation)){
    //do different processing…}
else if(UIDeviceOrientationIsValidInterfaceOrientation(interfaceOrientation)){
    //do something
}

Most programmers aren’t aware of these macros and they do cut down and simplify code.

2. Loading & Unloading View Controllers

To save on memory usage you can load and unload view controllers as your interface orientation changes. I found this incredibly useful as my application required a fair amount of memory. It basically works on the principle that the application’s delegate class will manage the changing of the view controllers.

In you application delegate you can add the following method:

- (void) switchViewControllers:(UIViewController *)controller{
if(self.viewController != nil){
    [viewController.view removeFromSuperview];
}
    self.viewController = controller;
    [window addSubview:self.viewController.view];
}

Make sure you have a viewController property on the application delegate of course.

When switchViewControllers is called, your currently visible view is removed from its superview and the new view controller and its associated view are added.

The next step is to add this piece of code inside your view controllers that you want to switch out during auto rotation:

- (BOOL)shouldAutorotateToInterfaceOrientation:
                (UIInterfaceOrientation)interfaceOrientation {
    CustomUIViewController *customUIViewController = 
                [[CustomUIViewController alloc] init];
    MyAppDelegate *appDelegate = 
        (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
    appDelegate switchViewControllers: customUIViewController];
    [customUIViewController release];
}

Where CustomUIViewController is your own view controller you want to switch to.

3. Pitfalls

This approach has worked very well for me but I did have an issue where I was using 3 view controllers. Let’s call them A, B and C. A switched to B and vice-versa when the phone’s orientation changed. C was loaded by B and was viewable in landscape and portrait orientations. The problem came in where if you changed C’s orientation and then the user navigated back to B where B had a different orientation, you would effectively not be notified of an orientation change. If you subsequently changed B’s orientation to load A, you would find A would load in the wrong orientation. Oops, bug…

After trolling a number forums I discovered there used to be an undocumented call in the SDK called changeOrientation: but for some or other reason Apple removed this method.

So how did I solve this? Well all I did was put this piece of code in A so that it would manually reorient itself from portrait to landscape:

- (void)viewDidAppear:(BOOL)animated {
    if(UIInterfaceOrientationIsLandscape(self.interfaceOrientation)){
        [UIView beginAnimations:@”View Flip” context:nil];
        [UIView setAnimationDuration:0.5f];
        [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
        self.view.transform = CGAffineTransformIdentity;
        self.view.transform = 
            CGAffineTransformMakeRotation(MPI * (90) / 180.0);
        self.view.bounds = CGRectMake(0.0f, 0.0f, 480.0f, 320.0f);
        self.view.center = CGPointMake(160.0f, 240.0f);
        [UIView commitAnimations];
    }
}

Conversely you can use this piece of code to reorient from landscape to portrait if your “A” view controller needs to be in portrait mode:

- (void)viewDidAppear:(BOOL)animated {
    if(UIInterfaceOrientationIsPortrait(self.interfaceOrientation)){
        [UIView beginAnimations:@”View Flip” context:nil];
        [UIView setAnimationDuration:0.5f];
        [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
        self.view.transform = CGAffineTransformIdentity;
        self.view.transform = 
            CGAffineTransformMakeRotation(MPI * (90) / 180.0);
        self.view.bounds = CGRectMake(0.0f, 0.0f, 320.0f, 480.0f);
        [UIView commitAnimations];
    }
}

So far this method of loading and unloading view controllers on orientation changes has proved to be very memory efficient and not too performance intensive either. Let me know in the comments if it works for you or if you have any ideas about this method of managing view controllers and orientation changes.

6 Responses to “Working with Orientation Changes on the iPhone”

  1. cafebabe says:

    In step 2 your code for shouldAutorotateToInterfaceOrientation: doesn’t return a BOOL, and seems more like the code you’d put in will/didRotateToInterfaceOrientation. Is this a typo?

  2. Michael Gaylord says:

    I found using the shouldRotateToInterfaceOrientation method a better place to load the view controllers because it makes sure the view controller is loaded before any orientation changes happen. I accidentally left out the line return YES at the end of the method. Thanks for that.

  3. charles says:

    MPI is undeclared. Can you clarify what MPI is?

  4. Michael Gaylord says:

    Oops. Typo. MPI should be M_PI. Just corrected it.

  5. I’ve been having trouble finding an example that handles what I want to do, and unsure if using the auto rotation feature is even the proper way to handle it. Basically, I want to leave everything in my interface right where it is, but rotate all the buttons in-place so they always appear right-side-up.

    Do you think it would it be better for me to just write my own test in the accelerometer delegate method in my app delegate, or should I go ahead and handle this in my view controller(s)?

    I guess it comes down to whether shouldAutorotateToInterfaceOrientation is called every time the device rotates, or just at the initialization of the view.

    Thoughts?

  6. Michael Gaylord says:

    Hi Scott,

    Apologies for the belated reply. I have since had some issues using the method I described in that blog post with iPhone OS 3.0 but you can find more information in one of my questions on http://www.stackoverflow.com.

    You can register your controller to listen for device orientation changes and then move your views accordingly which is probably the best way of going about doing what you want to do.

    Hope that helps and if you have already found a method of doing it, it would be great to hear about it.

Leave a Reply