An iPad iOS 5 Split View and Popover Example

From Techotopia
Jump to: navigation, search
PreviousTable of ContentsNext
Creating a Navigation based iOS 5 iPad Application using TableViewsImplementing a Page based iOS 5 iPad Application using UIPageViewController


Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book


As demonstrated in the previous chapters, whilst it is possible to use the UITableView class as both an information display and application navigation tool, it is extremely inefficient in terms of the use of screen space when used on an iPad. In recognition of this fact, Apple introduced the Split View and Popover concepts for use when developing iPad applications.

The purpose of this chapter is to provide an overview of Split Views and Popovers followed by a tutorial that implements these concepts in a simple example iPad application.


Contents


An Overview of Split View and Popovers

When an iPad is in landscape mode, the UISplitViewController class divides the iPad screen into two side-by-side panels which implement a master-detail model. Within this model, the left hand panel is the master panel and presents a list of items to the user. The right hand panel is the detail panel and displays information relating to the currently selected item in the master panel.

A prime example of this concept in action can be seen with the iPad Mail app which lists messages in the master panel and displays the content of the currently selected message in the detail panel.

When an iPad device is in portrait mode, however, the Split View Controller hides the master panel so that the detail panel is able to utilize the entire screen. In this instance, access to the master panel is provided using a Popover. A popover is a window that appears over the top of the current view and, in a split view situation, is typically accessed via a button placed in the toolbar of the detail view. When the device is rotated back to landscape orientation, the master and detail panels appear side by side once again and the popover access button is removed from view.

The UISplitterViewController is essentially a container to which two child view controllers are added to act as the master (also referred to as the root view controller) and detail views. The detail view controller is usually designated as a delegate to the splitter view controller in order to receive notifications relating to orientation changes, primarily so that the popover button may be hidden and displayed accordingly.

In the remainder of this chapter we will work through a tutorial that involves the creation of a simple iPad application that demonstrates the use of a split view and popover.

About the Example iPad Split View and Popover Project

The goal of this tutorial is to create an iPad application containing a split view user interface. The master panel will contain a table view listing a number of web site addresses. When a web site URL is selected from the list the detail panel will load the corresponding web site and display it using a UIWebView component. When rotated into portrait mode, a button will appear in the navigation bar of the detail view which, when selected, will display the master list in a popover.


Creating the Project

Begin by launching Xcode and creating a new iPad iOS application using the Master-Detail Application template. Enter splitView as the product name and class prefix, select iPad from the device family menu and verify that the Use Core Data and Use Storyboard options are off.

By using this template we save a lot of extra coding effort in the implementation of the split view and popover behavior. Much of the code generated for us is standard boilerplate code that does not change from one split view implementation to another. In fact, much of this template can be copied even if you plan to hand code split view behavior in future applications. That said, there are some unusual aspects to the template which will need to be modified during the course of this tutorial.

Reviewing the Project

The Split View template has created a number of project files for us. The most significant of these are the files relating to the Master View Controller and Detail View Controller classes. These classes correspond to the master and detail views and are named splitViewMasterViewController and splitViewDetailViewController respectively. Xcode has also created a custom didFinishLaunchingWithOptions: method for us in the splitViewAppDelegate class. Whilst the custom didFinishLaunchingWithOptions: method fits perfectly with our requirements, the template actually creates a simple application in which the date and time may be added to the master view and then displayed in the detail view when selected. Since this is not quite the behavior we need for our example it will be necessary to delete some of this template code as our project progresses.

Reviewing the Application Delegate Class

The first step in the application development process is to review the splitViewAppDelegate class to get a basic understanding of the Master-Detail template. Select the splitViewAppDelegate.m file and review the template didFinishLaunchingWithOptions: method:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.

    splitViewMasterViewController *masterViewController = 
         [[splitViewMasterViewController alloc]    
         initWithNibName:@"splitViewMasterViewController" 
         bundle:nil];
    UINavigationController *masterNavigationController = 
         [[UINavigationController alloc] 
         initWithRootViewController:masterViewController];

    splitViewDetailViewController *detailViewController = 
         [[splitViewDetailViewController alloc] 
         initWithNibName:@"splitViewDetailViewController" 
         bundle:nil];

    UINavigationController *detailNavigationController = 
         [[UINavigationController alloc]
         initWithRootViewController:detailViewController];

    masterViewController.detailViewController = detailViewController;

    self.splitViewController = [[UISplitViewController alloc] init];
    self.splitViewController.delegate = detailViewController;
    self.splitViewController.viewControllers = [NSArray 
          arrayWithObjects:masterNavigationController, 
          detailNavigationController, nil];
    self.window.rootViewController = self.splitViewController;
    [self.window makeKeyAndVisible];
    return YES;
} 

Before continuing with the tutorial, it is worth taking some time to review the tasks being performed in the above method.

Having created the standard UIWindow instance, the method allocates and initializes both the master and detail view controllers using the corresponding NIB files for each view. In addition, a navigation controller is created for each with the corresponding view controller declared as the root view controller. The code then assigns our detail view controller object to the detailViewController property of our master view controller object. This will be useful when we need to access this object in code later in the chapter. An instance of the split view controller class is then created and an array containing the master and detail navigation controllers assigned to the split view object’s viewControllers property. The detail view controller is declared as the delegate for the split view controller which in turn is assigned as root view controller of the window prior to being displayed.

Configuring Master View Items

The root view controller created for us by Xcode is actually a subclass of UITableView. The next step, therefore, is to configure the table view to display the list of web sites. For this purpose we will need to configure two array objects to store the web site names and corresponding URL addresses. Select the splitViewMasterViewController.h file and modify it as follows to declare these two arrays:

#import <UIKit/UIKit.h>

@class splitViewDetailViewController;

#import <CoreData/CoreData.h>

@interface splitViewMasterViewController : UITableViewController <NSFetchedResultsControllerDelegate>

@property (nonatomic, retain) NSArray *siteNames;
@property (nonatomic, retain) NSArray *siteAddresses;

@property (strong, nonatomic) splitViewDetailViewController *detailViewController;

@property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
@property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;

@end

Having declared the arrays, modify the splitViewMasterViewController.m file to add the @synthesize directive and to initialize the arrays in the viewDidLoad method. Note that Xcode has already added some code to the viewDidLoad method for the template example so be sure to remove this before adding the new code below. Also be sure to add code to release memory allocated to these arrays when the application exits:

#import "splitViewMasterViewController.h"
#import "splitViewDetailViewController.h"

@interface splitViewMasterViewController ()
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath;
@end

@implementation splitViewMasterViewController

@synthesize detailViewController = _detailViewController;
@synthesize fetchedResultsController = __fetchedResultsController;
@synthesize managedObjectContext = __managedObjectContext;
@synthesize siteNames, siteAddresses;
.
.
- (void)viewDidLoad
{
    [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
    siteNames = [[NSArray alloc] initWithObjects:@"Yahoo", @"Google",
        @"Apple", @"eBookFrenzy", nil];

    siteAddresses = [[NSArray alloc] 
         initWithObjects:@"http://www.yahoo.com", 
           @"http:/www.google.com", @"http://www.apple.com",        
           @"http://www.ebookfrenzy.com", nil];

    [self.tableView selectRowAtIndexPath: 
           [NSIndexPath indexPathForRow:0 inSection:0] 
             animated:NO 
             scrollPosition:UITableViewScrollPositionMiddle];
}
.
.
- (void)viewDidUnload
{
    // Relinquish ownership of anything that can be recreated in viewDidLoad or on demand.
    // For example: self.myOutlet = nil;
    self.siteNames = nil;
    self.siteAddresses = nil;
}

As outlined in the chapter entitled Creating a Simple iOS 5 iPad Table View Application there are a number of methods that must be implemented in order for the items to appear within the table view object. Fortunately, Xcode has already placed template methods for us to use in the splitViewMasterViewController.m file. First, modify the numberOfRowsInSection method to notify the table view of the number of items to be displayed (in this case, a value equal to the number of items in our siteNames array). Since there is only one section in the table also modify the numberOfSectionsInTableView method accordingly. Note that Xcode has, once again, added some code for the template example so be sure to remove this code:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView 
numberOfRowsInSection:(NSInteger)section
{
    return [siteNames count];
}

Next, modify the cellForRowAtIndexPath method to return the item to be displayed, using the row number argument as an index into the siteNames array:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView 
         dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] 
                 initWithStyle:UITableViewCellStyleDefault 
                 reuseIdentifier:CellIdentifier];
    }

    // [self configureCell:cell atIndexPath:indexPath];

    cell.textLabel.text = [siteNames objectAtIndex:indexPath.row];      
    return cell;
}

Before performing a test run of the changes so far, scroll up to the initWithNibName: method and change the title value for the view from “Master” to “Favorite Web Sites” as follows:

- (id)initWithNibName:(NSString *)nibNameOrNil 
bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil 
        bundle:nibBundleOrNil];
    if (self) {
        self.title = NSLocalizedString(@"Favorite Web Sites", 
           @"Favorite Web Sites");
        self.clearsSelectionOnViewWillAppear = NO;
        self.contentSizeForViewInPopover = CGSizeMake(320.0, 600.0);
    }
    return self;
}

Click on the Run button located in the Xcode toolbar to test the current state of the application. Once the application loads into the iOS iPad simulator, rotate the device into landscape mode (Hardware -> Rotate Left):


The current state of the iPad iOS 5 SplitView application

Figure 25-1


At this point the items in the master panel actually load the detail view onto the master panel when selected and the detail panel still displays the place holder label provided by Xcode when the project was first created. The next step is to change this behavior and add the web view to the detail view controller.

Configuring the Detail View Controller

When a user selects a web site item from the master panel, the detail panel will load the selected web site into a web view object. The first step in configuring the detail panel is to declare an outlet for the web view object. Select the splitViewDetailViewController.h file and modify it as follows:

#import <UIKit/UIKit.h>

@interface splitViewDetailViewController : UIViewController <UISplitViewControllerDelegate>

@property (strong, nonatomic) id detailItem;
@property (strong, nonatomic) IBOutlet UIWebView *webView;
@property (strong, nonatomic) IBOutlet UILabel *detailDescriptionLabel;
@end

Next, modify the splitViewDetailViewController.m file to add the @synthesize directive for the webView outlet:

#import "splitViewDetailViewController.h"

@interface splitViewDetailViewController ()
@property (strong, nonatomic) UIPopoverController *masterPopoverController;
- (void)configureView;
@end

@implementation splitViewDetailViewController

@synthesize detailItem = _detailItem;
@synthesize detailDescriptionLabel = _detailDescriptionLabel;
@synthesize masterPopoverController = _masterPopoverController;
@synthesize webView;
.
.
@end

Having declared the outlet for the web view the next step is to add the actual object to the view. Select the splitViewDetailViewController.xib file, highlight the placeholder label that reads “Detail view content goes here” and press the Delete key. Drag and drop a UIWebView object from the object library (View -> Utilities -> Object Library) onto the view and resize it so that it fills the available space.

Connect the webView outlet to the web view object by Ctrl-clicking on the File’s Owner icon and dragging to the web view object. Release the line and select the webView outlet from the resulting menu.

Connecting Master Selections to the Detail View

All that now remains is to configure the detail panel to update based on selections made in the master panel. When a user makes an item selection from the table view the didSelectRowAtIndexPath method in the master view controller is triggered. This template method needs to be modified to load the corresponding web site into the web view. Select the splitViewMasterViewController.m file and locate the method. Once again, Xcode has added code to the template example so remove the current code and modify the method as follows:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *urlString = [siteAddresses 
                 objectAtIndex:indexPath.row];
    NSURL *url = [NSURL URLWithString:urlString];

    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    splitViewDetailViewController *detailViewController = 
               self.detailViewController;

    detailViewController.webView.scalesPageToFit = YES;

    [detailViewController.webView loadRequest:request];
}

The method begins by identifying the URL of the selected web site by using the selected row as an index into the siteAddresses array which is then used to construct NSURL and NSURLRequest objects.

Next a reference to the detail view controller is obtained by accessing the detailViewController property of the master view controller (which is the current class, hence the reference to self). If you return to the didFinishLaunchingWithOptions: method located in the splitViewAppDelegate.m file you will notice that this was conveniently set up for us by the following line of code:

masterViewController.detailViewController = detailViewController;

Note that in order to make the web page scale to fit the detail view area the scalePagesToFit property of the UIWebView object is set to YES. Finally, the web view object in the detail view controller is instructed to load the web URL.

Popover Implementation

In actual fact the popover code was already implemented for us by Xcode when we selected the option to create a Master-Detail application. That said, it is worth taking the time to look at this code, which consists of two delegate methods in the splitViewDetailViewController.m file. The first method is called when the device rotates to portrait orientation and reads as follows:

- (void)splitViewController:(UISplitViewController *)splitController 
willHideViewController:(UIViewController *)viewController 
withBarButtonItem:(UIBarButtonItem *)barButtonItem  forPopoverController:(UIPopoverController *)popoverController
{
    self.barButtonItem.title = NSLocalizedString(@"Master", @"Master");
    [self.navigationItem setLeftBarButtonItem:barButtonItem animated:YES];
    self.masterPopoverController = popoverController;
}

This method configures the toolbar to display a button with the title “Master” and sets up the popover controller assignment. Before proceeding, modify the title line of code to display the “Favorite Web Sites” title:

self.barButtonItem.title = NSLocalizedString(@"Favorite Web Sites", @"Favorite Web Sites");

The second method simply removes the button from the toolbar and de-assigns the popoverController reference:

- (void)splitViewController:(UISplitViewController *)splitController 
willShowViewController:(UIViewController *)viewController 
invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
    // Called when the view is shown again in the split view, invalidating the button and popover controller.
    [self.navigationItem setLeftBarButtonItem:nil animated:YES];
    self.masterPopoverController = nil;
}

Testing the Application

All that remains is to test the application so click on the Run button and wait for the application to load into the iOS iPad Simulator. In portrait mode only the detail panel should be visible. Tap the “Favorite Web Sites” button to display the popover and select a website from the list so that it loads into the detail view:


The example iPad iOS 5 SplitView application running in portrait mode

Figure 25-2


Select the Hardware -> Rotate Left menu option to switch the device into landscape mode and note that both the master and detail panels are now visible:


The example iPad iOS 5 SplitView application running in landscape mode

Figure 25-3

Summary

Split views provide iPad applications with a master-detail interface paradigm designed to make better use of the larger screen space of the device. Where ever possible, this approach should be taken in favor of the table view based navigation common in iPhone based applications. In this chapter we have explored split views and popovers in general and worked through the implementation of an example application.


Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book



PreviousTable of ContentsNext
Creating a Navigation based iOS 5 iPad Application using TableViewsImplementing a Page based iOS 5 iPad Application using UIPageViewController