A TableView based menu for iPad

Menuing in iPad is typically handled through the use of popovers. But it is also possible to use separate table views in conjunction with button bar items to develop a menu for an iPad app. Let’s see how to do it!

Start Xcode, select “Create a new Xcode project,” and choose the Empty Application template. Name your project “TVMenu,” and choose options as shown:

Click Next, choose a location to save the project, and click Create.

We must now add a view controller to serve as our application’s view controller. From the menu, choose File | New… File as shown here:

Make the file a subclass of UIViewController and name it MainViewController. Make sure that both the “Targeted for iPad” and “With XIB for user interface” boxes are checked:

Click Next and Create the file in the default location.

Now that we have a view controller, let’s add it to the AppDelegate so it will be displayed inside a navigation controller in the main window. Open AppDelegate.h and adjust it:

#import <UIKit/UIKit.h>
#import "MainViewController.h"

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) MainViewController *rootVC;
@property (strong, nonatomic) UINavigationController *navController;

@end

Now open AppDelegate.m, synthesize the properties, and make the following changes to the application: didFinishLaunchingWithOptions: method:

#import "AppDelegate.h"

@implementation AppDelegate

@synthesize window = _window;
@synthesize rootVC = _rootVC;
@synthesize navController = _navController;

(BOOL)application:(UIApplication *)application
              didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
   
    self.rootVC = [[MainViewController alloc] initWithNibName:@"MainViewController" bundle:nil];
    self.rootVC.title = @"Menu Test";
    self.navController = [[UINavigationController alloc] initWithRootViewController:self.rootVC];
   
    [self.window addSubview:self.navController.view];
    [self.window makeKeyAndVisible];
    return YES;
}

After creating the window, the rootVC property is instantiated using the nib file we created earlier. The title is set to “Menu Test,” and the rootVC is added as the root view controller of a UINavigationController named navController. Finally, the nav controller’s view is added as a subview of the main window.

We must now make some changes to the xib file. Open MainViewController.xib.

(the lower left corner of the interface is shown above). Drag a ToolBar to the bottom of the view. Change the first button’s text to File, add a second UIBarButtonItem to the toolbar and title it “Edit.” Now drag two UITableViews to the interface, and resize them as shown. Set them both to “hidden.” Also drag out a UILabel control to the view, change the text size to 40, justify the text to the center of the label, and resize it so that it takes up the full width of the interface:

Place the label somewhere near the vertical center of the view. Finally, set the main view’s background color to a color of your choosing.

Open up MainViewController.h and make these changes:

#import <UIKit/UIKit.h>

@interface MainViewController : UIViewController
<UITableViewDelegate, UITableViewDataSource>

@property (nonatomic, weak) IBOutlet UITableView *fileMenu;
@property (nonatomic, strong) NSArray *fileMenuItems;
@property (nonatomic, weak) IBOutlet UITableView *editMenu;
@property (nonatomic, strong) NSArray *editMenuItems;
@property (nonatomic, weak) IBOutlet UILabel *selectionDisplay;

(IBAction)fileMenuButtonTouched:(UIBarButtonItem *)sender;
(IBAction)editMenuButtonTouched:(UIBarButtonItem *)sender;

@end

We’ve made this view controller adopt the UITableViewDelegate and UITableViewDataSource protocols, because it must populate the table views and respond to touches on them. Note that we have three outlets (fileMenu, editMenu, and selectionDisplay). FileMenu and editMenu will be wired to the table views, and selectionDisplay will be wired to the label. We’ve also defined two action methods, to be fired when the toolbar buttons are touched.

Return to the nib file (MainViewController.xib) and wire up the controls to their outlets and actions. Also, wire up the datasource and delegate of both table views to File’s Owner.

Now open MainViewController.m and make these changes:

#import "MainViewController.h"

@interface MainViewController ()

@end

@implementation MainViewController

@synthesize fileMenu, editMenu;
@synthesize fileMenuItems, editMenuItems;
@synthesize selectionDisplay;

(IBAction)fileMenuButtonTouched:(UIBarButtonItem *)sender
{
    if (self.fileMenu.hidden == YES) {
        self.fileMenu.hidden = NO;
        self.editMenu.hidden = YES;
        self.selectionDisplay.text = @"File Menu Selected";
    } else {
        self.fileMenu.hidden = YES;
        self.selectionDisplay.text = @"";
    }
}

(IBAction)editMenuButtonTouched:(UIBarButtonItem *)sender
{
    if (self.editMenu.hidden == YES) {
        self.editMenu.hidden = NO;
        self.fileMenu.hidden = YES;
        self.selectionDisplay.text = @"Edit Menu Selected";
    } else {
        self.editMenu.hidden = YES;
        self.selectionDisplay.text = @"";
    }

}

(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
        self.fileMenuItems = [[NSArray alloc] initWithObjects:@"new", @"open", @"save", @"saveAs…", @"exit", nil];
        self.editMenuItems = [[NSArray alloc] initWithObjects:@"cut", @"copy", @"paste", nil];
    }
    return self;
}

(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *displayString;
    if ([tableView isEqual:self.fileMenu]) {
        switch (indexPath.row) {
            case 0:
                displayString = [NSString stringWithString:@"File | new"];
                break;
            case 1:
                displayString = [NSString stringWithString:@"File | open"];
                break;
            case 2:
                displayString = [NSString stringWithString:@"File | save"];
                break;
            case 3:
                displayString = [NSString stringWithString:@"File | saveAs…"];
                break;
            case 4:
                displayString = [NSString stringWithString:@"File | exit"];
                break;
            default:
                break;
        }
    } else {
        switch (indexPath.row) {
            case 0:
                displayString = [NSString stringWithString:@"Edit | cut"];
                break;
            case 1:
                displayString = [NSString stringWithString:@"Edit | copy"];
                break;
            case 2:
                displayString = [NSString stringWithString:@"Edit | paste"];
                break;
            default:
                break;
        }
    }
    self.selectionDisplay.text = displayString;
    tableView.hidden = YES;
}

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

(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    NSInteger retVal;
    if ([tableView isEqual:self.fileMenu]) {
        retVal = [self.fileMenuItems count];
    } else {
        retVal = [self.editMenuItems count];
    }
    return retVal;
}

(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];
    }
   
    // Configure the cell…
    if ([tableView isEqual:self.fileMenu]) {
        cell.textLabel.text = [fileMenuItems objectAtIndex:indexPath.row];
    } else {
        cell.textLabel.text = [editMenuItems objectAtIndex:indexPath.row];
    }
    return cell;
}
//…

The two methods fileMenuButtonTouched and editMenuButtonTouched make sure that the appropriate table view is displayed, and also set the contents of the selectionDisplay label to reflect the menu that should currently be shown. If more than two menus were to be used, we wouldn’t use an “else” clause in our code to determine the touched button or menu: we would use “else if” and specifically state the menu button or table view we are dealing with.

In the initWithNibName: bundle: method, we set the contents of the arrays that hold the menu items to be displayed in the table views. Table views take their contents from an indexed array of NSString values. This can be hard-coded (as it is here), or loaded from a plist sourced either locally or from an online source.

In the tableView: didSelectRowAtIndexPath: method, we check to see which table view is being interacted with, and then look at the value of indexPath.row to find the value that should be displayed. We could also use indexPath.row as the index to pass to [NSArray objectAtIndex:], but the strategy shown here is easier to understand. When the user presses a row in a table view menu, the selectionDisplay label will be updated to reflect the user’s selection and the menu is then hidden.

The method numberOfSectionsInTableView simply returns 1, since each menu (in this demo) only has one section to display. But the numberOfRowsInSection method must return the number of indexes in each array, depending on which table view object is being shown.

CellForRowAtIndexPath first gets a reusable cell, then sets its textLabel.text property according to which table is being filled. Again, in a more robust application, we would not use else, but rather else if… to find the proper table view.

The full list of items wired to the File’s Owner (MainViewController class) is shown here:

Run the application, and test the menus:

Leave a Reply

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