Setting Up the Project
Create a new view-based
iPhone application project and name it Reframe.
Adding Outlets and Properties
In this exercise, you’ll manually resize and reposition three UI elements: two buttons (UIButton) and one label (UILabel).
Because we’ll need to access these programmatically, we’ll first edit
the interface and implementation files to include outlets and properties
for each of these objects.
Open the ReframeViewController.h file, and edit it to include IBOutlet declarations and @property directives for buttonOne, buttonTwo, and viewLabel, as shown in Listing 1.
Listing 1.
#import <UIKit/UIKit.h>
@interface ReframeViewController : UIViewController {
IBOutlet UIButton *buttonOne;
IBOutlet UIButton *buttonTwo;
IBOutlet UILabel *viewLabel;
}
@property (nonatomic,retain) UIButton *buttonOne;
@property (nonatomic,retain) UIButton *buttonTwo;
@property (nonatomic,retain) UILabel *viewLabel;
@end
|
Save your changes, then edit ReframeViewController.m, adding the appropriate @synthesize directives for buttonOne, buttonTwo, and viewLabel, immediately following the @implementation line:
@synthesize buttonOne;
@synthesize buttonTwo;
@synthesize viewLabel;
Releasing the Objects
Edit the dealloc method in ReframeViewController.m to release the label and button we’ve retained:
- (void)dealloc {
[buttonOne release];
[buttonTwo release];
[viewLabel release];
[super dealloc];
}
Enabling Rotation
Even when you aren’t going to
be taking advantage of the autoresizing/autorotation capabilities in
Interface Builder, you must still enable rotation in the shouldAutorotateToInterfaceOrientation: method. Update ReframeViewController.m to include the implementation you added in the earlier lesson:
- (BOOL)shouldAutorotateToInterfaceOrientation:
(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
With the exception of the logic
to detect and handle the reframing of our interface elements, that
finishes the setup of our application. Now, let’s create the default
view that will be displayed when the application first loads.
Creating the Interface
We’ve now reached the point in
the project where the one big caveat of reframing becomes
apparent—keeping track of interface coordinates and sizes. Although we
have the opportunity to lay out the interface in Interface Builder, we’ll need to make note of where all of the different elements are.
Why? Because each time the screen changes rotation, we’ll be resetting
their positions in the view. There is no “return to default positions”
method, so even the initial layout we create will have to be coded using
x,y coordinates and sizes so that we can call it back up when needed.
Let’s begin.
Open the ReframeViewController.xib file and its view in Interface Builder.
Disabling Autosizing
Before doing anything
else, click within the view to select it, and then open the Attributes
Inspector (Command+1). Within the View settings section, uncheck the
Autoresize Subviews check box, as shown in Figure 1.
If you forget to disable
the autoresize attribute in the view, your application code will
manually resize/reposition the UI elements at the same time the iOS
tries to do it for you. The result can be a jumbled mess and several
minutes of head scratching!
Laying Out the View... Once
Your next step is to lay out
the view exactly as you would in any other app. Recall that we added
outlets for two buttons and a label; using the Library, click and drag
those elements into your view now. Title the label Reframing and position it at the top of the view. Set the button titles to Button 1 and Button 2 and place them under the label. Your final layout should resemble Figure 2.
When you have the layout you
want, you’ll need to determine what the current frame attributes are
for each of your objects. You can get this information from the Size
Inspector.
Start by selecting the
label and opening the Size Inspector (Command+3). Click the dot in the
upper-right corner of the Size & Position settings to set it as the
origin point for measuring coordinates. Next, make sure that the
drop-down menu is set to Frame, as shown in Figure 3.
Now, write down the X, Y, W (width), and H (height) attributes for the label. This represents the frame
property of the object within your view. Repeat this process for the
two buttons. You should end up with a list of four values for each of
your objects. Our frame values are listed here for comparison:
Label: X: 95.0, Y: 15.0, W: 130.0, H: 20.0
Button 1: X: 20.0, Y: 50.0, W: 280.0, H: 190.0
Button 2: X: 20.0, Y: 250.0, W: 280.0, H: 190.0
Before doing anything else, save your view! We’ll be making some changes in the next section that you’ll want to undo.
Did you Know?
If you want to follow our example exactly,
feel free to enter the X, Y, W, and H point values we’ve provided for
the values of your objects in the Size Inspector. Doing this will resize
and reposition your view elements to match the one here!
Laying Out the View... Again
Your next step is to lay out the
view exactly as you would in any other app. Wait a sec; this sounds
very familiar. Why do we want to lay out the view again? The answer is
simple. We’ve collected all of the frame
properties that we’ll need to configure the portrait view, but we
haven’t yet defined where the label and buttons will be in the landscape
view. To get this information, we’ll lay the view out again in
landscape mode, collect all the location and size attributes, and then
discard those changes.
The process is identical
to what you’ve already done—the only difference is that you need to
click the rotate arrow in Interface Builder to rotate the view. Once
rotated, resize and reposition all the existing elements so that they
look the way you want them to appear when in landscape orientation on
your iPhone. Because we’ll be setting the positions and sizes
programmatically, the sky is the limit for how you arrange the display.
To follow our example, stretch Button 1 across the top of the view and
Button 2 across the button. Position the Reframing label in the middle,
as shown in Figure 4.
As before, when the view is
exactly as you want it to appear, use the Size Inspector (Command+3) to
collect the x,y coordinates and height and width of all of the UI
elements. Our landscape frame values are provided here for comparison:
Label: X: 175.0, Y: 140.0, W: 130.0, H: 20.0
Button 1: X: 20.0, Y: 20.0, W: 440.0, H: 100.0
Button 2: X: 20.0, Y: 180.0, W: 440.0, H: 100.0
When you’ve collected the landscape frame attributes, undo the changes by using Edit, Undo (Command+Z), or close ReframeViewController.xib (not saving the changes).
Connecting the Outlets
Before jumping back into Xcode to finish the implementation, we still need to connect the label and buttons to the outlets (viewLabel, buttonOne, and buttonTwo)
that we added at the start of the project. Open
ReframeViewController.xib again (if you closed it in the last step), and
make sure that the view window and Document window are both visible
onscreen.
Next, Control-drag from the File’s Owner icon to the label and two buttons, choosing viewLabel, buttonOne, and buttonTwo as appropriate. Figure 5 demonstrates the connection from the Reframing label to the viewLabel outlet.
Save the XIB file and return to Xcode to finish up the project!
Implementing the Reframing Logic
Now that you’ve built the view
and captured the values for the label and button frames in both
portrait and landscape views, the only thing that remains is detecting
when the iPhone is ready to rotate and reframing appropriately.
The willRotateToInterfaceOrientation:toInterfaceOrientation:duration: method is invoked automatically whenever the iPhone interface needs to rotate. We’ll compare toInterfaceOrientation
parameter to the different iPhone orientation constants to identify
whether we should be using the frames for a landscape or portrait view.
Open the ReframeViewController.m file in Xcode and add the method shown in Listing 2.
Listing 2.
1: -(void)willRotateToInterfaceOrientation:
2: (UIInterfaceOrientation)toInterfaceOrientation
3: duration:(NSTimeInterval)duration {
4:
5: [super willRotateToInterfaceOrientation:toInterfaceOrientation
6: duration:duration];
7:
8: if (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight ||
9: toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) {
10: viewLabel.frame=CGRectMake(175.0,140.0,130.0,20.0);
11: buttonOne.frame=CGRectMake(20.0,20.0,440.0,100.0);
12: buttonTwo.frame=CGRectMake(20.0,180.0,440.0,100.0);
13: } else {
14: viewLabel.frame=CGRectMake(95.0,15.0,130.0,20.0);
15: buttonOne.frame=CGRectMake(20.0,50.0,280.0,190.0);
16: buttonTwo.frame=CGRectMake(20.0,250.0,280.0,190.0);
17: }
18: }
|
The logic is straightforward.
To start, we need to make sure that any parent objects are notified that
the view is about to rotate. So, in lines 5–6, we pass the same willRotateToInterfaceOrientation:toInterfaceOrientation:duration: message to the parent object super.
In lines 8–12 we compare the incoming parameter toInterfaceOrientation
to the landscape orientation constants. If either of these match, we
reframe the label and buttons to their landscape layouts by assigning
the frame property to the output of the CGRectMake() function. The input to CGRectMake() is nothing more than the X, Y, W, and H values we collected earlier in Interface Builder.
Lines
13–16 handle the “other” orientation: portrait orientation. If the
iPhone isn’t rotated into a landscape orientation, the only other
possibility is portrait. Again, the frame values that we assign are
nothing more than the values identified using the Size Inspector in
Interface Builder.
And with this simple
method, the Reframe project is now complete! You now have the capability
of creating interfaces that rearrange themselves when users rotate
their phones.
We
still have one more approach to cover. In this final project, instead
of rearranging a view in landscape orientation, we’ll replace the view
altogether!