Some applications display
entirely different user interfaces depending on the iPhone’s
orientation. The iPod application, for example, displays a scrolling
list of songs in portrait mode and a “flickable” Cover Flow view of
albums when held in landscape. You, too, can create applications that
dramatically alter their appearance by simply switching between views
when the phone is rotated. Our last tutorial this hour will be short,
sweet, and give you the flexibility to manage your landscape and
portrait views all within the comfort of Interface Builder.
Setting Up the Project
Create a new project named Swapper
using the View-Based iPhone Application template. Although this
includes a single view already (which we’ll use for the default portrait
display), we need to supplement it with a second landscape view.
Adding Outlets and Properties
This application won’t implement any real user interface elements, but we will need to access two UIView instances programmatically.
Open the SwapperViewController.h file and edit it to include IBOutlet declarations and @property directives for portraitView, and landscapeView. The result should match Listing 1.
Listing 1.
#import <UIKit/UIKit.h>
@interface ReframeViewController : UIViewController {
IBOutlet UIView *portraitView;
IBOutlet UIView *landscapeView;
}
@property (nonatomic,retain) UIView *portraitView;
@property (nonatomic,retain) UIView *landscapeView;
@end
|
You know the routine. Save your changes, then edit SwapperViewController.m implementation file, adding the appropriate @synthesize directives immediately following the @implementation line:
@synthesize portraitView;
@synthesize landscapeView;
Releasing the Objects
Edit the dealloc method in ReframeViewController.m to release the two views we’ve retained.
- (void)dealloc {
[landscapeView release];
[portraitView release];
[super dealloc];
}
Enabling Rotation
Once more, we need to enable
rotation in order for the iPhone to properly react when it changes
orientation. Unlike the previous two implementations of shouldAutorotateToInterfaceOrientation:, this time, we’ll only allow rotate between the two landscape modes and upright portrait.
Update ReframeViewController.m to include the implementation in Listing 2.
Listing 2.
- (BOOL)shouldAutorotateToInterfaceOrientation:
(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationPortrait ||
interfaceOrientation == UIInterfaceOrientationLandscapeRight ||
interfaceOrientation == UIInterfaceOrientationLandscapeLeft);
}
|
The incoming interfaceOrientation parameter is compared to the UIInterfaceOrientationPortrait, UIInterfaceOrientationLandscapeRight, and UIInterfaceOrientationLandscapeLeft.
If it matches, rotation is allowed. As you might surmise, this covers
all of the possible orientations except upside-down portrait (UIInterfaceOrientationPortraitUpsideDown)—which we’ll disable this time around.
Adding a Degree to Radians Constant
Later
in this exercise, we’re going to need to call a special Core Graphics
method to define how to rotate views. The method requires a value to be
passed in radians rather than degrees. In other words, rather than
saying we want to rotate the view 90 degrees, we have to tell it we want
to rotate 1.57 radians. To help us handle the conversion, we will
define a constant for the conversion factor. Multiplying degrees by the
constant gets us the resulting value in radians.
To define the constant, add the following line after the #import line in SwapperViewController.m:
#define deg2rad (3.1415926/180.0)
Creating the Interface
When swapping views, the sky is
the limit for the design. You build them exactly as you would in any
other application. The only difference is that if you have multiple
views handled by a single view controller, you’ll need to define outlets
that encompass all the interface elements.
In this example, we’ll just demonstrate how to swap views, so our work in Interface Builder will be a piece of cake.
Creating the Views
Open SwapperViewController.xib and drag a new instance of the UIView object from the Library to the Document window. Don’t put the UIView inside the existing view. It should be added as a new separate view within the XIB file, as shown in Figure 1.
Now,
open each of the views and add a label to tell them apart. We’ve set
the background color of each view to be different as well. You’re
welcome to add other controls and design the view as you see fit. Our
finished landscape and portrait views are seen in Figure 2.
Did you Know?
To differentiate between the
two views within the Interface Builder Document window, you can edit the
name of each view just like you would edit a filename in the Finder!
Connecting the Outlets
To finish up in Interface
Builder, Control-drag from the File’s Owner icon to each of the views.
Connect the portrait view to the portraitView outlet, as shown in Figure 3, and the landscape view to landscapeView.
Save the XIB file and return to Xcode to finish up the Swapper implementation.
Implementing the View-Swapping Logic
For the most part,
swapping views is actually easier than the reframing logic we
implemented in the last project—with one small exception. Even though we
designed one of the views to be in landscape view, it doesn’t “know”
that it is supposed to be displayed in a landscape orientation. Before
we can display it, we need to rotate it and define how big it is.
Understanding the View-Rotation Logic
Each time we change
orientation, we’ll go through three steps—swapping the view, rotating
the view to the proper orientation through the transform property, and setting the view’s origin and size via the bounds property.
For example, assume we’re rotating to landscape right orientation:
1. | First, we swap out the view by assigning self.view, which contains the current view of the view controller, to the landscapeView
instance variable. If we left things at that, the view would properly
switch, but it wouldn’t be rotated into the landscape orientation. A
landscape view displayed in a portrait orientation isn’t a pretty thing!
For example:
|
2. | Next, to deal with the rotation, we define the transform
property of the view. This property determines how the view will be
altered before it is displayed. To meet our needs, we’ll have to rotate
the view 90 degrees to the right (for landscape right), –90 degrees to
the left (for landscape left), and 0 degrees for portrait. As luck would
have it, the Core Graphics C function, CGAffineTransformMakeRotation(), accepts a rotation value in radians, and provides an appropriate structure to the transform property handle the rotation. For example:
self.view.transform=CGAffineTransformMakeRotation(deg2rad*(90));
By the Way
Note that we multiply the rotation in degrees (90, –90, and 0) by the constant deg2rad that we defined earlier so that CGAffineTransformMakeRotation() has the radian value it expects.
|
3. | The final step is to set the bounds property of the view. The bounds
define the origin point and size of the view after it undergoes the
transformation. A portrait iPhone view has an original point of 0,0 and a
width and height of 320.0 and 460.0. A landscape view has the same
origin point (0,0) but a width of 480.0, and a height of 300.0. As with
the frame property, we can set bounds using the results of CGRectMake(). For example:
self.view.bounds=CGRectMake(0.0,0.0,480.0,320.0);
|
What Happened to 320×480? Where Are the Missing 20 Points?
The missing 20 points are
taken up by the iPhone status bar. When the phone is in portrait mode,
the points come off of the large (480) dimension. In landscape
orientation, however, the status bar eats up the space on the smaller
(320) dimension.
Now that you understand the steps, let’s take a look at the actual implementation.
Writing the View-Rotation Logic
As with the Reframing project, all this magic happens within a single method: willRotateToInterfaceOrientation:toInterfaceOrientation:duration:.
Open the SwapperViewController.m implementation file and implement the method as shown in Listing 2.
Listing2.
1: -(void)willRotateToInterfaceOrientation:
2: (UIInterfaceOrientation)toInterfaceOrientation
3: duration:(NSTimeInterval)duration {
4:
5: [super willRotateToInterfaceOrientation:toInterfaceOrientation
6: duration:duration];
7:
8: if (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight) {
9: self.view=landscapeView;
10: self.view.transform=CGAffineTransformMakeRotation(deg2rad*(90));
11: self.view.bounds=CGRectMake(0.0,0.0,480.0,320.0);
12: } else if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
13: self.view=landscapeView;
14: self.view.transform=CGAffineTransformMakeRotation(deg2rad*(-90));
15: self.view.bounds=CGRectMake(0.0,0.0,480.0,320.0);
16: } else {
17: self.view=portraitView;
18: self.view.transform=CGAffineTransformMakeRotation(0);
19: self.view.bounds=CGRectMake(0.0,0.0,300.0,460.0);
20: }
21: }
|
Lines 5–6 pass the interface rotation message up to the parent object so that it can react appropriately.
Lines 8–11 handle rotation to
the right (landscape right). Lines 12–15 deal with rotation to the left
(landscape left). Finally, lines 16–19 configure the view for the
default orientation, portrait.
Save the implementation file,
and then choose Build and Run to test the application. As you rotate the
phone or the iPhone simulator, your views should be swapped in and out
appropriately.
Did you Know?
Although we used an if-then-else statement in this example, you could easily use a switch structure instead. The toInterfaceOrientation parameter and orientation constants are integer values, which means they can be evaluated directly in a switch statement.