Chapter 19
Universal Applications for the iPad

Key Skills & Concepts

• Understanding the key differences of the iPad

• Creating a universal application

• Creating a Split View Controller

• Popover views

• Understanding of other iPad and iOS 4 features

It’s important to understand that the iPad isn’t just an iPhone with a bigger screen. Somehow, the larger screen completely changes the experience. When you’re designing for the iPad, don’t just think about spreading out your content so that it more or less fills the screen. Instead, think about how you could completely reimagine what you’re presenting given the freedom of the iPad’s large screen. Information that had to be spread over multiple tabs on an iPhone can be elegantly displayed on a single screen. Or you can include a variety of controls alongside your content. While the iPhone and iPod touch were best suited to consuming content, the iPad is also great for creating it—think about applications that you could design that would let the user create new content while on-the-go. The iPad provides whole new opportunities for innovative applications.

Everything that you’ve learned about iOS programming in the previous chapters also applies to the iPad, and all of the example applications should run just fine on an iPad. However, because they were designed and built for the smaller display of the iPhone, without modification they will run in a small area in the center of the iPad’s much larger display—not a particularly compelling experience for the user (Figure 19-1). Fortunately, the changes necessary to make a typical application run full screen on the iPad are quite easy. In this chapter you will begin by looking at the minimum changes necessary to create a universal application.

However, making your app run full screen as a universal application on the iPad is only the start. Unless your primary audience will be using an iPhone or iPod touch, you’ll want to go well beyond the minimum and design your information layout for the larger screen. You’ll also want to incorporate new iOS functionality added just for the iPad. For instance, the larger screen of the iPad makes it possible to display two levels of the hierarchy in a navigation-based application at once, making it faster and easier for the user to move around in your content. We will incorporate Split View Controllers and popovers into the DogBreeds app from Chapter 17.

The movie player from Chapter 18 used the full screen of the iPhone when playing video, which made sense on a small device. However, when video is supplementing other information in an iPad application, it will often make more sense to display it in only part of the screen so that you can display additional information or controls around the video. Later in this chapter you will convert the MoviePlayer application from Chapter 18 into a universal application and then modify it to play the video in the center of the screen.

Image

Figure 19-1 DogBreeds application running in the iPad Simulator

Finally, we will briefly take a look at some of the other new iOS functionality that showed up in iOS 3.2 on the iPad and then in iOS 4.0 on the iPhone and iPod touch. We don’t have enough space in an introductory book to try examples for each of these new features, but we’ll give you a taste for what they can do and point you at the Apple documentation if you decide they make sense for your applications.

Creating a Universal Application

In all of the Try This examples up to this point, the Targeted Device Family has always been set to iPhone (which includes the iPod touch). If you reopen the DogBreeds app from Chapter 17 and select iPad Simulator from the pull-down next to the Run button, and then click Run, the app will launch in the iPad Simulator in a small window in the center of the larger iPad display (Figure 19-1). Technically, it’s compatible with the iPad, but since it wasn’t built with any knowledge of the iPad’s different hardware, the iPad is just emulating an iPhone.

To take full advantage of the larger screen and other unique features of the iPad, your application will have to be built for the iPad. You essentially have two choices when starting a project that will run on the iPad. Your first option is to create a universal application that will run well on both the iPad and the iPhone, detecting which device it’s on and adjusting as necessary. This is best for users, since they can buy it once in the App Store and use it on both their iPhone and their iPad.

If the iPhone and iPad versions would sell to different audiences or your application contains a lot of predefined graphics that take up a lot of space and need to be different for each screen size (e.g., game backgrounds), it might be more practical to build separate versions for each device. That allows you to include only the graphics needed for the device, or to charge for both versions separately. If the code is largely the same, you can do this with a single project and two build targets. However, if your iPad and iPhone versions will have significantly different functionality, it might make sense to create two separate projects that share any common source files but implement different views and controllers.

In the next Try This example, we will turn the DogBreeds app from Chapter 17 into a universal application. The functionality will be very similar between the iPad and iPhone versions, and it’s the sort of reference application that users would expect to only pay for once. This will require us to check which device we’re running on and layout views accordingly, but it is usually worth complicating the code a bit to have a single application that is distributed to everyone.

Try This
Building an App for iPad and iPhone

1. Select the DogBreeds project in the Navigation pane. Make sure the Deployment Target is set to range from 3.2 through the latest version of the iOS.

2. Click Build Settings tab and select iPhone/iPad in the pull-down menu next to Targeted Device Family (Figure 19-2).

Image

Figure 19-2 Changing the targeted device family

3. Make sure the iPad Simulator is still selected in the pull-down next to the Run button and then click Run. The DogBreeds app will launch in the iPad Simulator and use the full screen (Figure 19-3).

4. Click in and out of the groups and look at a breed or two in the Herding group. Notice that the app more or less works, but as you look at the details for a specific breed, it only uses one corner of the display. You will also notice that when you rotate the iPad, the app continues to display in portrait mode.

Image

Figure 19-3 DogBreeds running full-screen in the iPad Simulator

Handling Orientation Changes

If you’ve had the opportunity to handle an iPad in person, one of the first things that you probably noticed is that you are just as likely to use it in landscape as portrait orientation. You may also find yourself rotating the iPad often while using it. This behavior is different than what is typical with the iPhone or iPod touch. With those devices, you could get away with creating an application that ignores device orientation and only functions in portrait mode. In fact, except for games that were designed for landscape orientation, most of the iPhone applications in the App Store ignore rotation and display in portrait orientation only.

When running on the iPad, your application will have to react to rotations and draw accordingly. Fortunately, reacting to changes in orientation and redrawing the standard UI components is very easy. When an iOS device is rotated, the shouldAutorotateToInterfaceOrientation method is called in the current view controller. If you don’t implement that method, then it defaults to NO.

Try This
Reacting to Orientation Changes

1. Reopen the DogBreeds project.

2. Add the shouldAutorotateToInterfaceOrientation method in Listing 19-1 in all of the view controllers in the application (AKCViewController, BreedViewConroller, BreedsListViewController, BreedDetailViewController, and RootViewController).

Listing 19-1 shouldAutorotateToInterfaceOrientation method

-(BOOL)shouldAutorotateToInterfaceOrientation:
(UIInterfaceOrientation)orientation {
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
      return YES;
    return NO;
}

3. Save all of your changes and click Run. In the Hardware menu of the iPad Simulator, there are menu commands to rotate the device left or right. Play with rotating it while on each of the DogBreed screens, and you will notice that it now rotates and redraws as necessary.

NOTE
The orientation argument to shouldAutorotateToInterfaceOrientation can have one of four values: UIInterfaceOrientationPortrait, UIInterfaceOrientationPortraitUpsideDown, UIInterfaceOrientationLandscapeLeft, UIInterfaceOrientationLandscapeRight. If you have an application that will only work in certain orientations (e.g., portrait and portrait upside down), you can use that argument to decide when to allow rotation.

In the preceding example, we simply allowed rotation when on the iPad and iOS took care of everything else for us, redrawing all of the subviews as necessary. That’s great for UITableViews, but for views where you’re presenting a lot of data in an elegant format, you will probably need to shift around or resize elements to make them continue to fit and look good. You can override the willRotateToInterfaceOrientation:duration method on any view controllers where you want a chance to change the content of the view before it rotates into the new orientation.

In Listing 19-1 you will also notice that the application needed to determine if it was currently running on an iPad and used the following test:

(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)

This is the Apple-preferred way of determining whether you’re running on an iPad or on an iPhone/iPod touch. As you modify various parts of the DogBreeds application to make it display differently on an iPad, you will find yourself using that test fairly often. It’s a judgment call, but when you find yourself having to test whether you are on an iPad over and over in the same view controller, it may make sense to define two view controllers and then instantiate and push one for the iPad and the other one for the iPhone.

Icons and Default Screens

We haven’t bothered creating icons for our sample iPhone applications in previous chapters, but that’s a step that you have to take care of before submitting your application to the App Store. With an iPhone application, you only needed to create a 57 × 57 pixel icon. When creating a universal application, you will also need three more sizes:

• A 72 × 72 icon named “icon-ipad.png” for the home screen

• A 50 × 50 icon named “icon-small-50.png” for Spotlight on the iPad

• A 29 × 29 icon named “icon-small.png” for Spotlight on the iPhone

After you’ve created your icons and added them to the Resources folder, control-click DogBreeds-Info.plist and select Open As Source Code. Replace the two lines:

<key>CFBundleIconFile</key>
    <string></string>

with the following lines to reference all of the icons:

<key>CFBundleIconFiles</key>
     <array>
        <string>icon.png</string>
        <string>icon-ipad.png</string>
        <string>icon-small-50.png</string>
        <string>icon-small.png</string>
     </array>

If you include a default screen in your application’s resources, iOS will use it to display a splash or startup screen while your application is launching. Our sample apps start up very quickly, so we haven’t bothered to create a splash screen. However, when your application is larger or needs to do a lot of initialization at startup, it’s nice to give your user something to look at while it launches. For an iPhone application, the screen would be 320 × 460 and added to your Resources folder as “Default.png.” If you’re creating a universal application, then you also need to include portrait (768 × 1004) and landscape (1024 × 748) screens named “Default-Portrait.png” and “Default-Landscape.png.”

Applications on the iPhone always launch in portrait orientation, but if you want your iPad application to be able to launch in any orientation, you’ll also have to add the following entry to the plist we were editing earlier:

<key>UISupportedInterfaceOrientations~ipad</key>
<array>
  <string>UIInterfaceOrientationPortrait</string>
  <string>UIInterfaceOrientationPortraitUpsideDown</string>
  <string>UIInterfaceOrientationLandscapeLeft</string>
  <string>UIInterfaceOrientationLandscapeRight</string>
</array>

Split Views

The DogBreeds application now runs full screen on the iPad, but it has a lot of wasted space. In a typical navigation-based application, the larger display of the iPad is ideally suited to displaying navigation and item details at the same time. When the iPad is in landscape orientation, a table view from the iPhone easily fits alongside a detail area that’s still much larger than it is on the iPhone. Apple calls this a split view, and iOS 3.2 on the iPad added the UISplitViewController to make it easy to implement a navigation view on the left that controls a detail view on the right.

When the iPad is in portrait orientation, there’s not quite enough width to display a standard table view alongside a reasonably-sized detail area, so when in portrait orientation, the Split View Controller uses the full display for the detail view and pops up a navigation view on top of it when the user taps a button. UISplitViewController automatically takes care of hiding the popover navigation view as you rotate the iPad.

Try This
Add a Split View

iOS lets you define a second main window xib that will be used when the application is running on the iPad. We start by creating that window and placing a Split View Controller in it instead of the Navigation Controller.

1. Open the DogBreeds project. Control-click the project in the Navigation pane and select New Group, naming it Resources-iPad. Drag the new folder down next to the Resources folder. This will give us a convenient place to keep all of our iPad-specific resources.

2. Control-click the Resources-iPad folder and select New File | Select User Interface from the column on the left and Application as the template. Select iPad from the Device Family pull-down (Figure 19-4). Name the new file MainWindow-iPad. This creates a new xib file that is sized specifically for the iPad.

Image

Figure 19-4 Creating a new window xib

3. Now we need to tell the application about the new MainWindow file. Select DogBreedsInfo.plist and add a new entry with key NSMainNibFile~ipad and the string value “MainWindow-iPad”.

4. Select MainWindow-iPad.xib. Drag a Split View Controller into the main window.

5. Select the View Controller on the left (under the Root View Controller title). In the Identity subpane of the Utilities pane, change the class of this View Controller to RootViewController. Select the right pane in the Split View Controller and change its class to BreedDetailViewController. In the Attributes subpane, set each of them to load from their respective NIB files. Save your changes.

6. Go to DogBreedsAppDelegate.h and define a new instance variable and IBOutlet property:

UISplitViewController *splitVC;
@property (nonatomic, retain) IBOutlet UISplitViewController
*splitVC;

7. Synthesize the property in the .m file and release it in the dealloc method.

8. Switch back to MainWindow-iPad.xib. Select the App Delegate and set its class to DogBreedsAppDelegate. In the Connections subpane, connect the navigationController outlet to the Navigation Controller, the splitVC outlet to the Split View Controller, and the window outlet to the Window (Figure 19-5).

Image

Figure 19-5 Setting up the Split View Controller

9. Now when the application launches, we need to use the appropriate view based on whether we’re running on the iPhone or iPad. Select DogBreedsAppDelegate.m and change the application:didFinishLaunchingWithOptions method to match Listing 19-2.

Listing 19-2 application:didFinishLaunchingWithOptions method

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
    [window addSubview:[splitVC view]];
  } else {
    [window addSubview:[navigationController view]];
  }
  [window makeKeyAndVisible];
  return YES;
}

10. Save your changes and click Run. If the iPad is in portrait orientation, rotate it to landscape orientation. You should see the dog groups navigation along the left and an empty details view on the right (Figure 19-6). If you stop the application and switch to running it in the iPhone Simulator, you’ll find that everything still works as expected on the smaller iPhone screen.

Image

Figure 19-6 DogBreeds with a split view

You will notice that clicking a group takes you to the breeds list as expected, but clicking a breed displays its details in the navigation pane rather than the details area. This is because the two panes don’t know anything about each other yet.

You will also notice that when you rotate the iPad Simulator to portrait orientation, the navigation view slides out of sight as expected. However, there’s no button to make the navigation appear as a popover. Let’s solve this problem first, and then we’ll worry about connecting the detail pane to the navigation pane.

We will need to add a toolBar to the Breed Detail View in order to have some place to draw the button. But the catch is that the iPhone version already has a navigation bar and we don’t want to add an additional toolbar there. Since we still want the rest of the behavior of the Breed Detail View, the easiest way to solve this will be to subclass BreedDetailViewController. Adding this subclass will also give us the opportunity to change the layout of the photo and description in the nib file to make better use of the larger iPad display.

1. Create a new file, making it a UIViewController subclass with Targeted for iPad selected. Make sure a XIB is created too (Figure 19-7). Name the new file BreedDetailViewController_iPad.

Image

Figure 19-7 Creating a BreedDetailViewController subclass

2. Edit BreedDetailViewController_iPad.h and change the class to inherit from BreedDetailViewController. Add an instance variable and IBOutlet property called toolbar to keep track of the toolbar we will be adding. Also create a property to keep track of the popover that we will be implementing a little later (Listing 19-3). Be sure to synthesize the properties and release them in BreedDetailViewController_iPad.m

Listing 19-3 BreedDetailViewController_iPad.h

#import <UIKit/UIKit.h>
#import "BreedDetailViewController.h"
@interface BreedDetailViewController_iPad : BreedDetailViewController
<UISplitViewControllerDelegate> {
  UIToolbar *toolbar;
  UIPopoverController *popover;
}
@property(nonatomic,retain) IBOutlet UIToolbar *toolbar;
@property(nonatomic, retain) UIPopoverController *popover;
@end

3. Select BreedDetailViewController_iPad.xib. Drag a toolbar into Edit pane and remove the button from the toolbar, since it will be added/removed in our code as the iPad is rotated. Connect the toolbar to the toolbar outlet in the File’s Owner.

4. Also add an Image View (500 pixels wide × 350 pixels tall) and add a Text View (600 pixels wide × 560 pixels tall). Connect them to their outlets in the File’s Owner (it will be similar to BreedViewController.xib). Save your changes.

5. Select MainWindow-iPad.xib and select the right-hand pane of the Split View Controller. Change its class from BreedDetailViewController to BreedDetailViewController_iPad. Also change the NIB that it’s loaded from to match. Save your changes.

We now have a toolbar where we can place a button to display the navigation popover when in portrait mode, but we need to be notified when the Split View Controller is changing orientation so that we can hide/show the button. Fortunately, iOS provides the UISplitViewControllerDelegate protocol with methods that are called when orientation changes.

6. Select BreedDetailViewController_iPad.h. Add the UISplitViewControllerDelegate protocol (Listing 19-3).

7. Select BreedDetailViewController_iPad.m. Implement the willHideViewController delegate method as shown in Listing 19-4. This method will be called right before the navigation controller is hidden and the detail view resized. We set the name of the button and then keep track of the popover so that we can delete it later, when the iPad rotates back to landscape orientation. We also take this opportunity to resize the photo and description fields to fit nicely in the portrait layout.

Listing 19-4 willHideViewController in BreedDetailViewController_iPad.m

- (void)splitViewController:(UISplitViewController*)svc
willHideViewController:(UIViewController *)aViewController
           withBarButtonItem:(UIBarButtonItem*)barButtonItem
forPopoverController:(UIPopoverController*)pc {
    if (self.selectedBreed != nil)
         barButtonItem.title = self.selectedBreed.group.name;
    else
       barButtonItem.title = aViewController.title;
    [self.toolbar setItems:[NSArray arrayWithObject:barButtonItem]
animated:YES];
    self.popover = pc;
    CGRect portraitPhotoFrame = photo.frame;
    portraitPhotoFrame.origin.x = 135;
    portraitPhotoFrame.origin.y = 50;
    photo.frame = portraitPhotoFrame;
    CGRect descFrame = breedDescription.frame;
    descFrame.size.height = 560;
    descFrame.origin.x = 85;
    descFrame.origin.y = 410;
    breedDescription.frame = descFrame;
}

8. Select MainWindow-iPad.xib. Select the Split View Controller and connect its delegate outlet to the BreedDetailViewController_iPad in the left pane of the view controller.

9. Save all of your changes and click Run. DogBreeds should now display a button when rotated to portrait orientation. Clicking the button brings up the group list or the breeds list if a group has already been selected.

10. You’ll notice that the button remains in the toolbar after switching back to landscape orientation. Implement the willShowViewController delegate method as shown in Listing 19-5. When the navigation view is about to reappear when the iPad rotates to landscape orientation, it removes the button and releases the popover view. We also take the opportunity to change the position and size of the photo and description fields to make them fit nicely in the landscape layout.

Listing 19-5 willShowViewController in BreedDetailViewController_iPad.m

- (void)splitViewController: (UISplitViewController*)svc
willShowViewController:(UIViewController *)aViewController
  invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem {
    [self.toolbar setItems:[NSArray array] animated:YES];
    self.popover = nil;
    // Adjust the photo and description to fit nicely in landscape mode
    CGRect portraitPhotoFrame = photo.frame;
    portraitPhotoFrame.origin.x = 100;
    portraitPhotoFrame.origin.y = 20;
    photo.frame = portraitPhotoFrame;
    CGRect descFrame = breedDescription.frame;
    descFrame.size.height = 360;
    descFrame.origin.x = 50;
    descFrame.origin.y = 375;
    breedDescription.frame = descFrame;
}

11. Run the application again and the button now disappears when you rotate back to landscape orientation.

We still have one major issue outstanding in our split view. Clicking a breed in the navigation view displays the details in the navigation view instead of the detail view. This is the desired behavior on the iPhone, but on the iPad the navigation view needs to be able to find the separate detail view and update it. We can do this by first ensuring that rootViewController has a reference to the detail view and then passing that reference on to the BreedsListViewController when it’s pushed so that it has access to the detail view when a breed is selected.

1. First add an IBOutlet property to RootViewController.h to store a reference to the BreedDetailViewController (and remember to synthesize it and release it in the dealloc method):

BreedDetailViewController *breedDetailVC;
@property (nonatomic, retain) IBOutlet BreedDetailViewController
*breedDetailVC;

2. Now we have to make sure it’s connected to the detail half of the Split View Controller. Open MainWindow-iPad.xib, select the left pane of the split view (the RootViewController) and in the Connections pane, connect breedDetailVC to the right hand pane of the split view (the BreedDetailViewController). Save your changes.

3. Select RootViewController.m and edit the didSelectRowAtIndexPath method and set the breedDetailVC property of breedsListVC to the breedDetailVC property of the RootViewController (Listing 19-6).

Listing 19-6 didSelectRowAtIndex in RootViewController.m

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:
(NSIndexPath *)indexPath {
    AKCGroup *theGroup = [[self fetchedResultsController]
objectAtIndexPath:indexPath];
    if (self.editing == YES) {
        self.groupEditorVC.group = theGroup;
        self.groupEditorVC.insertingGroup = NO;
        [self.navigationController pushViewController:
self.groupEditorVC animated:YES];
    } else {
        self.breedsListVC.selectedGroup = theGroup;
        self.breedsListVC.title = theGroup.name;
        self.breedsListVC.managedObjectContext =
self.managedObjectContext;
         if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
             self.breedsListVC.breedDetailVC = self.breedDetailVC;
         [self.navigationController pushViewController:
self.breedsListVC animated:YES];
        }
}

4. Select BreedDetailViewController.h and add two new method declarations:

- (void)updateDetails;
- (void)updateBreed: (Breed *)theBreed;

5. Switch to BreedDetailViewController.m and implement the methods as shown in Listing 19-7. We’re adding a handy method for changing the breed associated with the BreedDetailViewController and a separate method for loading the values for the details fields (photo and description) into their subviews. We’ll call updateBreed from the navigation controller when the user selects a specific breed.

Listing 19-7 Changes to BreedDetailViewController.m

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self updateDetails];
}
- (void)updateDetails {
    self.title = selectedBreed.name;
    self.breedDescription.text = self.selectedBreed.breedDescription;
    NSURL* aURL = [NSURL URLWithString:self.selectedBreed.photoURL];
    NSData *imageData = [[NSData alloc] initWithContentsOfURL:aURL];
    UIImage *theImage = [[UIImage alloc] initWithData:imageData];
    [photo setImage:theImage];
    [theImage release];
}
-(void)updateBreed: (Breed *)theBreed {
    self.selectedBreed = theBreed;
}

6. When running on the iPhone, updateBreed doesn’t update the subviews because that will have to wait until the view is ready to appear. However, on the iPad updateBreed should update the details immediately and notify the view that something has changed so that it will redraw. Switch to BreedDetailViewController_iPad.m and add an override of the updateBreed method (Listing 19-8).

Listing 19-8 Changes to BreedDetailViewController_iPad.m

-(void)updateBreed: (Breed *)theBreed {
    self.selectedBreed = theBreed;
    [self updateDetails];
    [self.view setNeedsDisplay];
}

7. Select BreedsListViewController.m and edit the didSelectRowAtIndexPath method to match Listing 19-9. We can now call updateBreed regardless of what device the application is running on. If the application is running on the iPhone we also need to push the detail view onto the stack.

Listing 19-9 didSelectRowAtIndexPath in BreedsListViewController.m

-(void)tableView:(UITableView *)tableView
                  didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
     Breed *theBreed = [[self fetchedResultsController]
                                objectAtIndexPath:indexPath];
    if (self.editing == YES) {
        self.breedEditorVC.breed = theBreed;
        self.breedEditorVC.insertingBreed = NO;
        [self.navigationController pushViewController:
self.breedEditorVC animated:YES];
    } else {
        [self.breedDetailVC updateBreed:theBreed];
        if (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPad) {
            [self.navigationController pushViewController:
                                self.breedDetailVC animated:YES];
        }
    }
}

8. Save all your changes and run the application (Figure 19-8).

Everything should be more or less working, but you’ll notice that in portrait orientation, when you select a breed and the details update behind the navigation popover, it remains visible. It would be nice if the popover disappeared once we’ve selected a breed so that you can easily see the breed information.

1. Select BreedDetailsViewController_iPad.m and edit the updateBreed method and add the following lines:

if (self.popover)
        [self.popover dismissPopoverAnimated:YES];

2. Save your changes and run the application. In portrait orientation, when you select a breed, the navigation popover will now disappear to let you see the breed information.

3. You will notice that when you stay in portrait orientation, the button to bring up the navigation popover doesn’t change to reflect the group of the currently selected breed. The updateBreed method can update the button title, but it will need a reference to the button first. Edit BreedDetailViewController_iPad.h and add a UIBarButtonItem property called navButton. Be sure to synthesize and release it.

4. Edit the willHideViewController method in BreedDetailViewController_iPad and set the navButton property to the button:

self.navButton = barButtonItem;

5. Edit the willShowViewController method and set navButton to nil, since it’s going away.

6. Finally, edit the updateBreed method and, if the button is non-nil, then update its title to match the group of the newly selected breed (Listing 19-10).

Image

Figure 19-8 DogBreeds running in landscape orientation

Listing 19-10 Updating the button title in updateBreed

-(void)updateBreed: (Breed *)theBreed {
    if (self.popover) {
        [self.popover dismissPopoverAnimated:YES];
        if (self.navButton != nil) {
            self.navButton.title = theBreed.group.name;
            [self.toolbar setNeedsDisplay];
        }
    }
    self.selectedBreed = theBreed;
    [self updateDetails];
    [self.view setNeedsDisplay];
}

Image

Figure 19-9 DogBreeds running in portrait orientation

7. Run the application and note that the button updates its title as you switch between breeds in different working groups (Figure 19-9).

Note that because we load the breed photo over the Internet before redrawing the detail view, there will be a delay between tapping the breed name and the details appearing. When the Internet connection is slow, this delay will be long enough that the user won’t think that their tap worked. Immediate feedback is especially important in navigation controls, and if we were creating a polished application ready for submission to the App Store, the photo would need to be loaded asynchronously so that the rest of the details can show up immediately.

You have now converted the DogBreeds application from Chapter 17 into a fully functional universal app that still works the same way it did on the iPhone but also looks good on the iPad. If you were planning to submit this application to the App Store, there’s still a lot of polishing you could do, but the basic functionality is all there. With the much bigger details view, you could store and display a lot more information about the breeds. To fit that extra information in the iPhone version, you might want to switch to a tabbed interface for the details screen and spread what fits in one screen on the iPad over several tabs on the iPhone.

Other iPad Features

The iPad and iOS 3.2 brought new functionality to iOS, and in this section we’ll briefly cover the most significant new features. It’s beyond the scope of this book to cover all of them in detail with complete Try This examples, but we will at least introduce the new features, describe when you might want to use them, and then point you toward excellent Apple documentation that you can leverage to add advanced functionality to your own applications.

Using Popovers for Information or Editing

The popover view that is used by UISplitViewController for navigation in portrait orientation can also be used independently on the iPad. Popovers work well in any situation where the user might want to tap on a visible item on the screen and then see more information or quickly edit some aspect of the item. They have the advantage of being quick and unobtrusive, while leaving the rest of the screen (the overall context) still visible to the user. However, popovers should not be used in situations where you must have an answer from the user. When using the popover, the user should always be able to tap outside the popover and have it disappear. If you need to get the attention of your user, or must have an answer to some question or choice, you will want to use a modal view instead.

Displaying a popover is quite easy. You will want to begin by creating a view controller that contains the information you want to display or the controls needed for editing an item that you are displaying on the screen. Make sure that view controller is accessible via an outlet in the enclosing view so that you can refer to it when the user taps on an item.

Popovers are ideal for situations where the user taps on an object in the display, or even just an obvious area of the screen. Assuming you detect that tap and then call a method to handle it, the implementation of that method will look something like the following:

-(void) handleUserTap: (id)sender {
UIPopoverController *thePopover = [[UIPopoverController alloc]
           initWithContentViewController: myInfoVC];
  thePopover.popoverContentSize = myInfoVC.view.frame.size;
  [thePopover presentPopoverFromRect: ((UIView *)sender).bounds
           inView: (UIView *)sender
           permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}

This assumes sender is the subview that was tapped by the user and the contents to be displayed in the popover are in an outlet called myInfoVC. If you need to know when the popover shows and hides in order to adjust its content, you can implement the UIPopoverControllerDelegate protocol. Apple’s “iPad Programming Guide” has a good description of using popovers for content, and Apple’s ToobarSearch sample application shows how to use a popover to display search results.

Movies in a View

On the iPad with its larger screen, it will often make sense to display video in a smaller view, leaving room on the screen for additional information or controls. As we mentioned in Chapter 18, with iOS 3.2 and later, the movie player does not need to take over the full screen. In the next Try This example, we will convert the MoviePlay application from Chapter 18 into a universal application. While it will behave the same as before on the iPhone, you will be modifying it to play the movie in the center of the screen on an iPad. You can easily imagine how a reference application could be particularly useful on the iPad when you can continue to display information while playing a video in only part of the screen.

Try This
MoviePlayer Centered on the iPad Screen

1. Open the MoviePlayer project from Chapter 18. You will convert it to a universal application by following steps similar to the first Try This example. Begin by changing the build settings so that the targeted device family is iPhone/iPad.

2. Create a new UIViewController subclass with the name MoviePlayerViewController_iPad. Make sure that Targeted for iPad and With XIB are checked.

3. Edit MoviePlayerViewController_iPad.h and make it a subclass of MoviePlayerViewController (you will need to import MoviePlayerViewController.h).

4. Create MainWindow_iPad.xib and add a View Controller with class MoviePlayerViewController_iPad, loaded from the MoviePlayViewController_iPad nib. Change the class of the App Delegate to MoviePlayerAppDelegate. Connect the window and view controller outlets of the App Delegate to the window and new view controller.

5. Select MoviePlayerViewController_iPad.xib and add a Play Movie button connected to the File’s Owner playMovie action. Set the button’s geometry so that its size is fixed and it centers horizontally and vertically (Figure 19-10).

6. Add an entry to MoviePlayer-Info.plist with key NSMainNibFile~ipad and string value MainWindow-iPad, the same as you did when converting DogBreeds to a universal app.

Image

Figure 19-10 Changing the geometry of the Play Movie button

7. Save all of your changes and run the application. The Play Movie button should show up and tapping it should start the movie in one corner of the display. It works, but it doesn’t look pretty on an iPad.

8. Override the playMovie method in MoviePlayerViewController_iPad using the implementation in Listing 19-11.

Listing 19-11 playMovie in MoviePlayerViewController_iPad.m

-(IBAction) playMovie: (id) sender {
  movieplayer = [[MPMoviePlayerController alloc]
        initWithContentURL: [NSURL fileURLWithPath:
        [[NSBundle mainBundle]
        pathForResource: @"short" ofType:@"3gp"]]];
  [[NSNotificationCenter defaultCenter]
        addObserver:self
        selector:@selector(playingDone)
        name:MPMoviePlayerPlaybackDidFinishNotification
        object:nil];
  CGPoint center =
        ([UIDevice currentDevice].orientation ==
              UIDeviceOrientationPortrait) ?
              CGPointMake(384,512) : CGPointMake(512,384);
  [[movieplayer view] setFrame:CGRectMake(center.x - 240,
        center.y - 160, 480, 320)];
  [[self view] addSubview:[movieplayer view]];
  [movieplayer play];
}

9. Save your changes and run the application. Now the button and movie should both appear centered in the display, whether the iPad is in landscape or portrait orientation (Figure 19-11).

Image

Figure 19-11 MoviePlayer on the iPad with video in a subview

You have now had practice converting a second application to be a universal application that runs well on the iPad but still runs correctly on the iPhone. On the iPad with its larger screen, it will often make sense to display video in a smaller view leaving room for additional information or controls. We successfully converted the MoviePlayer application from Chapter 18 so that on an iPad the video plays in an iPhone-sized view with plenty of room for additional information or controls.

External Display

A little known feature of the iPad is the ability to connect an external display. Apple sells an adapter cable that will plug into the connector on the bottom of the iPad and connect to any VGA display or projector. It’s important to note that the monitor or projector becomes a second display for the iPad; it doesn’t mirror the contents of the iPad screen. This is actually very useful behavior if you’re writing something like a presentation application where the second screen displays the content and the iPad displays controls or presentation notes. There aren’t very many applications in the App Store that take advantage of an external display, so if you have a clever idea for a new application that leverages two screens, it could be a great opportunity.

To use the external display, you have to add code to your application to detect the display, get a reference to it, and then add views to it. This is actually surprisingly easy to do. You can detect a second screen by registering to be notified when an external display is connected and disconnected:

NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector: @selector(screenConnected:)
name:UIScreenDidConnectNotification object:nil];
[center addObserver:self selector: @selector(screenDisconnected:)
name:UIScreenDidDisconnectNotification object:nil];

If an external display is already connected when your application launches, no notifications are sent, so you will also need to check for a second screen on launch. You can check for the second screen and get a reference to the screen using the UIScreen class:

NSAray *allTheScreens = [UIScreen screens];
if ([allTheScreens count] > 1) {
  // external display attached
}

The first screen in the array is always the iPad’s built-in display. Any additional screens will be external (at present only one is supported, but who knows what the future holds).

To draw on the external screen, you need to create a UIWindow, set its screen to the reference to the external screen and then add a view to the window. For example:

  UIScreen *secondScreen = [[UIScreen screens] objectAtIndex:1];
  UIWindow *externalWindow = [[UIWindow alloc] initWithFrame:[secondScreen
bounds]];
  [externalWindow setScreen: secondScreen];
  externalView = [[UIView alloc] initWithFrame: [externalWindow bounds]];
  [externalWindow addSubview:externalView];
  [externalWindow makeKeyAndVisible];

The externalView is just like any other view, and you add subviews to it or draw in it. The main thing to remember is to watch for notifications when the external screen appears or disappears, since the user can unplug it at any time and you will need to react and move controls or information back onto the main display as needed.

It’s also worth noting that while you’ve become accustomed to working with a fixed screen size on the iPhone and then the iPad, that isn’t necessarily the case with the external screen. Depending on the device attached, it could be one of several different sizes (at least 640 × 480, 800 × 600, and 1024 × 768 are supported by the iPad). You may need to examine the bounds of the second screen and adjust the layout of your content accordingly.

Working with Documents

With iOS 3.2 on the iPad and then iOS 4.0 on the iPhone and iPod touch, iOS devices gained some functionality for working with documents. The details are beyond the scope of this book, but if you’re writing a document-based application that would benefit from sharing documents with other applications, this new functionality can be extremely useful. Apple’s “iPad Programming Guide” has more details, but we will provide a brief introduction to the key features.

Your application has always had a Documents directory where it could store information, but prior to iOS 3.2, it was largely hidden from the user. Now, if you add the key UIFileSharingEnabled with value true to YourApplicationName-Info.plist, then your users can use iTunes to add files to the Documents folder and retrieve documents that were created or edited on the iPad.

You can also register your application to deal with specific types of documents. You will need to edit your application’s plist and add a key like the following:

<key>CFBundleDocumentTypes</key>
<array>
  <dict>
    <key>LSItemContentTypes</key>
    <array>
      <string>public.plain-text</string>
    </array>
  </dict>
</array>

Each dictionary entry is a type of document that your application can handle. In addition to registering the types that you can handle, your application will also need to handle opening the document when requested. In the application:didFinishLaunchingWithOptions method, you will need to check the options dictionary that is passed in for an NSURL that specifies a document that another application has requested you open. Again, see the “iPad Programming Guide” for more information.

Finally, if you write an application that handles documents that it doesn’t necessarily know how to open (e.g., productivity applications), then it can use a UIDocumentInteractionController object to work with those documents. The document interaction controller works with the system to preview files in place and determine if they can be opened by another application.

Summary

In this chapter, you learned how to take an iPhone/iPod touch application and convert it to a universal application that handles rotation and takes advantage of the larger iPad display. You then learned how to use a split view on the iPad to enhance that application so that it provided easy navigation on the iPad while still retaining the standard navigation on the iPhone. All of these same techniques would be used when creating a universal application from scratch.

You also learned how to make MPMoviePlayerController use only part of the screen on the iPad so that there is space to place additional information or controls around the video. While working with the movie player you also got practice creating a second iPad application.

We also briefly introduced some of the other features that became available with the iPad initially and then in iOS 4 on the iPhone and the iPod touch when it was released in the summer of 2010.

With the completion of this chapter, you should be fairly comfortable creating your own universal applications or an iPad-specific application. iOS has a very rich feature set, so there are many things that we didn’t cover in this book, but you’ve had a chance to work with all of the main functionality that is needed for a typical application. For the more advanced features, explore Apple’s documentation and sample applications and you’ll have everything you need to create the next big hit in the iTunes App Store. Have fun!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset