Chapter 5. Autorotation and Autosizing

The iPhone is an amazing piece of engineering. Apple engineers found all kinds of ways to squeeze maximum functionality into a pocket-sized package. One example is the mechanism that allows applications to be used in either portrait (tall and skinny) or landscape (short and wide) mode and to change that orientation at runtime if the phone is rotated. A prime example of this behavior, which is called autorotation, can be seen in iPhone's web browser, Mobile Safari (see Figure 5-1).

Like many iPhone applications, Mobile Safari changes its display based on how it is held, making the most of the available screen space.

Figure 5.1. Like many iPhone applications, Mobile Safari changes its display based on how it is held, making the most of the available screen space.

Autorotation might not be right for every application. Several of Apple's iPhone applications support only a single orientation. Movies can be watched only in landscape mode, for example, and contacts can be edited only in portrait mode. Bottom line, if autorotation enhances the user experience, add it to your application.

Fortunately, Apple did a great job of hiding the complexities of autorotation in the iPhone OS and in the UIKit, so implementing this behavior in your own iPhone applications is actually quite easy.

Autorotation is specified in the view controller, so if the user rotates the phone, the active view controller will be asked if it's OK to rotate to the new orientation (something you'll see how to do in this chapter). If the view controller responds in the affirmative, the application's window and views will be rotated, and the window and view will get resized to fit the new orientation.

A view that starts in portrait mode will be 320 pixels wide and 460 pixels tall or 480 pixels tall if there's no status bar. The status bar is the 20-pixel strip at the top of the screen (see Figure 5-1) that shows things like signal strength, time, and battery charge. When the phone is switched to landscape mode, the view rotates, along with the application's window, and gets resized to fit the new orientation, so that it is 480 pixels wide by 300 pixels tall (320 pixels if there's no status bar).

Most of the work in actually moving the pixels around the screen is managed by the iPhone OS. Your application's main job in all this is making sure everything fits nicely and looks proper in the resized window.

Your application can take three general approaches when managing rotation. Which one you use depends on the complexity of your interface, and we'll look at all three approaches in this chapter. With simpler interfaces, you can simply specify the correct autosize attributes for all of the objects that make up your interface. Autosize attributes tell the iPhone how your controls should behave when their enclosing view gets resized. If you've worked with Cocoa on Mac OS X, you're already familiar with the basic process, because it is the same one used to specify how Cocoa controls behave when the user resizes the window in which they are contained. You'll see this concept in action in just a bit.

Autosize is quick and easy but not appropriate for all applications. More complex interfaces have to handle autorotation in a different manner. For more complex views, you have two basic approaches. One approach is to manually reposition the objects in your view when notified that your view is rotating. The second approach is to actually design two different versions of your view in Interface Builder, one for portrait mode and a separate one for landscape mode. In both cases, you will need to override methods from UIViewController in your view's controller class.

Let's get started, shall we? We'll look at autosizing first.

Handling Rotation Using Autosize Attributes

Start a new project in Xcode, and call it Autosize. We're going to stick with the same view-based application template for this application. Before we design our view in Interface Builder, we need to tell the iPhone that our view supports autorotation. We do that by modifying the view controller class.

Specifying Rotation Support

Once your project is open in Xcode, expand the Classes folder, and single-click AutoSizeViewController.m. If you look at the code that's already there, you'll see that a method called shouldAutorotateToInterfaceOrientation: is already provided for you, courtesy of the template, but it's commented out. Uncomment it now by deleting the comment beginning and ending:

...
Specifying Rotation Support
// Override to allow orientations other than the default portrait // orientation. - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)
Specifying Rotation Support
interfaceOrientation { // Return YES for supported orientations return (interfaceOrientation == UIInterfaceOrientationPortrait); }
Specifying Rotation Support
...

This method is the system's way of asking your view controller if it's OK to rotate to a specific orientation. Four defined orientations correspond to the four general ways that the iPhone can be held:

  • UIInterfaceOrientationPortrait

  • UIInterfaceOrientationPortraitUpsideDown

  • UIInterfaceOrientationLandscapeLeft

  • UIInterfaceOrientationLandscapeRight

When the phone is changed to a new orientation, this method is called on the active view controller. The parameter interfaceOrientation will contain one of the four values in the preceding list, and this method needs to return either YES or NO to signify whether the application's window should be rotated to match the new orientation. Because every view controller subclass can implement this differently, it is possible for one application to support autorotation with some of its views but not with others.

Tip

Have you noticed that the defined system constants on iPhone are always designed so that values that work together start with the same letters? One reason why UIInterfaceOrientationPortrait, UIInterfaceOrientationPortraitUpsideDown, UIInterfaceOrientationLandscapeLeft, and UIInterfaceOrientationLandscapeRight all begin with UIInterfaceOrientation is to let you take advantage of Xcode's Code Sense feature. You've probably noticed that as you type Xcode frequently tries to complete the word you are typing. That's Code Sense in action. Developers cannot possibly remember all the various defined constants in the system, but you can remember the common beginning for the groups you use frequently. When you need to specify an orientation, simply type UIInterfaceOrientation (or even UIInterf) and then press the escape key to bring up a list of all matches (in Xcode's preferences, you can change that matching key from escape to something else). You can use the arrow keys to navigate the list that appears and make a selection by pressing the tab or return key. This is much faster than having to go look the values up in the documentation or header files.

The default implementation of this method looks at interfaceOrientation and returns YES only if it is equal to UIInterfaceOrientationPortrait, which limits this application to one orientation, effectively disabling autorotation.

If we wanted to enable rotation to any orientation, we'd simply change the method to return YES for any value passed in, like so:

- (BOOL)shouldAutorotateToInterfaceOrientation:
    (UIInterfaceOrientation)interfaceOrientation {
    return YES;
}

In order to support some but not all orientations, we have to look at the value of interfaceOrientation and return YES for those that we want to support and NO for those we don't. For example, to support portrait mode and landscape mode in both directions but not rotation to the upside down portrait mode, we could do this:

- (BOOL)shouldAutorotateToInterfaceOrientation:
    (UIInterfaceOrientation)interfaceOrientation {
    return (interfaceOrientation !=
        UIInterfaceOrientationPortraitUpsideDown);
}

Go ahead and change the shouldAutorotateToInterfaceOrientation: method to match the preceding version. As a general rule, UIInterfaceOrientationPortraitUpsideDown is discouraged by Apple, because if the phone rings while it is being held upside down, the phone is likely to remain upside down when it's answered.

Save, and then we'll look at setting autosize attributes in Interface Builder.

Designing an Interface with Autosize Attributes

In Xcode, expand the Resources folder, and double-click AutosizeViewController.xib to open the file in Interface Builder. One nice thing about using autosize attributes is that they require very little code. We do have to specify which orientations we support, as we just did in our view controller, but everything else we need to do in order to implement this technique will be done right here in Interface Builder.

To see how this works, drag six Round Rect Buttons from the library over to your view, and place them as we've done in Figure 5-2. Double-click each button, and assign a title to each one so we can tell them apart later. We've numbered ours from 1 to 6.

Adding six numbered buttons to the interface

Figure 5.2. Adding six numbered buttons to the interface

Save, and go back to Xcode. Let's see what happens now that we've specified that we support autorotation but haven't set any autosize attributes. Build and run. Once the iPhone simulator comes up, select Rotate Left from the Hardware menu, which will simulate turning the iPhone into landscape mode. Take a look at Figure 5-3. Oh, dear.

Well, that's not very useful, is it?

Figure 5.3. Well, that's not very useful, is it?

Most controls default to a setting that has them stay where they are in relation to the left side and top of the screen. There are some controls for which this would be appropriate. The top-left button, number 1, for example, is probably right where we want it—the rest of them, however, not so much.

Quit the simulator, and go back to Interface Builder.

Autosize Attributes

Single-click the top-left button on your view, and then press

Autosize Attributes

The size inspector allows you to set an object's autosize attributes. Figure 5-5 shows the part of the size inspector that controls an object's autosize attributes.

The box on the left in Figure 5-5 is where we actually set the attributes; the box on the right is a little animation that will show us how the object will behave during a resize. In the box on the left, the inner square represents the current object. If a button is selected, the inner square represents that button.

The size inspector allows you to set an object's autosize attributes.

Figure 5.4. The size inspector allows you to set an object's autosize attributes.

The red arrows inside the inner square represent the horizontal and vertical space inside the selected object. Clicking either arrow will change it from solid to dashed or from dashed back to solid. If the horizontal arrow is solid, the width of the object is free to change as the window resizes; if the horizontal arrow is dashed, the iPhone will try to keep the width of the object at its original value if possible. The same is true for the height of the object and the vertical arrow.

The Autosizing section of the size inspector

Figure 5.5. The Autosizing section of the size inspector

The four red "I" shapes outside the inner box represent the distance between the edge of the selected object and the same edge of the view that contains it. If the "I" is dashed, the space is flexible, and if it's solid red, the amount of space should be kept constant if possible.

Huh?

Perhaps this concept will make a little more sense if you actually see it in action. Take a look back at Figure 5-5, which represents the default autosize settings. These default settings specify that the object's size will remain constant as its superview is resized and that the distance from the left and top edges should also stay constant. If you look at the animation next to the autosize control, you can see how it will behave during a resize. Notice that the inner box stays in the same place relative to the left and top edges of the parent view as the parent view changes in size.

Try this experiment. Click both of the solid red "I" shapes (to the top and left of the inner box) so they become dashed and look like the ones shown in Figure 5-6.

With all dashed lines, your control floats in the parent and keeps its size.

Figure 5.6. With all dashed lines, your control floats in the parent and keeps its size.

With all the lines set to dashed, the size of the object will be kept the same, and it will float in the middle of the superview as the superview is resized.

Now, click the vertical arrow inside the box and the "I" shape both above and below the box so that your autosize attributes look like the ones shown in Figure 5-7.

This configuration allows the vertical size of our object to change.

Figure 5.7. This configuration allows the vertical size of our object to change.

With this configuration, we are indicating that the vertical size of our object can change and that the distance from the top of our object to the top of the window and the distance from the bottom of our object to the bottom of the window should stay constant. With this configuration, the width of the object wouldn't change, but its height would. Change the autosize attributes a few more times and watch the animation until you grok how different settings will impact the behavior when the view is rotated and resized.

Setting the Buttons' Autosize Attributes

Now, let's set the autosize attributes for our six buttons. Go ahead and see if you can figure them out. If you get stumped, take a look at Figure 5-8, which shows you the autosize attributes needed for each button in order to keep them on the screen when the phone is rotated.

Autosize attributes for all six buttons

Figure 5.8. Autosize attributes for all six buttons

Once you have the attributes set the same as Figure 5-8, save the nib, go back to Xcode, and build and run. This time, when the iPhone simulator comes up, you should be able to select Rotate Left or Rotate Right from the Hardware menu and have all the buttons stay on the screen (see Figure 5-9). If you rotate back, they should return to their original position. This technique will work for a great many applications.

The buttons in their new positions after rotating

Figure 5.9. The buttons in their new positions after rotating

In this example, we kept our buttons the same size, so now all of our buttons are visible and usable, but there is an awful lot of unused white space on the screen. Perhaps it would be better if we allowed the width or height of our buttons to change so that there will be less empty space on the interface? Feel free to experiment with the autosize attributes of these six buttons, and add some other buttons if you want. Play around until you feel comfortable with the way autosize works.

In the course of your experimentation, you're bound to notice that, sometimes, no combination of autosize attributes will give you exactly what you want. Sometimes, you are going to need to rearrange your interface more drastically than can be handled with this technique. For those situations, a little more code is in order. Let's take a look at that, shall we?

Restructuring a View When Rotated

In Interface Builder, single-click each of the buttons, and use the size inspector to change the w and h field to 125, which will set the width and height of the button to 125 pixels. When you are done, rearrange your buttons using the blue guidelines so that your view looks like Figure 5-10.

View after resizing all the buttons

Figure 5.10. View after resizing all the buttons

Can you guess what's going to happen this time when we rotate the screen? Well, assuming that you returned the buttons' autosize attributes back to those shown in Figure 5-8, what will happen isn't likely what we want to happen. The buttons are going to overlap and look like Figure 5-11, because there simply isn't enough height on the screen in landscape mode to accommodate three buttons that are 125 pixels tall.

Not exactly what we want

Figure 5.11. Not exactly what we want

We could accommodate this scenario using the autosize attributes by allowing the height of the buttons to change, but that's not going to make the best use of our screen real estate because it's going to leave a large gap in the middle of the screen. If there was room for six square buttons when the interface was in portrait mode, there should still be room for six square buttons in landscape mode, we just need to shuffle them around a bit. One way we can handle this is to specify new positions for each of the buttons when the view is rotated.

Declaring and Connecting Outlets

To change a control's attributes, we need an outlet that points to the object we want to change. As a result, we need to declare an outlet for each of the six buttons in order to rearrange them. Add the following code to AutosizeViewController.h:

#import <UIKit/UIKit.h>

@interface AutosizeViewController : UIViewController {
    UIButton *button1;
    UIButton *button2;
    UIButton *button3;
    UIButton *button4;
    UIButton *button5;
    UIButton *button6;
}
@property (nonatomic, retain) IBOutlet UIButton *button1;
@property (nonatomic, retain) IBOutlet UIButton *button2;
@property (nonatomic, retain) IBOutlet UIButton *button3;
@property (nonatomic, retain) IBOutlet UIButton *button4;
@property (nonatomic, retain) IBOutlet UIButton *button5;
@property (nonatomic, retain) IBOutlet UIButton *button6;
@end

Save this file, and go back to Interface Builder. Control-drag from the File's Owner icon to each of the six buttons, and connect them to the corresponding outlet. Once you've connected all six, save the nib, and pop back over to Xcode.

Moving the Buttons on Rotation

To move these buttons to make the best use of space, we need to override the method willAnimateRotationToInterfaceOrientation:duration: in AutosizeViewController.m. This method gets called automatically after a rotation has occurred but before the final rotation animations have occurred.

Note

The method willAnimateRotationToInterfaceOrientation:duration: is new with 3.0. In previous versions of the SDK, the method willAnimateSecondHalfOfRotationFromInterfaceOrientation:duration: can be used; however, the two-part animation used prior to 3.0 is considerably slower than the method we're using here, so you should avoid those methods unless you absolutely need to support older versions of the iPhone OS in your application.

Add the following code, and then we'll talk about what it's doing:

#import "AutosizeViewController.h"

@implementation AutosizeViewController
@synthesize button1;
@synthesize button2;
@synthesize button3;
@synthesize button4;
@synthesize button5;
@synthesize button6;

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)
    interfaceOrientation duration:(NSTimeInterval)duration {
    if (interfaceOrientation == UIInterfaceOrientationPortrait
        || interfaceOrientation ==
            UIInterfaceOrientationPortraitUpsideDown) {
        button1.frame = CGRectMake(20, 20, 125, 125);
        button2.frame = CGRectMake(175, 20, 125, 125);
        button3.frame = CGRectMake(20, 168, 125, 125);
        button4.frame = CGRectMake(175, 168, 125, 125);
        button5.frame = CGRectMake(20, 315, 125, 125);
        button6.frame = CGRectMake(175, 315, 125, 125);
}
else {
        button1.frame = CGRectMake(20, 20, 125, 125);
        button2.frame = CGRectMake(20, 155, 125, 125);
        button3.frame = CGRectMake(177, 20, 125, 125);
        button4.frame = CGRectMake(177, 155, 125, 125);
        button5.frame = CGRectMake(328, 20, 125, 125);
        button6.frame = CGRectMake(328, 155, 125, 125);
    }
}
- (BOOL)shouldAutorotateToInterfaceOrientation:
    (UIInterfaceOrientation)interfaceOrientation {
    return (interfaceOrientation !=
        UIInterfaceOrientationPortraitUpsideDown);
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Releases the view if it doesn't have a superview
    // Release anything that's not essential, such as cached data
}
- (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    self.button1 = nil;
self.button2 = nil;
    self.button3 = nil;
    self.button4 = nil;
    self.button5 = nil;
    self.button6 = nil;
    [super viewDidUnload];
}
- (void)dealloc {
    [button1 release];
    [button2 release];
    [button3 release];
    [button4 release];
    [button5 release];
    [button6 release];
    [super dealloc];
}

The size and position of all views, including controls such as buttons, are specified in a property called frame, which is a struct of type CGRect. CGRectMake is a function provided by Apple that lets you easily create a CGRect by specifying the x and y positions along with the width and height.

Note

The function CGRect() begins with the letters "CG," indicating that it comes from the Core Graphics framework. As its name implies, the Core Graphics framework contains code related to graphics and drawing. In earlier versions of the iPhone SDK, the Core Graphics framework was not included in Xcode iPhone project templates and had to be added manually. That step is no longer necessary, since the Core Graphics framework is automatically included when you use any of the iPhone Xcode templates.

Save this code. Now build and run to see it in action. Try rotating, and watch how the buttons end up in their new positions.

Swapping Views

There is one other way of handling autorotation, and it's an option you'll likely use only in the case of very complex interfaces. Moving controls to different locations, as we did in the previous section, can be a very tedious process, especially with a complex interface. Wouldn't it be nice if we could just design the landscape and portrait views separately and then swap them out when the phone is rotated?

Well, we can. But it's a moderately complex option. While controls on both views can trigger the same actions, we will have to have two completely distinct sets of outlets, one for each of the views, and that will add a certain complexity to our code. It is, by no means, an insurmountable amount of complexity, and there are times when this option is the best one. Let's try it out.

Create a new project in Xcode using the view-based application template again; we'll start working with other templates next chapter. Call this project Swap. The interface we'll be building in this application won't actually be complex enough to really justify the technique we're using. However, we want to make sure the process is clear, so we're going to use a fairly simple interface. When this application we're writing starts up, it will be in portrait mode. There will be two buttons, one on top of the other (see Figure 5-12).

The Swap application at launch

Figure 5.12. The Swap application at launch

When you rotate the phone, we'll swap in a completely different view to be shown for the landscape orientation. It will also feature two buttons with the exact same labels (see Figure 5-13), so the user won't know they're looking at two different views.

Similar but not the same

Figure 5.13. Similar but not the same

When the buttons are tapped, they will become hidden. This gives us a chance to show you some of the nuances of dealing with two sets of outlets. In a real application, there may be times when you want to hide or disable a button like this. As an example, you might create a button that kicked off a lengthy process and you didn't want the user tapping the same button again until that process had finished.

Determining Outlets

Because there are two buttons on each view that we're going to build and because an outlet can't point to more than one object, we need to declare four outlets, two for the landscape view buttons and two for the portrait view buttons. When using this technique, it becomes very important to put some thought into your outlet names to keep your code from becoming confusing to read.

But, oho! Is that somebody in the back saying, "Do we really need outlets for all these buttons? Since we're deactivating the button that was tapped, can't we just use sender instead?" And in a single-view scenario, that would be exactly the right way to go about it.

Think about this. What if the user taps the Foo button and then rotates the phone? The Foo button on the other view is a completely different button, and it will still be active, which isn't the behavior we want. We don't really want to advertise to the users that the object they're dealing with now isn't the same one they were dealing with a moment ago.

In addition to the outlets for the buttons, we need two more outlets to point to the two different versions of our view. When working with a single view only, our parent class's view property was all we needed. But, since we're going to be changing the value of view at runtime, we need to make sure we have a way to get to both views, hence the need for two UIView outlets.

Determining Actions

Our buttons need to trigger an action, so we're definitely going to need at least one action method. We're going to design a single action method to handle the pressing of any of the buttons, so we'll just declare a single buttonPressed: action in our view controller class.

Declaring Actions and Outlets

Add the following code to SwapViewController.h to create the outlets we'll need when we go to Interface Builder.

#import <UIKit/UIKit.h>
#define degreesToRadians(x) (M_PI * (x) / 180.0)
@interface SwapViewController : UIViewController {
    UIView    *landscape;
    UIView    *portrait;

    // Foo
    UIButton *landscapeFooButton;
    UIButton *portraitFooButton;

    // Bar
UIButton *landscapeBarButton;
UIButton *portraitBarButton;
}
@property (nonatomic, retain) IBOutlet UIView *landscape;
@property (nonatomic, retain) IBOutlet UIView *portrait;
@property (nonatomic, retain) IBOutlet UIButton *landscapeFooButton;
@property (nonatomic, retain) IBOutlet UIButton *portraitFooButton;
@property (nonatomic, retain) IBOutlet UIButton *landscapeBarButton;
@property (nonatomic, retain) IBOutlet UIButton *portraitBarButton;

-(IBAction)buttonPressed:(id)sender;
@end

This line of code:

#define degreesToRadians(x) (M_PI * (x) / 180.0)

is simply a macro to convert between degrees and radians. We'll use that in a few minutes when calling a function that requires radians as an input. Most people, including us, don't think in radians, so this macro will make our code much more readable by letting us specify angles in degrees instead of radians.

Everything else in this header should be familiar to you, so now that we have our outlets implemented, let's go to Interface Builder and build the two views we need. Double-click SwapViewController.xib in the Resources folder of the Groups & Files pane to open the file in Interface Builder.

Designing the Two Views

Ideally, what you're seeing in Interface Builder right now should feel very familiar to you. We'll need two views in our nib. We don't want to use the existing view that was provided as part of the template because its size can't be changed. Instead, we'll delete the default view and create two new ones.

Single-click the View icon, and press the Delete button. Next, drag over two Views from the library. After doing that, you'll have two icons labeled View. That might get a little confusing, so let's rename them to make it obvious what each one does.

To rename an icon in the nib's main window, you have to single-click the view to select it, wait a second or two, and then click the name of the icon. After another second, the name will become editable, and you can type the new name. Note that this trick works only in the icon view mode. Name one view Portrait and the other Landscape.

Now, control-drag from the File's Owner icon to the Portrait icon, and when the gray menu pops up, select the portrait outlet. Then, control-drag from File's Owner to the Landscape icon, and select the landscape outlet. Now control-drag a third time from File's Owner to Portrait, and select the view outlet to indicate which view should be shown at launch time.

Double-click the icon called Landscape, and press

Designing the Two Views

Control-drag from the File's Owner icon to the Foo button, and assign it to the landscapeFooButton outlet; then do the same thing to assign the Bar button to the landscapeBarButton outlet. Now, single-click the Foo button, and switch to the connections inspector by pressing

Designing the Two Views

Double-click the Portrait icon to open that view for editing. Drag two more Round Rect Buttons from the library, placing them one above the other this time. Again, make the size of each button 125 pixels wide and 125 pixels tall. Double-click the top button, and give it a title of Foo. Then, double-click the bottom button, and assign it a title of Bar. Control-drag from the File's Owner icon to the Foo button, and assign it to the portraitFooButton outlet. Control-drag from the File's Owner icon once again to the Bar button, and assign it to the portraitBarButton outlet. Click the Foo button, and drag from the Touch Up Inside event on the connections inspector over to the File's Owner icon, and select the buttonPressed: action. Repeat this connection with the Bar button.

Save the nib, and go back to Xcode.

Implementing the Swap and the Action

We're almost done now; we just need to put the code in place to handle the swap and the button taps. Add the code that follows to your SwapViewController.m file.

Note

This code listing does not show commented-out methods provided by the stub. Feel free to delete the commented-out methods that were already in your controller class.

#import "SwapViewController.h"

@implementation SwapViewController
@synthesize landscape;
@synthesize portrait;
@synthesize landscapeFooButton;
@synthesize portraitFooButton;
@synthesize landscapeBarButton;
@synthesize portraitBarButton;

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)
    interfaceOrientation duration:(NSTimeInterval)duration {
    if (interfaceOrientation == UIInterfaceOrientationPortrait) {
        self.view = self.portrait;
        self.view.transform = CGAffineTransformIdentity;
        self.view.transform =
            CGAffineTransformMakeRotation(degreesToRadians(0));
        self.view.bounds = CGRectMake(0.0, 0.0, 300.0, 480.0);
    }
    else if (interfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
        self.view = self.landscape;
        self.view.transform = CGAffineTransformIdentity;
        self.view.transform =
        CGAffineTransformMakeRotation(degreesToRadians(-90));
        self.view.bounds = CGRectMake(0.0, 0.0, 460.0, 320.0);
    }
    else if (interfaceOrientation ==
                       UIInterfaceOrientationPortraitUpsideDown) {
        self.view = self.portrait;
        self.view.transform = CGAffineTransformIdentity;
        self.view.transform =
            CGAffineTransformMakeRotation(degreesToRadians(180));
        self.view.bounds = CGRectMake(0.0, 0.0, 300.0, 480.0);
    }
    else if (interfaceOrientation ==
            UIInterfaceOrientationLandscapeRight) {
        self.view = self.landscape;
        self.view.transform = CGAffineTransformIdentity;
        self.view.transform =
            CGAffineTransformMakeRotation(degreesToRadians(90));
        self.view.bounds = CGRectMake(0.0, 0.0, 460.0, 320.0);
    }
}
-(IBAction)buttonPressed:(id)sender {

    if (sender == portraitFooButton || sender == landscapeFooButton) {
        portraitFooButton.hidden = YES;
        landscapeFooButton.hidden = YES;
    } else {
        portraitBarButton.hidden = YES;
        landscapeBarButton.hidden = YES;
}
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)
    interfaceOrientation {
    return YES;
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Releases the view if it doesn't have a superview
    // Release anything that's not essential, such as cached data
}
- (void)viewDidUnload {
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    self.landscape = nil;
    self.portrait = nil;
    self.landscapeFooButton = nil;
    self.landscapeBarButton = nil;
    self.portraitFooButton = nil;
    self.portraitBarButton = nil;
    [super viewDidUnload];
}
- (void)dealloc {
    [landscape release];

    [portrait release];
    [landscapeFooButton release];
    [portraitFooButton release];
    [landscapeBarButton release];
    [portraitBarButton release];

    [super dealloc];
}

@end

The first method in our new code is called willAnimateRotationToInterfaceOrientation:duration:. This is a method from our superclass that we've overridden that gets called as the rotation begins but before the rotation actually happens. Actions that we take in this method will be animated as part of the first half of the rotation animation.

In this method, we look at the orientation that we're rotating to and set the view property to either landscape or portrait, as appropriate for the new orientation. We then call CGAffineTransformMakeRotation, part of the Core Graphics framework, to create a rotation transformation. A transformation is a mathematical description of changes to an object's size, position, or angle. Ordinarily, iPhone takes care of setting the transform value automatically when the phone is rotated. However, when we swap in our new view here, we have to make sure that we give it the correct value so as not to confuse the iPhone. That's what willAnimateRotationToInterfaceOrientation:duration: is doing each time it sets the view's transform property. Once the view has been rotated, we adjust its frame so that it fits snugly into the window at the current orientation.

Next up is our buttonPressed: method, and there shouldn't be anything too surprising there. We look at the button that was tapped, hide it, and then hide the corresponding button on the other view.

You should be comfortable with everything else we wrote in this class. The new shouldAutorotateToInterfaceOrientation: method simply returns YES to tell the iPhone that we support rotation to any orientation, and the code added to the dealloc method is simple memory cleanup.

Now, we're ready to compile it and try it. Note that if you accidentally clicked both buttons, the only way to bring them back is to quit the simulator and rerun the project. Don't use this approach in your own applications.

Rotating Out of Here

In this chapter, you got to try out three completely different approaches to supporting autorotation in your applications. You learned about autosizing attributes and how to restructure your views, in code, when the phone rotates. You saw how to swap between two completely different views when the phone rotates, and you learned how to link new frameworks into your project.

In this chapter, you also got your first taste of using multiple views in an application by swapping between two views from the same nib. In the next chapter, we're going to start looking at true multiview applications. Every application we've written so far has used a single view controller and all except the last used a single content view. A lot of complex iPhone applications such as Mail and Contacts, however, are only made possible by the use of multiple views and view controllers, and we're going to look at exactly how that works in Chapter 6.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.15.84