iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) (48 page)

Read iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) Online

Authors: Aaron Hillegass,Joe Conway

Tags: #COM051370, #Big Nerd Ranch Guides, #iPhone / iPad Programming

BOOK: iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides)
5.86Mb size Format: txt, pdf, ePub
13
UIPopoverController and Modal View Controllers

So far, you have seen four ways to show a view controller’s view: setting it as the root view controller of the window, pushing it onto a
UINavigationController
’s stack, adding it to a
UITabBarController
, and presenting it modally.

 

In this chapter, we will look at
UIPopoverController
and more options for presenting modal view controllers. Some of these options are only available on the iPad, so we’ll start by making
Homepwner
a
universal application
– an application that runs natively on the iPad as well as the iPhone and iPod touch.

 

Figure 13.1  Homepwner on the iPad

 
 
Universalizing Homepwner

Open
Homepwner.xcodeproj
and select the
Homepwner
project from the project navigator. Then select the
Homepwner
target in the editor area. In the
Summary
pane, change the
Devices
pop-up to
Universal
(
Figure 13.2
).

 

Figure 13.2  Universalizing Homepwner

 
 

That was it.
Homepwner
now runs natively on the iPad. Click on the
Scheme
pop-up button next to the Run button. You’ll see that there is now an
iPad Simulator
option. Select this option and build and run the application.

 

The
ItemsViewController
view looks great on the iPad, but if you select a row, you’ll see that the
DetailViewController
’s interface could use some work. (Also, tapping the button to take a picture throws an exception. We’ll fix this shortly.)

 

One way to improve the looks of the
DetailViewController
’s interface on the iPad is to change the autoresizing masks of the subviews in
DetailViewController.xib
so that when its view is resized to fit the iPad, all of the subviews are organized nicely. This is what you did when you universalized your
HeavyRotation
application.

 

Another way is to create two completely independent XIB files: one for the iPhone device family and the other for the iPad. This is most useful when you want to have a different interface on the iPad that takes advantage of the additional screen space. To do this, you name the iPad-specific XIB file the same as the iPhone version with an added
~ipad
suffix, like
DetailViewController~ipad.xib
. Remember to recreate the entire view hierarchy and re-establish connections in the iPad version of the XIB file.

 

When you create separate XIB files for the two device families and use the
~ipad
suffix, you do not need to write any extra code to load the appropriate XIB file. Every
UIViewController
has a
nibName
that you pass in the initializer message. (If you pass
nil
, the
nibName
is effectively the name of the class.) When a view controller goes to load its view, it loads the XIB file that matches its
nibName
. However, if the application is running on an iPad, it first checks for a matching XIB file suffixed with
~ipad
. If there is one, it loads that XIB file instead.

 

At this point, we’re not focusing on the appearance of the
DetailViewController
’s view, so we have a third option: leave it as it is. But let’s do something about the blindingly white background. In
DetailViewController
’s
viewDidLoad
method, the background color of the view is set to be the
groupTableViewBackgroundColor
color. This color is not available on the iPad, which is why you get the all-white background instead. So when the application is running on an iPad, let’s set the color to closely match the background color of
ItemsViewController
’s view.

 
Determining device family

First, we need to check what type of device the application is running on. The object to ask is the
UIDevice
singleton. You get this object by sending the class method
currentDevice
to the
UIDevice
class. Then you can check the value of its
userInterfaceIdiom
property. At this time, there are only two possible values:
UIUserInterfaceIdiomPad
(for an iPad) and
UIUserInterfaceIdiomPhone
(for an iPhone or an iPod touch).

 

In
DetailViewController.m
, modify
viewDidLoad
.

 
- (void)viewDidLoad
{
    [super viewDidLoad];
    
[[self view] setBackgroundColor:[UIColor groupTableViewBackgroundColor]];
    UIColor *clr = nil;
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
        clr = [UIColor colorWithRed:0.875 green:0.88 blue:0.91 alpha:1];
    } else {
        clr = [UIColor groupTableViewBackgroundColor];
    }
    [[self view] setBackgroundColor:clr];
}

Build and run the application on the iPad simulator or on an iPad. Navigate to the
DetailViewController
and sigh in relief at the much nicer background color.

 

iPad users expect applications to work in all orientations, so add the following method to both
ItemsViewController.m
and
DetailViewController.m
:

 
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)io
{
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
        return YES;
    } else {
        return (io == UIInterfaceOrientationPortrait);
    }
}

Build and run the application on both the iPhone and iPad (or their respective simulators). Notice that rotating on the iPhone does nothing and rotating on the iPad rotates the view of the view controller that is on the screen.

 

Now that
Homepwner
can run on the iPad, let’s take advantage of some iPad-only ways to present view controllers, starting with
UIPopoverController
.

 
UIPopoverController

It is common for an application to present a view controller so that the user can make a choice about what the application should do next. For example, the
UIImagePickerController
shows the user a table of images to choose from.

 

On the iPhone and iPod touch, view controllers like this are presented modally and take up the entire screen. However, the iPad, having more screen real estate, offers another option:
UIPopoverController
.

 

A popover controller displays another view controller’s view in a bordered window that floats above the rest of the application’s interface. When you create a
UIPopoverController
, you set this other view controller as the popover controller’s
contentViewController
.

 

In this chapter, you will present the
UIImagePickerController
in a
UIPopoverController
when the user taps the camera bar button item in the
DetailViewController
’s view.

 

Figure 13.3  UIPopoverController

 
 

In
DetailViewController.h
, add an instance variable to hold the popover controller. Also, declare that
DetailViewController
conforms to the
UIPopoverControllerDelegate
protocol.

 
@interface DetailViewController : UIViewController
        
 UIPopoverControllerDelegate
>
{
    __weak IBOutlet UITextField *nameField;
    __weak IBOutlet UITextField *serialNumberField;
    __weak IBOutlet UITextField *valueField;
    __weak IBOutlet UILabel *dateLabel;
    __weak IBOutlet UIImageView *imageView;
    UIPopoverController *imagePickerPopover;
}
 

In
DetailViewController.m
, add the following code to the end of
takePicture:
.

 
    [imagePicker setDelegate:self];
    
[self presentViewController:imagePicker animated:YES completion:nil];
    // Place image picker on the screen
    // Check for iPad device before instantiating the popover controller
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
        // Create a new popover controller that will display the imagePicker
        imagePickerPopover = [[UIPopoverController alloc]
                initWithContentViewController:imagePicker];
        [imagePickerPopover setDelegate:self];
        // Display the popover controller; sender
        // is the camera bar button item
        [imagePickerPopover presentPopoverFromBarButtonItem:sender
                            permittedArrowDirections:UIPopoverArrowDirectionAny
                            animated:YES];
    } else {
        [self presentViewController:imagePicker animated:YES completion:nil];
    }
}

Notice that we check for the device before creating the
UIPopoverController
. It is critical to do this. You can only instantiate
UIPopoverController
s on the iPad family of devices, and trying to create one on an iPhone will throw an exception.

 

Build and run the application on the iPad simulator or on an iPad. Navigate to the
DetailViewController
and tap the camera icon. The popover will appear and show the
UIImagePickerController
’s
view
. Select an image from the picker, and it will appear in
DetailViewController
’s
view
.

 

You can dismiss the popover controller by tapping anywhere else on the screen. When a popover is dismissed in this way, it sends the message
popoverControllerDidDismissPopover:
to its delegate. Implement this method in
DetailViewController.m
.

 
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController
{
    NSLog(@"User dismissed popover");
    imagePickerPopover = nil;
}

Notice that you set
imagePickerPopover
to
nil
here to destroy the popover. You will create a new one each time the camera button is tapped.

 

The popover should also be dismissed when you select an image from the image picker. In
DetailViewController.m
, at the end of
imagePickerController:didFinishPickingMediaWithInfo:
, dismiss the popover when an image is selected.

 
    [imageView setImage:image];
    
[self dismissViewControllerAnimated:YES completion:nil];
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
        // If on the phone, the image picker is presented modally. Dismiss it.
        [self dismissViewControllerAnimated:YES completion:nil];
    } else {
        // If on the pad, the image picker is in the popover. Dismiss the popover.
        [imagePickerPopover dismissPopoverAnimated:YES];
        imagePickerPopover = nil;
    }
}
 

When you explicitly send the message
dismissPopoverAnimated:completion:
to dismiss the popover controller, it does not send
popoverControllerDidDismissPopover:
to its delegate, so you must set
imagePickerPopover
to
nil
in
dismissPopoverAnimated:completion:
.

 

There is a small bug to fix. If the
UIPopoverController
is visible and the user taps on the camera button again, the application will crash. This crash occurs because the
UIPopoverController
that is on the screen is destroyed when
imagePickerPopover
is set to point at the new
UIPopoverController
in
takePicture:
. You can ensure that the destroyed
UIPopoverController
is not visible and cannot be tapped by adding the following code to the top of
takePicture:
in
DetailViewController.m
.

 
- (IBAction)takePicture:(id)sender
{
    if ([imagePickerPopover isPopoverVisible]) {
        // If the popover is already up, get rid of it
        [imagePickerPopover dismissPopoverAnimated:YES];
        imagePickerPopover = nil;
        return;
    }
    UIImagePickerController *imagePicker =
                [[UIImagePickerController alloc] init];

Build and run the application. Tap the camera button to show the popover and then tap it again – the popover will disappear.

 

Other books

The Story of My Heart by Felices, Margarita
Going Home Again by Dennis Bock
Tyed to You by Jordyn McKenzie
City of Dreams by Martin, William
Bondmate by J.J. Lore