Using the Master Detail template in iPad

The Master Detail template for iPad applications offers a quick and relatively easy way to set up an application having two view controllers: a master view controller containing a table view that controls the content of one or more detail view controllers containing information. Unlike most of the templates available, the Master Detail template gives us a functional application, allowing dates to be added and removed from the master controller’s table view. In order to develop our own application implementing the Master Detail structure, we will have to remove some code before beginning (or start from scratch using UISplitViewController in an Empty Application template). Let’s see how it works.

Start Xcode, select “Create a new Xcode project,” and choose the Master Detail Application template. Click Next, name the project “MasterDetail,” and set options as shown here:

(In this demo, we are not using storyboards, so that we can easily examine the setup of the view controllers in the AppDelegate.) Click Next, choose a location to save the project, and click Create.

When Xcode has finished populating the template, you will see several objects listed in the navigation panel:

MasterViewController is a subclass of UITableViewController, and DetailViewController is a subclass of UIViewController. By using the template, all of the view controllers are properly set up in the AppDelegate class. Let’s examine AppDelegate.m to see how these controllers fit together:

#import "AppDelegate.h"

#import "MasterViewController.h"

#import "DetailViewController.h"

@implementation AppDelegate

@synthesize window = _window;
@synthesize splitViewController = _splitViewController;

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

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

    DetailViewController *detailViewController = [[DetailViewController alloc] initWithNibName:@"DetailViewController" 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;
}

As is most often the case, the important work of setting up the controllers is done in the application: didFinishLaunchingWithOptions; method of AppDelegate. Both the MasterViewController and DetailViewController are instantiated, and set as the root view of their own UINavigationController object. Then (and most importantly), a UISplitViewController object is instantiated, and its viewControllers array is set with the master and detail view controllers’ navigation controller objects. Setting things up this way is very convenient: it allows us to push (and pop) new view controllers using the UINavigationController objects associated with both the master and detail controllers.

After setting up the splitViewController object, it is set as the main window’s rootViewController, and the main window is made key and visible.

Now open MasterViewController.h and make alterations as shown:

#import <UIKit/UIKit.h>

@class DetailViewController;

@interface MasterViewController : UITableViewController

@property (strong, nonatomic) DetailViewController *detailViewController;
@property (strong, nonatomic) NSDictionary *masterCellContent;

@end

Note that we’ve created a property called masterCellContent: this is an NSDictionary object whose keys will become the text for each cell in the table view. Now open MasterViewController.m and alter it as shown:

#import "MasterViewController.h"

#import "DetailViewController.h"

@interface MasterViewController ()

@end

@implementation MasterViewController

@synthesize detailViewController = _detailViewController;
@synthesize masterCellContent;

(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        self.title = NSLocalizedString(@"Master", @"Master");
        self.clearsSelectionOnViewWillAppear = NO;
        self.contentSizeForViewInPopover = CGSizeMake(320.0, 600.0);
    }
    return self;
}
                                                       
(void)viewDidLoad
{
    [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
    masterCellContent = [NSDictionary dictionaryWithObjectsAndKeys:
                         [UIColor redColor], @"red",
                         [UIColor blueColor], @"blue",
                         [UIColor greenColor], @"green",
                         [UIColor cyanColor], @"cyan",
                         [UIColor yellowColor], @"yellow",
                         [UIColor whiteColor], @"white",
                         [UIColor blackColor], @"black",
                         [UIColor grayColor], @"gray", nil];
}

(void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
}

(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return YES;
}

#pragma mark – Table View

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

(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.masterCellContent.count;
}

// Customize the appearance of table view cells.
(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];
    }

    cell.textLabel.text = [[self.masterCellContent allKeys] objectAtIndex:indexPath.row];

    return cell;
}

(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *colorKey = [[self.masterCellContent allKeys] objectAtIndex:indexPath.row];
    self.detailViewController.view.backgroundColor = [self.masterCellContent objectForKey:colorKey];
}

@end

Let’s look first at viewDidLoad. In this method, we create the NSDictionary. The keys are colors, and the value for each key is a UIColor object corresponding to the color named in the key.

The number of sections in the master’s table view is 1, this value is returned in the numberOfSectionsInTableView: method. The number of rows in the section is returned from the tableView: numberOfRowsInSection: method: this value is the count of the keys in the masterCellContent dictionary.

In tableView: cellForRowAtIndexPath:, after creating the cell if it cannot be reused from the queue of reusable cells, we set the cell’s textLabel.text property to the key in the masterCellContent dictionary having the index of indexPath.row. There is no ordering implied in the keys of an NSDictionary; the order of these keys will be set by the system. (This array of keys could be sorted using normal sorting methods, however.)

Finally, in tableView didSelectRowAtIndexPath:, we obtain the key corresponding to the indexPath.row value, then set the detailViewController’s backgroundColor property to the value corresponding to that key in the masterCellContent dictionary. It is not necessary (nor is it desired) to push the detailViewController, since it is already visible as a part of the UISplitViewController set up in the AppDelegate.

A few adjustments must also be made to DetailViewController.h and .m:

#import <UIKit/UIKit.h>

@interface DetailViewController : UIViewController <UISplitViewControllerDelegate>

@end

#import "DetailViewController.h"

@interface DetailViewController ()

@property (strong, nonatomic) UIPopoverController *masterPopoverController;

@end

@implementation DetailViewController

@synthesize masterPopoverController = _masterPopoverController;

#pragma mark – Managing the detail item

(void)setDetailItem:(id)newDetailItem
{
    if (self.masterPopoverController != nil) {
        [self.masterPopoverController dismissPopoverAnimated:YES];
    }        
}

(void)viewDidLoad
{
    [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.

}

(void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
}

(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return YES;
}

(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        self.title = NSLocalizedString(@"Detail", @"Detail");
    }
    return self;
}
                                                       
#pragma mark – Split view

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

(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;
}

@end

These alterations remove the UILabel (and accompanying objects and methods) from the detail view controller.

Run the application, and note the result of selecting a color in the master view controller. Also note the behavior of the splitViewController when the device is rotated to landscape mode.

Using the Master Detail template, we can create many different iPad applications. We can choose to create only a single detail controller (as is done in the template) and adjust its properties depending on the choice made in the master view controller. We could also choose to present different detail views, depending on the choice the user makes in the master’s table view. Finally, don’t forget that both the master and detail view controllers each have their own navigation controllers: this allows for some very complex user interface possibilities indeed!

Leave a Reply

Your email address will not be published. Required fields are marked *