We will create a custom view controller. The Notification Center project was able to detect a change of orientation; now we will also able to make the text run in the correct direction.
A view under the control of an iOS 7 view controller occupies the full
screen;
see the
Transition
Guide.
I tried to turn this off with the
edgesForExtendedLayout
property of the view controller,
but the view persisted in being 568 pairs of pixels high.
Fortunately,
however,
the iOS 7 status bar is transparent.
I merely started the text in the text view with an empty line
to prevent any text from being written behind the status bar.
Note that the
frame
and
bounds
properties disagree in the landscape orientations.
main.m
PortraitAppDelegate
ViewController
View
To create class
ViewController
,
highlight the
Portrait
folder in the Project Navigator.
File →
New →
File…
Choose a template for your new file: Objective-C class
Next
Class: ViewController
Subclass of: UIViewController
Uncheck With XIB for user interface.
Next
Create
To create class
View
,
highlight the
Portrait
folder in the Project Navigator.
File →
New →
File…
Choose a template for your new file: Objective-C class
Next
Class: View
Subclass of: UITextView
Uncheck With XIB for user interface.
Next
Create
To allow the app to run in the upside down orientation,
select the name of the project in the Xcode Project Navigator.
In the big center pane of Xcode, scroll down to
Deployment Info →
Deice Orientation
and check all four checkboxes.
Add the
supportedInterfaceOrientations
method to the view controller.
See
Handling
View Rotations.
This app redraws itself by calling the
drawRect:
method of the
View
whenever the iPhone is moved to a new orientation:
portrait,
landscape,
portrait upside down,
etc.
The machinery that calls
drawRect:
interfaceOrientation
.
Whenever the controller senses that this property has changed,
the controller will ensure that someone calls
the
drawRect:
method of the view that is controlled by the controller.
In the future,
I promise that the controller will respond to changes in something much
more substantial than
interfaceOrientation
,
which is one of the controller’s own properties.
The controller will respond to changes in a separate object called the
model,
which can be a formidable repository of data.
When the data in the model changes,
the controller will see to it that
drawRect:
is called.
Data will also flow in the opposite direction. The controller will sense when the buttons and sliders in the view have received data from the user. The controller will then transmit this data to the model. For the present, however, the flow of data is only one way, from the controller down to the view.
We saw an example where a little yellow view was added to a big white view. We could have provided a view controller for that big white view but not for the little yellow view. A little view that has been added to another view does not have a view controller of its own.
Instead of creating a view and a window, this app delegate creates a window and a view controller. The view controller will create the view. In fact, the controller will be the view’s lifelong guardian angel. At any given time, a controller controls only one view.
The application delegate has a propery variable named
window
that points to the window.
The window has a property named
rootViewController
that points to the controller.
The controller has a property named
view
that points to the view.
Make a diagram of this on a piece of paper.
The controller’s
view
is created by the
loadView
method of the controller.
loadView
creates the view and stores the address of the view into the
view
property of the controller.
The code in
loadView
is familiar code that until now has been in
application:didFinishLaunchingWithOptions:
.
loadView
is called automatically the first time the value of the controller’s
view
property is used.
For example,
the method
application:didFinishLaunchingWithOptions:
loadView
with the following mention of
viewController.view
.loadView
would have been called immediately before the value of
viewController.view
addSubview:
.
ViewController *viewController = [[ViewController alloc] initWithNibName: nil bundle: nil]; [self.window addSubview: viewController.view];
Instead of the above two statements,
application:didFinishLaunchingWithOptions:
rootViewController
.
self.window.rootViewController = [[ViewController alloc] initWithNibName: nil bundle: nil];
The view controller and the view contain instance variables or properties
that point to each other.
The
view
property of the
ViewController
object,
inherited from class
UIViewController
,
points down to the
View
object.
Conversely, the
viewController
instance variable of the
View
object points up to the
ViewController
object.
This instance variable allows the
View
object to send messages to the
ViewController
object.
It has to be a
__weak
pointer (two underscores)
to allow the view to die before the view controller.
Otherwise, the view and the view controller will keep each other alive forever
and we’d have a memory leak.
In our view controller, the
interfaceOrientation
propery has one of the four possible values of the data type
UIInterfaceOrientation
.
Just between you and me,
this data type is basically an
int
,
and the four values are basically 1, 2, 3, 4.
The initial orientation is portrait.
The
View
’s
superview
is the
window.
The
frame
and
bounds
of the
window
remain serenely unaffected by the change in orientation.
The Macintosh origin is in the lower left corner of the screen, with the Y axis pointing up. The iPhone origin is in the upper left corner of the screen, with the Y axis pointing down. The CTM agrees with the view’s bounds. The CTM for Portrait and PortraitUpsideDown means “flip it upside down, double the coördinates, and add 640 to the y.” The CTM for LandscapeLeft and LandscapeRight means “flip it upside down, double the coördinates, and add 1136 to the y.”
applicationFrame
,
the view occupies the entire window.
This causes the status bar to appear in front of the top part of the view.
To prevent the words in the view from apearing behind the status bar,
drawRect:
started the format string with the newline character
"\n"
.
A better way to accomplish this would be to remove the newline
and insert the following initialization code into the
initWithFrame:controller:
method of the view.
//Draw no text in the part of the UITextView //that lies behind the status bar. CGFloat height = [UIApplication sharedApplication].statusBarFrame.size.height; self.textContainerInset = UIEdgeInsetsMake( height, self.textContainerInset.left, self.textContainerInset.bottom, self.textContainerInset.right );
supportedInterfaceOrientations
should
return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortraitUpsideDown;or, more briefly,
return UIInterfaceOrientationMaskAll;To make it redraw only for portrait and portrait upside down, you can say
return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown;See all the possible values for the
UIInterfaceOrientationMask
.
drawRect:
method of class
UIView
is called when the orientation of the view changes.
The
layoutSubviews
method of
class
UIView
is also called.
In class
View
,
change the name of the
drawRect:
method to
layoutSubviews
and remove the unused argument
rect
.
ViewController
’s
tab bar item.
Add the file
star.png
to the Supporting Files folder in the Project Navigator,
checking the checkbox for
“Copy items into destination group’s folder (if needed).”
In
ViewController.m
,
- (id) initWithNibName: (NSString *) nibNameOrNil bundle: (NSBundle *) nibBundleOrNil { self = [super initWithNibName: nibNameOrNil bundle: nibBundleOrNil]; if (self) { // Custom initialization self.title = @"Title"; self.tabBarItem.image = [UIImage imageNamed: @"star.png"]; self.tabBarItem.badgeValue = @"b"; } return self; }Run the app and note that the tab bar item is not yet visible.
Now put a tab bar controller over the the
ViewController
.
Initialize the tab bar controller
in
application:didFinishLaunchingWithOptions:
.
A tab bar controller usually has an array of five or so controllers under it,
but this tab bar controller has an array of only one controller.
Our other arrays were created with
arrayWithObjects:
;
this tiny array is created with
arrayWithObject:
.
// Override point for customization after application launch. self.window.rootViewController = [[UITabBarController alloc] init]; ((UITabBarController *)self.window.rootViewController).viewControllers = [NSArray arrayWithObject: [[ViewController alloc] initWithNibName: nil bundle: nil] ];The
viewController
’s tab bar item should now be visible.
(For a row of tab bar items, see the app store.)
A badge of more than one character
will block part of the star.
viewController
’s
navigation item.
In the
initWithNibName:bundle:
method of class
ViewController
,
insert the following code.
A naviagtion item has room for a more elaborate title than a tab bar item.
self.navigationItem.prompt = @"Welcome to"; self.navigationItem.title = @"More Elaborate Title"; self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem: UIBarButtonSystemItemDone target: nil action: NULL ];Run the app and note that the navigation item is not yet visible.
Now put a navigation controller over the
ViewController
.
Initialize the navigation controller
in
application:didFinishLaunchingWithOptions:
.
self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController: [[ViewController alloc] initWithNibName: nil bundle: nil ] ];The
viewController
’s
navigation item should now be visible.
We conclude that the navigation bar is 74 pixels (or pixel pairs) high,
because