IOS 4 iPad Rotation, View Resizing and Layout Handling (Xcode 4)
Previous | Table of Contents | Next |
Understanding iPad iOS 4 Views, Windows and the View Hierarchy (Xcode 4) | Creating an iOS 4 iPad Multiview Application using the Tab Bar (Xcode 4) |
Learn SwiftUI and take your iOS Development to the Next Level |
If you have ever developed an application using a user interface toolkit for a windowing based desktop system (be it Windows, Mac OS X, Linux, Qt or GTK+) you will likely be familiar with the need to design your user interfaces such that they resize appropriately when the user enlarges or reduces the size of a window. This generally involves deciding which visual components of a window move and which ones resize to accommodate the containing window.
Obviously iOS based devices such as the iPad and iPhone do not have windows that the user can resize in the conventional sense that we are used to in the world of desktop applications. The iPad is, however, sensitive to physical rotation, providing the option for a running application to be displayed in either portrait or landscape mode. Given this capability, it is essential that an iPad application user interface layout adapt accordingly should you decide to support different device orientations within your application.
In this chapter we will look at the mechanism for enabling rotation and modifying the layout of user interfaces within an application. In the course of this chapter we will be talking about concepts such as the view, superviews, subviews and view hierarchies. If these concepts are unfamiliar to you we recommend first reading the chapter entitled Understanding iPad iOS 4 Views, Windows and the View Hierarchy (Xcode 4) before proceeding.
Setting up the Example
In the remainder of this chapter we will work through a self contained example. To set up this example, start Xcode and create a new iOS View-based iPad project named layout.
From within Xcode project navigator panel, select the layoutViewController.xib file and create a simple interface consisting of a single button object.
iPad Screen Resolution
In terms of screen resolution, for both the first generation iPad and the more recent iPad 2 the overall window size is 1024 pixels high by 768 pixels wide in portrait mode and, of course, 768 pixels high and 1024 pixels wide in landscape. Keep in mind that if the application displays the standard status bar the actual display space available will be reduced accordingly.
Enabling Rotation
The iOS app templates for the iPad provided by Xcode automatically assume that your application will sensibly support rotation. Despite this default behavior, it is not a given that every app should support rotation. If there is nothing to be gained in terms of the user experience by supporting both landscape and portrait layouts of your iPad application’s user interface do not feel compelled to implement this capability.
When the iPad detects that it has been rotated to a new orientation a call is made to the shouldAutorotateToInterfaceOrientation method of the view controller instance. In order to control rotation support it is necessary to modify this method in the currently active view controller for your application (in the case of our example this is the layoutViewController class).
Fortunately, Xcode has placed a template method in our layoutViewController.m:
- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation { // Return YES for supported orientations Return YES; }
The method takes as a single argument the new orientation value and returns either true or false depending on whether the application will allow the new orientation change to take place. In the template example above, the method only returns true if the orientation passed to the method is portrait (in other word it matches the value represented by UIInterfaceOrientationPortrait). The full range of supported orientations that can potentially be passed through to this method are as follows:
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeRight
- UIInterfaceOrientationLandscapeLeft
In order to restrict support to only portrait orientation, for example, simply modify the code in this method as follows:
- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation { // Return YES for supported orientations return (interfaceOrientation == UIInterfaceOrientationPortrait); }
Testing Rotation Behavior
If you are testing your iOS applications natively on a physical iPad device, you can simply rotate the device to test the behavior of your application. If, on the other hand, you are running the application in the iOS Simulator, you can simulate rotation by selecting the Hardware -> Rotate Left and Hardware -> Rotate Right menu options.
Configuring View Autosizing
In order to make the handling of orientation changes a little easier to implement, the UIView class from which visual components are derived provides a feature called Autosizing. Autosizing allows us to define on a view by view basis how a subview will be resized and positioned relative to the superview in which it is contained. These settings can be defined programmatically in code or from within the Size Inspector of the Interface Builder tool during the user interface design process.
In order to demonstrate autosizing, select the layoutViewController.xib file to load it into Interface Builder, add an additional button to the interface and reposition and label both buttons so that they appear as outlined in the following figure:
In order to gain an appreciation of the importance of handling orientation changes, save the design and build and run the application. Once the simulator appears, rotate the device by 90⁰. Because rotation is enabled by default, the view will rotate to the landscape orientation:
Here we see a classic example of the implications of enabling rotation in an application without taking the additional steps to reformat the view to match the change in orientation. Having rotated the device, only the top button is visible and it is still centered assuming that the device is in portrait mode. The second button is similarly positioned, resulting in it being outside the viewable area of the screen.
In order to ensure the correct layout of the user interface it is necessary to make some changes to the autosize settings of the two buttons. The current settings for a view are accessed from within Interface Builder by selecting the component and displaying the Size Inspector (View -> Utilities -> Size Inspector). The following figure shows the Size Inspector for the top button in our user interface:
The section of this window that is of interest to us is the area labeled Autosizing and in particular the white box on the left. The outer box represents the superview and the inner box represents the subview that is contained within the superview. Any autosizing settings that are configured on a subview are relative to the superview. Within the subview box are two intersecting lines with arrows at each end. These vertical and horizontal lines indicate whether the height and width of the selected view object should increase or decrease in proportion to size changes in the corresponding superview. A faint dotted line indicates that the currently selected view object is not to resize in relation to the superview. A solid red line, on the other hand, indicates that it should resize proportionally to the parent view. The graphic to the right of the white box provides a visual representation of how the view will behave based on the current settings. The red box represents the subview and the white box represents the containing superview. Click on the horizontal line to change it to solid red and see how the width of the red box representing the button grows and shrinks as the containing superview size changes. Similarly, enable resizing for the height of the button and observe how the height of the button also now changes proportionally to the superview:
These two settings correspond to the UIViewAutoResizingFlexibleHeight and UIViewAutoResizingFlexibleWidth autoresizing mask properties.
The lines separating the four sides of the inner subview and the outer superview boxes are referred to as struts and control what happens to the margin between each side of the subview and the corresponding sides of the superview when the latter resizes as the result of an orientation change. When a strut is enabled (indicated by the solid red lines) the corresponding margin remains fixed when the superview resizes. In the above example the top and left hand margins are fixed which explains why the position of the top left hand corner of the button does not move in the graphical representation when the superview resizes. When the strut is turned off (symbolized by the faint dotted lines) the margin changes proportionally to the change in size of the superview. Doing so with our example button will cause the subview to remain centered in the superview as illustrated in the following figure:
These struts correspond to the following autoresizing mask values:
- UIViewAutoResizingFlexibleLeftMargin
- UIViewAutoResizingFlexibleRightMargin
- UIViewAutoResizingFlexibleTopMargin
- UIViewAutoResizingFlexibleBottomMargin
When multiple autosize attributes are set on a single axis (for example flexible left and right margins combined with flexible width) the resizing is distributed evenly over each dimension.
In order to modify the resize behavior of the buttons in our example application we want to make sure that both buttons remain centered relative to the width of the superview, so we need to turn off all struts on both buttons. This will also have the effect of making the position of the bottom button relative to the bottom of the superview flexible so that it will be visible after a switch to landscape mode. Finally we want the width of the buttons to change in proportion to the width of the superview so need to turn on horizontal resizing for both buttons and vertical resizing off. With these changes made, compiling and running the application should result in the correct layout appearing when the device is rotated:
Coding Layout and Size Changes
Whilst the autosizing features will work for many situations there are some limitations. It is quite possible for example that you will want to change the entire layout of your user interface depending on whether the device is in portrait or landscape orientation. This might involve not only changing the size of view elements but also moving them to specific locations on the screen. Clearly this is beyond the capabilities of autosizing. A useful approach to this situation involves intercepting the rotation of the device and changing the position and dimensions of the user interface components before the screen is redrawn. In order to achieve this we will need to override the willAnimateRotationToInterfaceOrientation method of the UIViewController class. This is the method that gets called just before the user interface begins to rotate.
Learn SwiftUI and take your iOS Development to the Next Level |
#import <UIKit/UIKit.h> @interface layoutViewController : UIViewController { UIButton *firstButton; UIButton *secondButton; } @property (nonatomic, retain) IBOutlet UIButton *firstButton; @property (nonatomic, retain) IBOutlet UIButton *secondButton; @end
Next, the layoutViewContoller.m implementation file needs to be edited to synthesize accesss to the outlets and to override the willAnimateRotationToInterfaceOrientation method:
#import "layoutViewController.h" @implementation layoutViewController @synthesize firstButton; @synthesize secondButton; - (void)willAnimateRotationToInterfaceOrientation: (UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft || toInterfaceOrientation == UIInterfaceOrientationLandscapeRight) { firstButton.frame = CGRectMake(20, 20, 482, 708); secondButton.frame = CGRectMake(522, 20, 482, 708); } else { firstButton.frame = CGRectMake(20, 20, 728, 472); secondButton.frame = CGRectMake(20, 512, 728, 472); } } . . - (void)viewDidUnload { // Release any retained subviews of the main view. // e.g. self.myOutlet = nil; self.firstButton = nil; self.secondButton = nil; } - (void)dealloc { [firstButton release]; [secondButton release]; [super dealloc]; } @end
As mentioned previously, the willAnimateRotationToInterfaceOrientation method gets called just before the user interface rotation takes place. Passed through as an argument to this method is the new orientation of the device in the form of the toInterfaceOrientation variable. Within the body of the method we identify if the device has rotated to a landscape or portrait orientation. Using the CGRectMake method (which takes x, y, width and height as arguments) we create a CGRect structure containing the new co-ordinates and dimensions for each button and assign them to the frame property of the button instances. When the method returns, the system will then proceed to draw the buttons using the new frame information that was configured in the overridden method.
With the appropriate code changes made, select the layoutViewController.xib file and modify the user interface such that appears as follows:
The size and co-ordinates that we specified in our method are designed to position the buttons side by side when the device rotates into a landscape orientation and then back to the above layout when it returns to portrait. Before we try this out, however, we need to set up the connections from the buttons to the two outlets we created in the view controller. To achieve this, hold down the control key and click and drag from the File’s Owner icon to the top button. Release at this point and select firstButton from the resulting menu. Repeat this for the other button, selecting secondButton from the menu and then build and run the application. Once the application is running, rotate the simulator window by selecting the Hardware -> Rotate Left menu option. The device window should rotate and the buttons appear side by side:
Rotating the device back to portrait orientation should trigger the second part of the if .. else .. construct in the overridden method, thereby returning the buttons to the original layout.
Learn SwiftUI and take your iOS Development to the Next Level |
Previous | Table of Contents | Next |
Understanding iPad iOS 4 Views, Windows and the View Hierarchy (Xcode 4) | Creating an iOS 4 iPad Multiview Application using the Tab Bar (Xcode 4) |