iPad Tutorial for iOS: How To Port an iPhone Application to the iPad

An iPad tutorial that shows you how to port an iPhone application to the iPad. By Ray Wenderlich.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Adding a Popover List

Just like we did in the UISplitView iPad tutorial, it’s standard practice to have a way to bring up the left hand side when you’re in portrait mode by tapping a button in the toolbar.

Although what we are about to do is similar to the way we did in the last iPad tutorial, there are a few changes due to this being a Universal app, and since the right side is a navigation controller rather than a view with a toolbar, so you may want to keep following along here.

Make the following changes to PortMeGameDetailsController.h:

// Add UISplitViewControllerDelegate to the list of protocols
@interface PortMeGameDetailsController : UIViewController 
    <GameSelectionDelegate, UISplitViewControllerDelegate> { 

// Inside the class definition
id _popover;
 
// In the property section
@property (nonatomic, retain) id popover;

This is similar to the way we did in the past iPad tutorial, except we declare the UIPopoverController as a generic object to avoid problems on the 3.0-3.1.3 OS.

Then add the following to PortMeGameDetailsController.m:

// In synthesize section
@synthesize popover = _popover;

// In dealloc and viewDidUnload
self.popover = nil;

// In gameSelectionChanged
if (_popover != nil) {
    [_popover dismissPopoverAnimated:YES];
}

// New functions
- (void)splitViewController: (UISplitViewController*)svc 
    willHideViewController:(UIViewController *)aViewController 
    withBarButtonItem:(UIBarButtonItem*)barButtonItem 
    forPopoverController: (UIPopoverController*)pc {
    barButtonItem.title = @"Sidebar";    
    
    UINavigationItem *navItem = [self navigationItem];
    [navItem setLeftBarButtonItem:barButtonItem animated:YES];
    
    self.popover = pc;
}

- (void)splitViewController: (UISplitViewController*)svc 
    willShowViewController:(UIViewController *)aViewController 
    invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem {    
    
    UINavigationItem *navItem = [self navigationItem];
    [navItem setLeftBarButtonItem:nil animated:YES];
    
    self.popover = nil;
    
}

Then add the following to PortMeGameListController.m to make the popover be a bit smaller rather than the full height of the screen:

// In viewDidLoad
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
    [self setContentSizeForViewInPopover:CGSizeMake(320.0, 300.0)];
}

Note we have to be careful to only run this if we’re on the iPad (and use message passing rather than dot notation) since we may be running on the 3.0 OS.

One last thing – go back to MainWindow-iPad.xib and control-drag from “Split View Controller” to “Port Me Game Deetails Controller” to set the right view controller as the delegate of the split view controller.

Compile and run the app, and if all goes well you should have an item on your nav controller bar that you can tap to bring up the list of board games!

Popover View in UINavigationController bar

Using UIPopoverController

There’s still another area in our app where we’re thinking too much in iPhone terms – the rate button. Rather than going to a completely separate screen, this would be much better served by using a UIPopoverController on the iPad.

This is similar to the method we used in the UIPopoverController iPad tutorial but again with some slight differences.

Add the following to PortMeGameDetailsController.h:

// Inside class declaration
id _ratingPopover;

// In property section
@property (nonatomic, retain) id ratingPopover;

Again note the use of the generic objects here since this is a Universal app.

And the following to PortMeGameDetailsController.m:

// In synthesize section
@synthesize ratingPopover = _ratingPopover;

// In dealloc AND viewDidUnload
self.ratingPopover = nil;

// In rateTapped, replace pushViewController with the following:
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
    UIButton *button = (UIButton *)sender;
    if (_ratingPopover == nil) {
        Class classPopoverController = NSClassFromString(@"UIPopoverController");
        if (classPopoverController) {
            self.ratingPopover = [[[classPopoverController alloc]
                initWithContentViewController:_ratingController] autorelease];
        }
    }
    [_ratingPopover presentPopoverFromRect:button.frame inView:self.view 
        permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];        
} else {
    [self.navigationController pushViewController:_ratingController animated:YES];
}

Here we again switch based on whether we’re running on the iPad or not, and either present a popover or push onto the navigation controller as usual.

However note we have to create the popover controller by looking up the class name from a string, and then constructing it. This is again a case where we can’t use the actual class name since we may be running on a 3.0 device.

We also use a slightly different method to present the popover here, which lets us specify a rectangle which the popover will point to with an arrow.

Next switch over to PortMeGameRatingController.m and add the following:

if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
    [self setContentSizeForViewInPopover:CGSizeMake(320.0, 300.0)];
}

Compile and run the app, and if you select a game and tap rate you should see the following:

Rate Popover

Getting a Better Detail View

So far, our port is coming along quite well, and is behaving much more iPad like. However, our detail view could still use some work.

First, the image in the background was actually made for the iPhone, and is just being scaled to the larger screen. So it looks a bit grainy at the larger resolution.

Secondly, we could make better use of the increased screen real estate by making some of the text bigger, or moving around some of the labels to make better use of the space.

When you start to get a lot of changes you’d like to make to the view like this, you COULD do everything programatically by modifying the UI elements in code and switching on the UI_USER_INTERFACE_IDIOM(), but it’s often easier to just make a custom view XIB for the iPad.

So let’s give that a shot! Open up PortMeGameDetailsController.xib, and click “File\Create iPad Version Using Autosizing Masks”. It will create an Untitled XIB – save the XIB in the project folder and name it “PortMeGameDetailsController-iPad.xib”.

Then download a copy of a higher resolution background image and set the UIImageView’s image to “bg-iPad.jpg”. (Image credit: szajmon).

Then, have some fun improving the interface a bit. I’d recommend the moving the board game type to the right of board game name, aligned right. Make sure to fix the autosizing masks appropriately! Also, you should probably improve the font size of everything.

Save the XIB. The last step is to make sure that the iPad XIB is the one that is loaded. Open up MainWindow-iPad.xib, select the “Port Me Game Details Controller”, and go to the first tab of the Inspector. Set the NIB name to “PortMeGameDetailsController-iPad.xib”.

Save the XIB, compile and run the project. If all goes well you should see the new view like the following:

iPhone App Ported to iPad Result!

Testing on the iPhone

Ok, our app is looking pretty good on the iPad. The next step is to try it again on the iPhone and make sure we didn’t break anything!

Erm, but we have a small problem. By default the simulator launches directly into the iPad simulator (not the iPhone simulator).

So what do we do? The workaround I used was to just test on the device. However if that is annoying, Noel from Games from Within has written a nice post about Universal Apps that includes a workaround to run in the iPhone simulator.

Switch to “Device – 3.2” and try to compile and run the app. Oops – we get an error message like the following:

Minimum OS version of 3.2 error dialog

That’s OK. We can fix this by a setting in our Target Info. Basicaly, even though we’re compiling against the 3.2 SDK, we can deploy to a different SDK.

This is why we were being so careful about using all of those runtime checks – we have the 3.2 headers available to us but maybe not the actual code when we run on the device.

So go to Target Info and set the iPhone OS Deployment Target to iPhone OS 3.0 as follows:

Deployment Target for Universal App testing on iPhone

Try running again and this time it should show up just fine on our iPhone – and since we were careful about runtime checks, everything works like normal! :]

Screenshot of Universal App on the iPhone