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

Ray Wenderlich
What it will look like when we're done!

What it will look like when we're done!

I recently ported several of my apps from the iPhone to the iPad, and thought it would be useful to share some of what I learned along the way.

In this iPad tutorial, I’ll show you how to port an iPhone application to the iPad. We’ll take a simple app I wrote that displays a list of board games and lets you rate them, and we will port it to the iPad.

Along the way, we will cover how to test the device you’re running on, how to handle autosizing and orientation, how to make different versions of a XIBs for the iPad, how and when to use the new iPad elements, and more!

You may find it helpful to go through the iPad tutorials on How to use UISplitView and How to use UIPopoverController before you start, but it is not required.

The iPhone App We’ll Port

The app we’ll be porting is a simple app called “PortMe” that contains some of the elements found in many iPhone apps – table views, standard view controllers, and editing capability.

The iPhone App We'll Port

So grab a copy of the iPhone app we’ll port and check it out!

You’ll see that there are three view controllers. The first is a table view of the list of board games. The second is a view controller with a XIB to show the details for a board game. The third is a table view that lets the user rate a board game.

Look over the app and make sure you’re familiar with the structure. Once you’re done – let’s get to porting!

Upgrade Target for iPad

The initial step to port an iPhone to the iPad is almost misleadingly easy. Simply expand Targets and select “PortMe”, then click “Project\Upgrade Current Target for iPad”.

XCode Upgrade Current Target Dialog

You have two options here – one universal application, or two device-specific applications. If you choose “One universal application”, that will allow you to make an app that customers can buy once, and have it work on both the iPhone or the iPad. If you want customers to purchase them separately, you may wish to choose “Two device-specific applications.”

In our case we’re going to make a Universal application, so click that and click OK.

Let’s take a look at what this did for us. The first thing it did was to create a new file called “MainWindow-iPad.xib” inside the “Resources-iPad” folder.

If you open it up and double click the Window, you’ll notice a big iPad sized Window. The XIB also contains the objects that were in the old XIB – the navigation controller with the PortMeGameListController set as the root view controller.

Main Window iPad Version

Another thing this did was to set our project to link against the 3.2 SDK. You’ll notice that compiling for the 3.2 SDK isn’t even an option anymore (at least once you switch off of the old SDK for the first time):

3.2 SDK Options Only

We can see this in the Target Info as well; it changed the Base SDK to “iPhone Device 3.2″ and the Targeted Device Family to iPhone/iPad:

iPad Settings in Target Info

Well, let’s see what it looks like! Make sure “Simulator – 3.2″ is selected, and run the sucker. You’ll see the first screen doesn’t look too bad because the table view auto-resizes to the screen, but our second view controller doesn’t fare nearly so well.

First Draft of iPad Port

So we learned our first lesson of iPad porting – it’s not as simple as selecting the “Upgrade Current Target for iPad” option! Now we get to the fun and interesting part – reworking our app to work with the larger screen size and new requirements/opportunities.

Let’s start by fixing that messed up view!

Autosizing

When I set up the sample project, I just dragged over several UI elements into the view, and paid no attention whatsoever to the autosizing attributes – as could be a common case when an iPhone app is made without rotation support.

But now that we want our app to support both the small and big screen as well as (eventually) be able to support rotation, autosizing becomes very important. With autosizing, we can tell each UI element how it should react when the size of its parent view changes.

The easiest way to see this working is by trying it out ourselves! Open up PortMeGameDetailsController.xib, and double click the view.

For the top two labels (board game name and type), we want the labels grow in width as the view grows in width. So select those two labels and go to the third tab in the inspector. Down in the Autosizing section, set click on the light red areas until you get it looking like the following:

Autosizing Name/Type Labels

The animation to the right shows you the behavior based on your selection; in this case, it means that the labels should grow in width as the view expands, and they should maintain their current distance from the top, left, and right.

Now let’s take care of the rest. Set the UIImageView like the following:

This means that the UIImageView should grow in both width AND height as the view expands, and stay anchored to the edges of the view.

Set the text view like the following:

Autosizing UITextView

Set the label like the following:

Autosizing rating label

And finally, the button like the following:

Autosizing rate button

Let’s see how it looks! Make sure you save the XIB, then compile and run the project and you’ll see a new and improved view:

Autosizing Result

Definitely an improvement!

However there’s one major problem: if you try to rotate the simulator with “Hardware\Rotate Left” – the iPad rotates but the app doesn’t! And since supporting all orientations is a requirement for iPad apps, that would mean instant rejection at this point.

Luckily, since we’ve set our autosizing attributes correctly for our view (and since UITableViewController already supports rotation), we can fix with just a couple lines of code.

Add the following code to the end of PortMeGameListController.m, PortMeGameDetailsController.m, and PortMeGameRatingController.m:

- (BOOL)shouldAutorotateToInterfaceOrientation:
    (UIInterfaceOrientation)toInterfaceOrientation {
    return YES;
}

Compile and run the app, and now you should be able to rotate the phone to any direction and have the elements move correctly:

Orientation Result

So far so good! However, our major problem at this point is we’re still thinking in an iPhone mindset. On the iPad, there is so much screen real estate, that we want to make the most use of it rather than forcing users to drill down to multiple levels unnecessarily.

And this is exactly what the UISplitViewController and UIPopoverController are for! So let’s see how we can use these in our app – while reusing our existing view controllers along the way.

UISplitViewController Integration

UISplitViewControllers are designed so that you navigate to an item on the left hand side, and then see the details of the item on the right hand side.

This would be perfect for our app! We can put the list of board games on the left, and put the board game details on the right.

So let’s go ahead and integrate a UISplitViewController into our project. Open up MainWindow-iPad.xib, and drag a Split View Controller into the window, and delete the old Navigation Controller.

Expand the Split View Controller tree until you find the Table View Controller. In the Inspector, go to the fourth tab and set the Class to PortMeGameListController.

Now we have to set up the right hand side. Let’s think about this a minute. When the user taps “Rate This”, we currently push another view controller onto the stack. This means that we need the right side view controller also to be a UINavigationController (at least for now until we change that).

So drag a Navigation Controller on top of the View Controller for the right hand side, dig down and set the root view controller to “PortMeGameDetailsController.” When you’re done it should look like this:

UISplitView XIB settings

Ok now we need to hook this up to the code. Currently, the Application Delegate is set up to add the navController property as a subview to the main window. However, for the iPad, we want it to add the split view controller to the window instead.

This means that we need an outlet for the split view controller. We might be tempted to just declare it as normal – however keep in mind this app needs to work on both the iPhone (running iPhone OS 3.0-3.1.3) and the iPad (running iPhone OS 3.2). iPhone OS 3.2 is iPad only btw.

So this means that the older OS versions will not have the UISplitViewController class, and trying to declare variables of that type will cause problems. Therefore, we need to be much more careful about how we use those clasess, and use runtime checks for new symbols.

The easiest way to see how to do this is to try it out. Open up PortMeAppDelegate.h and add the following code:

// In the class interface
id _splitViewController;
 
// Afterwards
@property (nonatomic, retain) IBOutlet id splitViewController;

Here we declare the SplitViewController as a generic id so we can load up the class at runtime and avoid problems on the 3.0 OS.

Then add the following to PortMeAppDelegate.m:

// In synthesize section
@synthesize splitViewController = _splitViewController;
 
// In didFinishLaunchingWithOptions, replace window addSubview line with:
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {      
    UIView *view = [_splitViewController view];
    [window addSubview:view];
} else {
    [window addSubview:_navController.view];	
}
 
// In dealloc
self.splitViewController = nil;

The first line is the test you can use to see if your code is running on the iPad or not. If it is running on the iPad, we want to add the split view controller’s view as a subview of our window.

Since we have stored the split view as a generic object (rather than as the UISplitViewController class), we need to get the view by sending a message to it (rather than using dot notation).

That’s it for code! Last thing is to go back to MainWindow-iPad.xib and Control-drag from “Port Me App Delegate” to the “Split View Controller” and connect it to the “splitViewController” outlet, and Control-drag from “Port Me App Delegate” to the “Port Me Game List Controller” and connect it to the “gameListController” outlet.

Make sure you save the XIB, then compile and run the app, and switch to landscape mode. The list of games shows up OK on the left, but when you tap a game it shows up in the same navigation controller instead of on the right hand side:

Left side incorrectly showing detail view

So let’s fix that next!

Linking up the Detail View

As mentioned in the UISplitView iPad tutorial, there are many good ways to hook the left and the right sides of a split view together, but one approach that works particularly well is delegation.

So we’ll follow the same approach we did in that iPad tutorial and set up a protocol for “game selected” that the detail view will implemenet to refresh the view.

Actually, this is a good point where you can practice doing this on your own and make sure you remember how to do it. If you successfully implement it on your own, just skip to the next section. Otherwise, you can keep following along!

If you choose to continue following along, go to File\New, choose Objective-C class, make sure “Subclass of” is “NSObject”, and click “Next”. Name the file “GameSelectionDelegate” and click “Finish”.

Replace GameSelectionDelegate.h with the following:

#import <Foundation/Foundation.h>
 
@class Game;
 
@protocol GameSelectionDelegate
- (void)gameSelectionChanged:(Game *)curSelection;
@end

Then delete GameSelectionDelegate.m, since we don’t need it for a protocol.

Now, let’s modify the PortMeGameListController to take a GameSelectionDelegate. And add the following to PortMeGameListController.h:

// At top, under #import
#import "GameSelectionDelegate.h"
 
// Inside LeftViewController
id<GameSelectionDelegate> _delegate;
 
// Under @property
@property (nonatomic, assign) IBOutlet id<GameSelectionDelegate> delegate;

And add the following to PortMeGameListController.h:

// Under @implementation
@synthesize delegate = _delegate;
 
// Inside didSelectRowAtIndexPath, replace navigation push line with:
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
    if (_delegate != nil) {            
        [_delegate gameSelectionChanged:game];
    }
} else {
    [self.navigationController pushViewController:_detailsController animated:YES];
}
 
// Inside dealloc
self.delegate = nil;

Then, we’ll modify PortMeGameDetailsController to implement the delegate. Make the following changes to PortMeGameDetailsController.h:

// At top, under #import
#import "GameSelectionDelegate.h"
 
// Class declaration line
@interface PortMeGameDetailsController : UIViewController <GameSelectionDelegate> {

Then add the following to PortMeGameDetailsController.m:

// Replace viewWillAppear with the following:
- (void)refresh {
    _nameLabel.text = _game.name;
    _typeLabel.text = _game.type;
    _descrView.text = _game.descr;
    _ratingLabel.text = [NSString 
        stringWithFormat:@"Your rating: %@", _game.rating];
}
 
- (void) viewWillAppear:(BOOL)animated {
    [self refresh];
}
 
- (void)gameSelectionChanged:(Game *)curSelection {
    self.game = curSelection;
    [self refresh];
}

And for the final step, we can actually connect the delegate using Interface Builder since we marked the delegate as an IBOutlet. Open up MainWindow-iPad.xib and Control-drag from “Port Me Game List Controller” to “Port Me Game Details Controller” and connect it to the delegate outlet.

That’s it! Compile and run the app, and you now should be able to select between the board games like the following:

Right side hooked up with left

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

Final Notes

That’s all we’re going to do for this iPad tutorial, but for your reference, before you’d be able to submit this to the App Store, you’d need to do a couple more things:

  • Add default images for the iPad. You’ll need the Default.jpg like you normally would (this will be used on the iPhone). But now you’ll also need Default-Landscape.jpg and Default-Portrait.jpg for the iPhone screen size. The sizes of these images needs to be 1024×748 (landscape) and 768×1004 (portrait) – basically the size of the screen minus the status bar.
  • Add UISupportedInterfaceOrientations to your info.plist and add in all the orientations the app supports (for us, all of them!)
  • Add a 72×72 icon into the project to be used on the iPad, and create a new key CFBundleIconFiles to your info.plist, and put the names of both icons inside as an array. The OS will pick the appropriate icon based on size.

Gratuitous Tip

A tip for those of you porting your iPhone apps out there. Be really careful never to have two popover controllers visible at the same time – it’s confusing to users and your app will get rejected if you do (I know from experience, heh!)

The easy fix is just to dismiss any current popover controllers when you display another. But there’s a tricky case where you need to launch a popover controller from the left side of a split view – since the left side could itself be a popover in portrait mode!

For this case, a good fix is to call setContentViewController on your existing popover to replace its contents with what you *would have* put in the new popover such as follows:

[_sidebarPopoverController setContentViewController:
    wasGonnaBeInMySecondPopoverViewController animated:YES];

Where To Go From Here?

By this time you should have a firm grasp on the major tasks you’ll come across while porting an iPhone app to the iPad.

From here I’d recommend reading through Apple’s iPad Programming Guide, if you haven’t already. It discusses a lot of what we just covered above, as well as much more useful information!

Here is the sample project with the completed iPad port!

I’d be curious to hear about your experiences porting iPhone apps as well!

Ray Wenderlich

Ray is an indie software developer currently focusing on iPhone and iPad development, and the administrator of this site. He’s the founder of a small iPhone development studio called Razeware, and is passionate both about making apps and teaching others the techniques to make them.

When Ray’s not programming, he’s probably playing video games, role playing games, or board games.

User Comments

23 Comments

[ 1 , 2 ]
  • Thanks for the tutorial :-)

    Up above you said:

    Expand the Split View Controller tree until you find the Table View Controller. In the Inspector, go to the fourth tab and set the Class to PortMeGameListController.

    There is no Table View Controller in the tree, are you missing a step?
    PeterPurple
  • Hi guys,

    I just finished my iPhone app and I want to make it Universal. I read the original post but since it's been a while now, I'd like to re-check things with you and explain a bit my scenario.

    What I got:

    - Simple iPhone app, recently created (iOS 5 - Storyboard), with three screens.
    - My app represents a table with three cards that you can flip touching them. The user can input (on the second screen) text to be displayed on the cards.
    - When I created the project I checked "Universal" so I have two Storyboards. After that nothing else I did had to do with iPad (except for a line on my "contact support" email option where I used UIModalPresentationPageSheet).

    Here a glimpse of my app's storyboard:



    As I told you before, pretty simple beginner's application.

    What I'd like to accomplish:

    - Same app on the iPad: my application is so straightforward I don't have any use for split views or details. I just want the same objects and layout but with bigger and better graphics (table, cards, etc).

    I like it because it'd make a great introduction-level migration.

    I have no idea where to start. When I run the iPad simulator a white screen comes up and that's it.

    Can you please point me in the right direction?

    Thanks in advanced,
    juan294
  • Hi guys,

    is this post closed? Should I open a new one with my question?

    Let me know to move on with the new post.

    Thanks!
    juan294
  • Hi,
    Really Enjoyed the tutorial.
    Am facing an issue in ios 5.1 onwards , using xcode 4.5.2
    When I am trying to resize the popover for the side bar in split view controller, even though the popover's height is getting set as 300, still it occupies the full length in portrait mode.Have followed port me as an example only
    Also since this project was created for previous versions I understand. I have done by changing device type to universal. But what if I needed to create a seperate app for ipad. What should be done?


    Thanks
    megha
  • Just a quick note: this code will not run properly in iOS6 (specifically, the rotate no longer works).

    To fix this for iOS6, you will need to change to this in PortMeAppDelegate's Start Up Window block

    Code: Select all
        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {     
       //     UIView *view = [_splitViewController view];
       //     [window addSubview:view];
            self.window.rootViewController = self.splitViewController;

        } else {
       //     [window addSubview:_navController.view];
            self.window.rootViewController = self.navController;
        }
    AlphaTwo
  • Does anyone know where I might find some updated information about this topic for IOS 6 and maybe even for the soon release of IOS 7??
    Any help would be greatly appreciated. I am looking on stack overflow and googling, but anyone who can post something here would be a massive time saver cheers.
    Lordmorrison
  • Please upgrade the code for iOS 7
    surajkthomas
  • please upgrade the tutorial for iOS 7 and for using storyborad.
    surajkthomas
[ 1 , 2 ]

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in May: Procedural Level Generation in Games with Kim Pedersen.

Sign Up - May

Coming up in June: WWDC Keynote - Podcasters React! with the podcasting team.

Sign Up - June

Vote For Our Next Book!

Help us choose the topic for our next book we write! (Choose up to three topics.)

    Loading ... Loading ...

Our Books

Our Team

Tutorial Team

  • Marcelo Fabri

... 55 total!

Editorial Team

... 22 total!

Code Team

  • Orta Therox

... 1 total!

Translation Team

  • David Hidalgo
  • Lin Ma
  • Dave Harry

... 38 total!

Subject Matter Experts

... 4 total!