• Developing Split View Apps for iPad

    19 February 2016

    Getting Started

    Using Xcode, create a Split View-based Application project and name it splitViewBased (see Figure 1).


    Figure 1. Creating a new Split View-based Application

    Observe the files created in the Classes and Resources folder (see Figure 2). Notice that there are two View Controller classes (RootViewController and DetailViewController) as well as two XIB files (MainWindow.xib and DetailView.xib).


    Figure 2. Examining the files created in the project

    Press Command-R in Xcode to test the application on the iPad Simulator. Figure 3 shows the application when it is displayed in landscape mode. When you rotate the simulator to portrait mode, the application now looks like Figure 4. The list of items is now displayed in a PopoverView, a new View available only for the iPad.


    Figure 3. Viewing the Split View-based application in landscape mode


    Figure 4. Viewing the Split View-based application in portrait mode

    Dissecting the Split View-based Application

    The magic of a Split View-based Application lies in its transformation when the device is rotated. When in landscape mode, the application displays a list of rows on the left. When it is turned to portrait mode, the list of rows is now hidden in a Popover view. Let's see how this is done.

    First, observe the content of the splitViewBasedAppAppDelegate.h file:

     #import <UIKit/UIKit.h>
    &nbsp;
    @class RootViewController;
    @class DetailViewController;
    &nbsp;
    @interface splitViewBasedAppAppDelegate : <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/ObjC_classic/Classes/NSObject.html">NSObject</a> <UIApplicationDelegate> {
    &nbsp;
        UIWindow *window;
    &nbsp;
        UISplitViewController *splitViewController;
    &nbsp;
        RootViewController *rootViewController;
        DetailViewController *detailViewController;
    }
    &nbsp;
    @property (nonatomic, retain) IBOutlet UIWindow *window;
    &nbsp;
    @property (nonatomic,retain) IBOutlet UISplitViewController *splitViewController;
    @property (nonatomic,retain) IBOutlet RootViewController *rootViewController;
    @property (nonatomic,retain) IBOutlet DetailViewController *detailViewController;
    &nbsp;
    @end

    Notice that it contains a view controller object of type UISplitViewController (splitViewController) as well as two view controllers (rootViewController and detailViewController). The UISplitViewController is a container view controller that contains two view controllers, allowing you to implement a master-detail interface.

    Next, observe the content of the splitViewBasedAppAppDelegate.m file:

     #import "splitViewBasedAppAppDelegate.h"
    &nbsp;
    #import "RootViewController.h"
    #import "DetailViewController.h"
    &nbsp;
    @implementation splitViewBasedAppAppDelegate
    &nbsp;
    @synthesize window, splitViewController, rootViewController, detailViewController;
    &nbsp;
    - (BOOL)application:(UIApplication *)application 
    didFinishLaunchingWithOptions:(<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/ObjC_classic/Classes/NSDictionary.html">NSDictionary</a> *)launchOptions {    
    &nbsp;
        // Override point for customization after app launch    
    &nbsp;
        // Add the split view controller's view to the window and display.
        [window addSubview:splitViewController.view];
        [window makeKeyAndVisible];
    &nbsp;
        return YES;
    }
    &nbsp;
    - (void)applicationWillTerminate:(UIApplication *)application {
        // Save data if appropriate
    }
    &nbsp;
    - (void)dealloc {
        [splitViewController release];
        [window release];
        [super dealloc];
    }
    &nbsp;
    @end

    When the application is loaded, the view contained in the splitViewController object is added to the window.

    Now, double-click on the MainWindow.xib file to edit it in Interface Builder. Observe that the MainWindow.xib contains an item named Split View Controller (recall that for a View-based Application project, you will have a View Controller item instead).

    Switch the MainWindow.xib file to display in List view mode and observe the various items located within the Split View Controller item (see Figure 5).


    Figure 5. Viewing the MainWindow.xib window in List view mode

    The Split View Controller item contains the following items:

    • Navigation Controller
    • Detail View Controller

    The Navigation Controller controls the left side of a Split View-based application. Figure 6 shows that it consists of a Navigation Bar as well as a Root View Controller.


    Figure 6. The Navigation Controller controls the left side

    Split View-based application

    The Root View Controller is mapped to the RootViewController class (see Figure 7).


    Figure 7. The Root View Controller is mapped to the RootViewController class

    The Detail View Controller controls the right side of a split view application (see Figure 8).


    Figure 8. The Detail View Controller controls the right side of a Split View-based application
    The Detail View Controller is mapped to the DetailViewController class (see Figure 9).


    Figure 9. The Detail View Controller is mapped to the DetailViewController class

    The application delegate is connected to the various view controllers, as you can see when you right-click on the Split View Based App App Delegate item (see Figure 10).


    Figure 10. The application delegate is connected to the various view controllers

    Let's examine the two view controllers that are contained within the Split View Controller - RootViewController and DetailViewController.

    Observe the content of the RootViewController.h file:

     #import <UIKit/UIKit.h>
    &nbsp;
    @class DetailViewController;
    &nbsp;
    @interface RootViewController : UITableViewController {
        DetailViewController *detailViewController;
    }
    &nbsp;
    @property (nonatomic, retain) IBOutlet DetailViewController *detailViewController;
    &nbsp;
    @end

    Note that the RootViewController class inherits from the UITableViewController class, not the UIViewController class you would see in a View-based application. The UITableViewController class is a subclass of the UIViewController class, providing the ability to display a table containing rows of data.

    The content of the RootViewController.m file contains many methods related to the Table view; here is a quick summary of some of the important methods:

    • contentSizeForViewInPopoverView - the size of the PopoverView to display.
    • numberOfSectionsInTableView: - the number of sections to be displayed in the Table view.
    • tableView:numberOfRowsInSection: - the number of rows to be displayed in the Table view.
    • tableView:cellForRowAtIndexPath: - the data to populate for each row.
    • tableView:didSelectRowAtIndexPath: - the row that was selected by the user.

    Next, let's visit the DetailsViewController.h file:

    #import <UIKit/UIKit.h>
    &nbsp;
    @interface DetailViewController : UIViewController 
        <UIPopoverControllerDelegate, UISplitViewControllerDelegate> {
    &nbsp;
        UIPopoverController *popoverController;
        UIToolbar *toolbar;
    &nbsp;
        id detailItem;
        UILabel *detailDescriptionLabel;
    }
    &nbsp;
    @property (nonatomic, retain) IBOutlet UIToolbar *toolbar;
    &nbsp;
    @property (nonatomic, retain) id detailItem;
    @property (nonatomic, retain) IBOutlet UILabel *detailDescriptionLabel;
    &nbsp;
    @end

    Notice that the DetailsViewController class implements the following protocols:

    • UIPopoverControllerDelegate - It needs to implement this protocol because when the iPad is held in the portrait orientation, the PopoverView will display the content of the Table view.
    • UISplitViewControllerDelegate - It needs to implement this protocol because when the iPad changes orientation, it needs to hide/display the PopoverView.

    Examine the content of the DetailsViewController.m file:

     #import "DetailViewController.h"
    #import "RootViewController.h"
    &nbsp;
    @interface DetailViewController ()
    @property (nonatomic, retain) UIPopoverController *popoverController;
    - (void)configureView;
    &nbsp;
    @end
    &nbsp;
    @implementation DetailViewController
    @synthesize toolbar, popoverController, detailItem, detailDescriptionLabel;
    &nbsp;
    /*
         ---Other commented out code are omitted from this code listing---
    */
    &nbsp;
    /*
     When setting the detail item, update the view and dismiss the popover controller if it's showing.
     */
    &nbsp;
    - (void)setDetailItem:(id)newDetailItem {
        if (detailItem != newDetailItem) {
            [detailItem release];
            detailItem = [newDetailItem retain];
            // Update the view.
            [self configureView];
        }
        if (popoverController != nil) {
            [popoverController dismissPopoverAnimated:YES];
        }        
    }
    &nbsp;
    - (void)configureView {
        // Update the user interface for the detail item.
        detailDescriptionLabel.text = [detailItem description];   
    }
    &nbsp;
    - (void)splitViewController: (UISplitViewController*)svc 
         willHideViewController:(UIViewController *)aViewController 
         withBarButtonItem:(UIBarButtonItem*)barButtonItem 
         forPopoverController: (UIPopoverController*)pc {
    &nbsp;
        barButtonItem.title = @"Root List";
        <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/ObjC_classic/Classes/NSMutableArray.html">NSMutableArray</a> *items = [[toolbar items] mutableCopy];
        [items insertObject:barButtonItem atIndex:0];
        [toolbar setItems:items animated:YES];
        [items release];
        self.popoverController = pc;
    }
    &nbsp;
    &nbsp;
    // Called when the view is shown again in the split view, 
    // invalidating the button and popover controller.
    &nbsp;
    - (void)splitViewController: (UISplitViewController*)svc 
         willShowViewController:(UIViewController *)aViewController 
      invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem {
    &nbsp;
        <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/ObjC_classic/Classes/NSMutableArray.html">NSMutableArray</a> *items = [[toolbar items] mutableCopy];
        [items removeObjectAtIndex:0];
        [toolbar setItems:items animated:YES];
        [items release];
        self.popoverController = nil;
    }
    &nbsp;
    - (void)dealloc {
        [popoverController release];
        [toolbar release];
    &nbsp;
        [detailItem release];
        [detailDescriptionLabel release];
        [super dealloc];
    }
    &nbsp;
    @end

    There are two important events you need to handle in this controller:

    • splitViewController:willHideViewController:withBarButtonItem:forPopoverController: - fired when the iPad switches over to portrait mode (where the PopoverView will be shown and the Table View will be hidden).
    • splitViewController:willShowViewController:invalidatingBarButtonItem: - fired when the iPad switches over to landscape mode (where the PopoverView will be hidden and the Table View will be shown).

    Displaying a list of Movies

    Now that you have seen a Split View-based Application in action, it is now time to make some changes to it and see how it is useful for the iPad. You will modify the application to show a list of movies and when a movie is selected, a picture will be displayed on the detail view.

    Double-click the DetailView.xib file to edit it in Interface Builder.

    Add an ImageView to the View window and set its Mode to Aspect Fit in the Attributes Inspector window (see Figure 11).


    Figure 11. Adding a ImageView to the View window

    In the Size Inspector window, set is Autosizing attribute as shown in Figure 12.


    Figure 12. Setting the Autosizing attribute of the ImageView

    In Xcode, add the images as shown in Figure 13 to the Resources folder.


    Figure 13. Adding some images to the Resources folder.

    In the DetailViewController.h file, insert the following statements in bold:

     #import <UIKit/UIKit.h>
    &nbsp;
    @interface DetailViewController : UIViewController <UIPopoverControllerDelegate, UISplitViewControllerDelegate> {
    &nbsp;
        UIPopoverController *popoverController;
        UIToolbar *toolbar;
    &nbsp;
        id detailItem;
        UILabel *detailDescriptionLabel;
    &nbsp;
        IBOutlet UIImageView *imageView;
    }
    &nbsp;
    @property (nonatomic, retain) IBOutlet UIToolbar *toolbar;
    &nbsp;
    @property (nonatomic, retain) id detailItem;
    @property (nonatomic, retain) IBOutlet UILabel *detailDescriptionLabel;
    &nbsp;
    @property (nonatomic, retain) UIImageView *imageView;
    &nbsp;
    @end

    Control-click and drag the File's Owner item and drop it over the ImageView. Select imageView (see Figure 14).


    Figure 14. Connecting the outlet to the ImageView

    Add the following statements in bold to the RootViewController.m file:

     #import "RootViewController.h"
    #import "DetailViewController.h"
    &nbsp;
    @implementation RootViewController
    &nbsp;
    @synthesize detailViewController;
    &nbsp;
    <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/ObjC_classic/Classes/NSMutableArray.html">NSMutableArray</a> *listOfMovies;
    &nbsp;
    - (void)viewDidLoad {   
    &nbsp;
        //---initialize the array---
        listOfMovies = [[<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/ObjC_classic/Classes/NSMutableArray.html">NSMutableArray</a> alloc] init];
        [listOfMovies addObject:@"Training Day"];
        [listOfMovies addObject:@"Remember the Titans"];
        [listOfMovies addObject:@"John Q."];
        [listOfMovies addObject:@"The Bone Collector"];
        [listOfMovies addObject:@"Ricochet"];
        [listOfMovies addObject:@"The Siege"];
        [listOfMovies addObject:@"Malcolm X"];
        [listOfMovies addObject:@"Antwone Fisher"];
        [listOfMovies addObject:@"Courage Under Fire"];
        [listOfMovies addObject:@"He Got Game"];
        [listOfMovies addObject:@"The Pelican Brief"];
        [listOfMovies addObject:@"Glory"];
        [listOfMovies addObject:@"The Preacher's Wife"];
    &nbsp;
        //---set the title---
        self.navigationItem.title = @"Movies";    
    &nbsp;
        [super viewDidLoad];
        self.clearsSelectionOnViewWillAppear = NO;
        self.contentSizeForViewInPopover = CGSizeMake(320.0, 600.0);
    }
    &nbsp;
    - (NSInteger)tableView:(UITableView *)aTableView 
    numberOfRowsInSection:(NSInteger)section {
        // Return the number of rows in the section.
        //return 10;
        return [listOfMovies count];
    }
    &nbsp;
    - (UITableViewCell *)tableView:(UITableView *)tableView 
    cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    &nbsp;
        static <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/ObjC_classic/Classes/NSString.html">NSString</a> *CellIdentifier = @"CellIdentifier";
    &nbsp;
        // Dequeue or create a cell of the appropriate type.
        UITableViewCell *cell = [tableView 
            dequeueReusableCellWithIdentifier:CellIdentifier];
    &nbsp;
        if (cell == nil) {
            cell = [[[UITableViewCell alloc] 
                       initWithStyle:UITableViewCellStyleDefault 
                       reuseIdentifier:CellIdentifier] autorelease];
            cell.accessoryType = UITableViewCellAccessoryNone;
        }
    &nbsp;
        // Configure the cell.    
        // cell.textLabel.text = 
        //     [NSString stringWithFormat:@"Row %d", indexPath.row];
    &nbsp;
        cell.textLabel.text = [listOfMovies objectAtIndex:indexPath.row];
    &nbsp;
        return cell;
    }
    &nbsp;
    - (void)tableView:(UITableView *)aTableView 
    didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    &nbsp;
        /*
         When a row is selected, set the detail view controller's detail item to 
         the item associated with the selected row.
         */
        //detailViewController.detailItem = 
        //    [NSString stringWithFormat:@"Row %d", indexPath.row];
    &nbsp;
        detailViewController.detailItem = 
            [<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/ObjC_classic/Classes/NSString.html">NSString</a> stringWithFormat:@"%@", 
                [listOfMovies objectAtIndex:indexPath.row]];    
    }
    &nbsp;
    - (void)dealloc {
        [listOfMovies release];
        [super dealloc];
    }

    Here, you start by initializing a mutable array with list of movie names.

    The value returned by the tableView:numberOfRowsInSection: method sets the number of rows to be displayed, and this case it is the size of the mutable array. The tableView:cellForRowAtIndexPath: method is fired for each item in the mutable array, thereby populating the Table view.

    When an item is selected in the Table view, you will pass the movie selected to the DetailViewController object via its detailItem property.

    Add the following statements in bold to the DetailViewController.m file:

     #import "DetailViewController.h"
    #import "RootViewController.h"
    &nbsp;
    @interface DetailViewController ()
    &nbsp;
    @property (nonatomic, retain) UIPopoverController *popoverController;
    &nbsp;
    - (void)configureView;
    &nbsp;
    @end
    &nbsp;
    @implementation DetailViewController
    &nbsp;
    @synthesize toolbar, popoverController, detailItem, detailDescriptionLabel;
    &nbsp;
    @synthesize imageView;
    &nbsp;
    /*
     When setting the detail item, update the view and dismiss the popover controller if it's showing.
     */
    - (void)setDetailItem:(id)newDetailItem {
        if (detailItem != newDetailItem) {
            [detailItem release];
            detailItem = [newDetailItem retain];
    &nbsp;
            // Update the view.
            <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/ObjC_classic/Classes/NSString.html">NSString</a> *imageName = 
                [<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/ObjC_classic/Classes/NSString.html">NSString</a> stringWithFormat:@"%@.jpg",
                [detailItem description]];
            imageView.image = [UIImage imageNamed:imageName];
    &nbsp;
            [self configureView];
        }
    &nbsp;
        if (popoverController != nil) {
            [popoverController dismissPopoverAnimated:YES];
        }        
    }

    In the DetailViewController.m file, you modified the setDetailItem: method (which is really a setter for the detailItem property) so that an image can be displayed. For the image name, you simply append a ".jpg" to the movie name.

    Press Command-R to test the application on the iPhone Simulator. The following shows that when the simulator is in landscape mode, the application shows a list of movies on the left of the application (see Figure 15). Selecting a movie displays the movie image. You can also switch to portrait mode and select the movies from the PopoverView (see Figure 16).


    Figure 15. Viewing the completed application in landscape mode


    Figure 16. Viewing the completed application in portrait mode

Comments

Comments closed on this post.