iPad



Relevant resources


Documentation


Web


Books


Sample applications - Apple


Sample applications - class


With the recent launch of the iPad, we now have a third iPhone OS platform to join the iPhone and iPod touch.  While sharing the same underlying OS as the existing devices, the iPad brings with it iPhone OS 3.2, which has a number of additional APIs and extensions to existing frameworks.


However, the biggest difference (no pun intended) between the iPad and its pocket-sized cousins is the physical size of the device and corresponding increase in processing power.  A much larger screen does far more than just provide more interface space, it changes the way that people interact with the device.  iPhone applications simply scaled to the new screen size generally look and feel terrible.


Some smaller user interface conventions have changed, such as UIToolbars typically being present on the top of the screen, rather than the bottom.  Also, given the awkward nature of shaking an iPad, it is recommended that undo / redo functions be located in the onscreen keyboard or as a button in the toolbar.  To differentiate between undo and redo actions, you should present an action sheet below the button when held that gives both options (assuming that both are viable actions at that point in time).


iPad applications should support both landscape and portrait orientations, with few exceptions.  This runs counter to the iPhone, where it was perfectly acceptable for most applications to only handle one orientation or the other.


For other examples of the general thinking behind the new interface paradigm, I recommend reading the new iPad Human Interface Guidelines.


UIPopoverController and UISplitViewController


In some cases, moving to the iPad may require completely rethinking the design of an existing iPhone application, but Apple has provided new interface elements to ease the migration from the mindset of an iPhone application to an iPad one.  These are the split view and the popover.


UIPopoverController


UIPopoverController addresses the need for presenting contextual options, acting like a pull-down or right-click menu would in a desktop application.  Action sheets take on this form, as well, instead of modally blocking the whole screen.


A UIPopoverController is created as follows:


downloadOptionsPopover = [[UIPopoverController alloc] initWithContentViewController:downloadNavigationController];

[downloadOptionsPopover setDelegate:self];


As you can see, a view controller is passed into the UIPopoverController containing what will be displayed in the popover.  Any type of view content can be provided, from navigation controllers to completely custom views.


To present a UIPopoverController from a toolbar button, you use code like the following:


[downloadOptionsPopover presentPopoverFromBarButtonItem:sender permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];


The display of popovers can be triggered by events other than taps on toolbar buttons.  To present a popover from an arbitrary location in space, you can use code like the following:


[downloadOptionsPopover presentPopoverFromRect:[view frame] inView:superView permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];


This will present the popover from a given rectangle (typically the frame of a view or layer), relative the the coordinate space of the specified enclosing view.


By default, the popover will grow vertically to fit the height of the view whose controller you provided to the popover.  In the case of table views, this will cause them to take up the entire height of the screen, which may be undesirable.  To set a specific height for a view controller within a popover, you can use the new /insert property here/ property on UIViewController.  For example, you could set the height in a custom view controller's initialization using code like the following:


if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)

{

self.contentSizeForViewInPopover = CGSizeMake(320.0, 600.0);

}


As you can see, we only execute this code on iPad devices using the conditional shown above.  This will be described in more detail when we cover universal applications.


Apple's Human Interface Guidelines require you to only allow one popover to be shown at a time, so you will need to provide some code to manage the display and hiding of popovers as new ones are brought onto the screen.  The easiest way to do this is to hold on to your UIPopoverControllers as instance variables and manually dismiss them when new ones are brought onto the screen using code like


[popoverToDismiss dismissPopoverAnimated:YES];


For finer control over popovers, you can make your overall controller the delegate of the popover controllers and have it conform to the UIPopoverControllerDelegate protocol.  This protocol includes methods like


- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController;

- (BOOL)popoverControllerShouldDismissPopover:(UIPopoverController *)popoverController;


where you can clean up after the popovers when they are dismissed, or even prevent them from being dismissed under certain conditions.


Action sheets have gained a popover-like interface, which you can activate using code like the following:


[actionSheet showFromBarButtonItem:visualizationBarButton animated:YES];


These alert sheets behave just like normal alert sheets, so you handle taps on their buttons the same way that you do their modal iPhone counterparts.  You may need to dismiss them if other popovers are displayed, using code like the following:


[actionSheet dismissWithClickedButtonIndex:2 animated:YES];


UISplitViewController


A UISplitViewController provide a means for displaying more information on the screen when in the landscape orientation.  UINavigationControllers don't work well stretched out across the screen, so the preferred layout of an iPad application is for it to have a large central detail or work view, with any tables or navigation controllers for selection of items placed in the left view of a split view or within a popover.


A UISplitViewController consists of a smaller left-hand view (which should be 320 pixels in width, mimicking the iPhone's portrait orientation width) and a larger right-hand view taking up the remainder of the display.  Usually, the left view controller will be a UINavigationController, containing one or more UITableViewControllers in a hierarchy.


A UISplitViewController will most commonly be constructed via Interface Builder, but the following is an example of generating one programmatically:


UISplitViewController *newSplitViewController = [[UISplitViewController alloc] init];

newSplitViewController.viewControllers = [NSArray arrayWithObjects:tableNavigationController, rootViewController, nil];

newSplitViewController.delegate = rootViewController;

[window addSubview:newSplitViewController.view];

[newSplitViewController release];


In this example, we create a UISplitViewController and then assign it an NSArray of two view controllers, corresponding to the left-hand navigation controller and the right-hand detail view.  We then make the rootViewController the delegate of the split view controller, for reasons we'll discuss in a bit, and add the view of the UISplitViewController to the main window.


A split view does not work well in a portrait orientation, so when the application starts in portrait or is rotated to that orientation, the left-hand pane is not displayed.  Instead, it is suggested that you provide a button in the left-hand side of a toolbar you've created at the top of the screen which, when tapped, will present a UIPopoverController containing the view controller you had in the left-hand pane of the split view.


Thankfully, Apple has made this easy to implement in code.  To do this, you will need to make your overall controller the delegate of the split view controller, and have it conform to the UISplitViewControllerDelegate protocol by implementing the following methods:


- (void)splitViewController:(UISplitViewController*)svc willHideViewController:(UIViewController *)aViewController withBarButtonItem:(UIBarButtonItem*)barButtonItem forPopoverController:(UIPopoverController*)pc

{

[(UINavigationController *)aViewController navigationBar].barStyle = UIBarStyleBlackOpaque;

NSMutableArray *items = [[mainToolbar items] mutableCopy];

[items insertObject:barButtonItem atIndex:0];

[mainToolbar setItems:items animated:YES];

[items release];

}


- (void)splitViewController:(UISplitViewController*)svc willShowViewController:(UIViewController *)aViewController invalidatingBarButtonItem:(UIBarButtonItem *)button

{

[(UINavigationController *)aViewController navigationBar].barStyle = UIBarStyleBlackOpaque;

NSMutableArray *items = [[mainToolbar items] mutableCopy];

[items removeObjectAtIndex:0];

[mainToolbar setItems:items animated:YES];

[items release];

}


As you can see, these delegate methods pass in a UIToolBarButton that is all wired up to present a popover controller for the left-hand contents, as well as notify you when the device has been rotated to landscape orientation, the UISplitViewController is now fully displayed, and the button should be removed from the toolbar.  The lines setting the navigation bar of the left-hand view to opaque black were a workaround for a bug in the Simulator, and probably won't be needed in your case.  


If you need to hide other popovers when the popover for the split view controller's left-hand view is displayed, you can implement the delegate method


- (void)splitViewController:(UISplitViewController*)svc popoverController:(UIPopoverController*)pc willPresentViewController:(UIViewController *)aViewController


When setting up your view controllers in the split view, you need to properly configure each of them to support autorotation.  If a single view controller returns NO to -shouldAutorotateToInterfaceOrientation: for a particular orientation, the whole split view will not rotate.  Again, unless you have a very good reason for it, all orientations of the iPad should be supported in your application.


Universal iPhone / iPad applications


Many iPhone developers will want to port their existing applications across to the iPad.  Some will choose to do this by creating a distinct iPad application that shares some of the codebase for the original iPhone application.  Apple's recommendation (and my personal one) is for you to create a so-called universal application which will run on both classes of devices, presenting an appropriate interface for whichever one it's running on.


For those creating new applications that will target both iPhone and iPad, Xcode's Window-based application now provides an option to generate a universal application.  This will populate the correct settings in the application for you, and give you a good starting-off point.


However, many developers will have existing iPhone applications that need to be made universal.  The first step in this process is to load your iPhone application project into Xcode, navigate to the target that will generate your application, and choose the Upgrade Current Target for iPad menu option.  Select the option to build a universal application and Xcode will automatically fill in the appropriate build settings that are necessary to enable targeting both types of iPhone OS platforms.  You can change all of these settings manually, but it is not recommended.


As of this writing, the APIs of iPhone OS 3.2 that enable support for iPads are not available on iPhones, so we will need to take a look at some special build settings.  All universal and iPad-only applications will need to be built using the 3.2 SDK, so that the new APIs can be compiled against.  In order to support iPhones running 3.0 and higher, you will need to go to the build settings for your application target and set the Deployment Target to the earliest iPhone OS version you want to support with your application (with 3.0 being as far back as you can go).


However, there's more to making your application universal than just adjusting a few build settings.  Because you will be linking against frameworks that have portions which may not exist on older OS versions, you will need to weak-link them in.  To indicate that a weak link should be used for a framework, go to the target for your application and inspect it.  At the bottom of the General tab will be a list of Linked Libraries, along with their type.  Normally, they will be set to Required, but you will want to change these to Weak.  For an iPad / iPhone application, at least UIKit needs to be weak linked in this fashion or you will encounter errors.


Once the build environment has been configured, you will need to adapt your code to support both classes of devices.  The cleanest way to do this is to have branching paths of interfaces, one for the iPhone / iPod touch, the other for the iPad.


If you have code that you would like to run only if the device is an iPad, you can use code like the following:


+ (BOOL)isRunningOniPad;

{

static BOOL hasCheckediPadStatus = NO;

static BOOL isRunningOniPad = NO;

if (!hasCheckediPadStatus)

{

if ([[UIDevice currentDevice] respondsToSelector:@selector(userInterfaceIdiom)])

{

if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)

{

isRunningOniPad = YES;

hasCheckediPadStatus = YES;

return isRunningOniPad;

}

}

hasCheckediPadStatus = YES;

}

return isRunningOniPad;

}


I've defined this as a class method, typically placed in the application delegate so that it can be used to fork your interface setup in one of two directions, depending on what device is involved.  I cache the result of the check in a static variable, so that this can be slightly faster if called many times (for example, if each cell in a table needs to be formatted differently for the two classes of devices).  The method first checks to see if the -userInterfaceIdiom method is implemented on UIDevice.  This method, newly added in iPhone OS 3.2, returns either UIUserInterfaceIdiomPhone or UIUserInterfaceIdiomPad to specify which class of device the current one belongs to.


Of course, there's a simpler way of doing this, which I discovered after I'd written all this.  However, the above demonstrates how you can check for the existance of specific methods on known classes.  The easier way to do this would be to use 


if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)

{

// Do iPad-specific actions here

}

else

{

// Do iPhone and iPod alternatives here

}


You can also check for the availability of specific classes using code like the following:


Class splitViewControllerClass = (NSClassFromString(@"UISplitViewController"));

if (splitViewControllerClass != nil)

{

// Class exists, use it

}

else

{

// Class is not present, fall back to old code or don't do anything

}


These runtime checks for additional functionality will also become important when iPhone OS 4.0 hits.  For more on the topic, see the MailComposer sample application, which was designed by Apple to help people migrate from OS 2.x to 3.0 by targeting 2.x and selectively enabling 3.x features if support was detected.


In general, if you need specific API support for something, don't rely on device type checks but use the methods above to see if the API is present on the current OS.  If you need to display certain interface elements only on a specific device type, then you should check for the user interface idiom.


Beyond the build settings and conditional code execution, you'll need to update a few resources.  First, you'll need to create a new 72 x 72 pixel in PNG format to use on the higher-resolution iPad home screen.  To have this new icon coexist with your iPhone-sized 57 x 57 pixel icon, you'll want to edit your Info.plist to have a section looking something like:


<key>CFBundleIconFile</key>

<string>iPhoneIcon.png</string>

<key>CFBundleIconFiles</key>

<array>

<string>iPhoneIcon.png</string>

<string>iPadIcon.png</string>

</array>


3.0 and 3.1 iPhones will only recognize the first key, using that icon.  iPads and newer OS iPhones will parse the list in the second key to determine the appropriate icon for their screen size.


While editing the Info.plist, you will want to make sure that your application can start in all possible orientations by adding a section like the following:


<key>UISupportedInterfaceOrientations</key>

<array>

<string>UIInterfaceOrientationPortrait</string>

<string>UIInterfaceOrientationPortraitUpsideDown</string>

<string>UIInterfaceOrientationLandscapeLeft</string>

<string>UIInterfaceOrientationLandscapeRight</string>

</array>


The larger screen of the iPad will require new default images for startup.  For the iPhone, you provided a 320 x 480 (with status bar) or 300 x 480 (without status bar) Default.png image to be displayed during the loading of your application.  On the iPad, you need to provide two more images: a 1024 x 768 or 1024 x 748 Default-Landscape.png and a 768 x 1024 or 768 x 1004 Default-Portrait.png to account for the two classes of orientations your iPad application can start in.


File sharing and document handling


iPhone OS 3.2 brings with it some new capabilities for sharing documents between the desktop and your iPhone / iPad applications.  This is to be expected, given that the iPad will be used as a document creation and editing tool, as well as a viewer.


To enable basic file sharing support, simply add the following to your application's Info.plist:


<key>UIFileSharingEnabled</key>

<true/>


This will cause your application to share its /Documents directory in iTunes.  Every file in your Documents directory will be visible in iTunes when the device is connected, underneath the listing of Applications in the Apps tab.  This is a flat list, so you are not allowed to use subdirectories to organize files in the Documents directory.  


Your application should assume that files will be added and removed during its execution, so you should keep checking the file list and updating the interface as necessary.


In addition to file sharing, your application can register to handle certain document types, received through email or other means.  To specify which types of documents your application can open, you will need to add a section like the following to its Info.plist:


<key>CFBundleDocumentTypes</key>

<array>

<dict>

<key>CFBundleTypeIconFiles</key>

<array>

<string>Document-molecules-320.png</string>

<string>Document-molecules-64.png</string>

</array>

<key>CFBundleTypeName</key>

<string>Molecules Structure File</string>

<key>CFBundleTypeRole</key>

<string>Viewer</string>

<key>LSHandlerRank</key>

<string>Owner</string>

<key>LSItemContentTypes</key>

<array>

<string>com.sunsetlakesoftware.molecules.pdb</string>

<string>org.gnu.gnu-zip-archive</string>

</array>

</dict>

</array>


Two images are provided that will be used as icons for the supported types in Mail and other applications capable of showing documents.  The LSItemContentTypes key lets you provide an array of Uniform Type Identifiers (UTIs) that your application can open.  For a list of system-defined UTIs, see Apple's Uniform Type Identifiers Reference.  Even more detail on UTIs can be found in Apple's Uniform Type Identifiers Overview.  Those guides reside in the Mac developer center, because this capability has been ported across from the Mac.


One of the UTIs used in the above example was system-defined, but the other was an application-specific UTI.  The application-specific UTI will need to be exported so that other applications on the system can be made aware of it.  To do this, you would add a section to your Info.plist like the following:


<key>UTExportedTypeDeclarations</key>

<array>

<dict>

<key>UTTypeConformsTo</key>

<array>

<string>public.plain-text</string>

<string>public.text</string>

</array>

<key>UTTypeDescription</key>

<string>Molecules Structure File</string>

<key>UTTypeIdentifier</key>

<string>com.sunsetlakesoftware.molecules.pdb</string>

<key>UTTypeTagSpecification</key>

<dict>

<key>public.filename-extension</key>

<string>pdb</string>

<key>public.mime-type</key>

<string>chemical/x-pdb</string>

</dict>

</dict>

</array>


This particular example exports the com.sunsetlakesoftware.molecules.pdb UTI with the .pdb file extension, corresponding to the MIME type chemical/x-pdb.


With this in place, your application will be able to handle documents attached to emails or from other applications on the system.  In Mail, you can tap-and-hold to bring up a list of applications that can open a particular attachment.


When the attachment is opened, your application will be started and you will need to handle the processing of this file in your -application:didFinishLaunchingWithOptions: application delegate method.  It appears that files loaded in this manner from Mail are copied into your application's Documents directory under a subdirectory corresponding to what email box they arrived in.  You can get the URL for this file within the application delegate method using code like the following:


NSURL *url = (NSURL *)[launchOptions valueForKey:UIApplicationLaunchOptionsURLKey];


Note that this is the same approach we used for handling custom URL schemes.  You can separate the file URLs from others by using code like the following:


if ([url isFileURL])

{

// Handle file being passed in

}

else

{

// Handle custom URL scheme

}


iPhone OS 3.2 also introduces the concept of the UIDocumentInteractionController, which lets you manage files that your application may not know how to open.  It is the counterpart to the above document-handling code.


Gesture recognizers


A significant addition in iPhone OS 3.2 is the gesture recognizer.  These gesture recognizers make handling taps, swipes, pinches, and other gestures trivial to respond to.  They remove a lot of the code we discussed when talking about touch events.


All gesture recognizers are subclasses of UIGestureRecognizer.  These subclasses include


When you create a gesture recognizer, you can add it to a view and have it trigger actions when the gesture it looks for is used.  For example, the following code sets up a gesture recognizer that responds to a double-tap on a view and triggers the -handleDoubleTap: method when one occurs:


UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTap:)];

[doubleTapRecognizer setNumberOfTapsRequired:2];

[view addGestureRecognizer:doubleTapRecognizer];

[doubleTapRecognizer release];


This cuts out the need to subclass UIView or have your UIViewController implement -touchesBegan:withEvent: or -touchesEnded:withEvent: delegate methods.


Other gesture recognizers have options that let you customize what they respond to.  UISwipeGestureRecognizers let you set the direction that swipes are recognized from using the direction property.  This property has potential values of UISwipeGestureRecognizerDirectionRight, UISwipeGestureRecognizerDirectionLeft, UISwipeGestureRecognizerDirectionUp, and UISwipeGestureRecognizerDirectionDown.


You can also set the numberOfTouches property to require more than one finger be performing the swipe gesture.


Taps and swipes are single actions that trigger a specific response.  Other gestures, like pinches, are more continuous in nature.  The callback action you specify will be triggered continuously during the gesture.  Within that callback, you can take action to change state.  For example, you can use the following to deal with a pinch gesture:


- (void)handlePinch:(UIPinchGestureRecognizer)recognizer

{

CGFloat newScale = [recognizer scale];

// Adjust scale of your element here

}


This uses the scale property on UIPinchGestureRecognizer to let you know how far the view should be scaled based on its starting value of 1.0.  You can also read the velocity property to determine how fast this gesture is taking place.


UIPanGestureRecognizer has similar translationInView: and velocityInView: methods, and UIRotationGestureRecognizer has its rotation property.


Custom keyboards and text handling


The iPad is intended to be used for more serious text editing than the iPhone, so iPhone OS 3.2 bring with it the ability to set custom accessory views on top of the keyboard, and even custom keyboards.  To set a custom accessory view to be displayed when a UITextField or UITextView (or any UIResponder subclass) is edited, create a view and set it to the inputAccessoryView property of the view to be edited.  This will now appear above the keyboard when the keyboard is displayed.


Likewise, a custom keyboard can be set by creating a view and setting it to the view to be edited's inputView property.


Apple has brought over the advanced Core Text system from the Mac to iPhone OS 3.2, letting you do advanced text manipulation in your applications.  The full scope of Core Text is beyond what we can cover, so I direct you to the Core Text Programming Guide for more.  Core Text works with NSAttributedStrings, another class brought over from the Mac that let you construct strings with complex intra-string formatting.


Spell-checking is now provided via the UITextChecker class.


With the introduction of NSAttributedStrings to the iPhone OS in 3.2, Apple has also brought across CATextLayer to Core Animation, for which you had to roll your own implementation if you wanted something like it before.


Support for external displays


New in iPhone OS 3.2 is the ability for applications to display a portion of their content to an external monitor.  One of the accessories for the iPad is a VGA adapter that plugs into the 30-pin dock connector of the iPad, so that applications which make use of external displays, like Keynote, can be used in a presentation mode.


Unfortunately, the screen of the iPad is not automatically mirrored on the external display, so you have to do a little work in order to manage projecting some of your content onto it.


To determine if more than one screen is attached, you can use code like the following:


if ([[UIScreen screens] count] > 1)

{

// External screen attached

}

else

{

// Only local screen present

}


You will need to listen for notifications to determine when screens have been attached or removed.  To do this, listen for the UIScreenDidConnectNotification and the UIScreenDidDisconnectNotification.  In the case of the UIScreenDidConnectNotification, the object passed into the notification is the UIScreen instance representing the new screen.


To present content onto a new screen, you need to create a new UIWindow instance and set its screen property to the new UIScreen.  For example, the following code will create a new UIWindow and display it on the externalScreen UIScreen instance:


CGRect externalBounds = [externalScreen bounds];

externalWindow = [[UIWindow alloc] initWithFrame:externalBounds];

UIView *backgroundView = [[UIView alloc]  initWithFrame:externalBounds];

backgroundView.backgroundColor = [UIColor whiteColor];

[externalWindow addSubview:backgroundView];

[backgroundView release];

externalWindow.screen = externalScreen;

[externalWindow makeKeyAndVisible];


Screen resolutions up to 1024 x 768 or 1280 x 720 are supported via the VGA adapter.  You can examine which display modes are available for use on the connected display by looking at the availableModes property of the UIScreen instance corresponding to that display.  You can select a particular screen mode by setting that mode to the currentMode property of the UIScreen instance.


New graphics abstractions


iPhone OS 3.2 introduces a couple of abstractions around Core Graphics drawing elements: UIBezierPath and UIKit PDF generation.  UIBezierPath acts as a Cocoa wrapper around a CGPathRef, letting you generate complex paths without the procedural C functions required in normal Quartz drawing.


New presentation styles for modal views


To fit the larger interface of the iPad, you now can present modal view controllers in a few different ways.  Using the new modalPresentationStyle property on UIViewController, you can set the style of the modal view controller to be UIModalPresentationFullScreen, UIModalPresentationPageSheet, or UIModalPresentationFormSheet, rather than always appearing full screen.  You see examples of this partial modal display when composing a new message in Mail on the iPad.


There's also a new transition style for presenting modal view controllers: UIModalTransitionStylePartialCurl, which replicates the effect seen in the Maps application when you curl up the page to reveal the settings underneath.


New options in Xcode


Even the tools themselves added a few new capabilities when going to Xcode 3.2.2 in the new SDK.  You can now Build and Archive your App Store distribution builds, which will store your compiled application and its debug symbols in an archive directory at Library/Mobile Device/Archived Applications.  These archived versions will also appear in the Xcode Organizer.


The archived applications can be validated before submission to the App Store using the Validate Application... button in the Archived Applications section of the Organizer.  This will run the standard automated tests that the App Store does on your application bundle, testing for correct codesigning, icons, etc.  It does not guarantee that your application will pass review, it just lets you test one aspect of the submission process.


If you have entered in all the metadata for a new application or update to an existing one in iTunes Connect, you can click on the Submit Application to iTunesConnect... button to submit your application binary right from within Xcode.


iPhone OS 4.0


Given that the upcoming OS release is still under NDA, there's not much that can be said outside of what is public.  Given what has been shown, I'd say it's safe to conclude that much of what's in iPhone OS 3.2, except for the things that are iPad-specific, will find their way into 4.0.


One huge under-the-hood change that has leaked out from other developers is the presence of Grand Central Dispatch and blocks.  This seemed like an inevitability when we talked about it in the Multithreading class, and now it's here.  I highly recommend reading Introducing Blocks and Grand Central Dispatch, the Concurrency Programming Guide, and Blocks Programming Topics for more information on this new way of looking at concurrent operations.  Beyond being useful for multithreading, blocks as a language structure can save a tremendous amount of code by eliminating delegate method callbacks, notification handlers, etc.


The same techniques I describe above for conditional code execution from OS 3.2 to 3.0 can easily be used to let you incorporate 4.0 features in your application while still targeting 3.2 and 3.0.