显示标签为“iPad”的博文。显示所有博文
显示标签为“iPad”的博文。显示所有博文

2011年11月2日星期三

Getting Started with SplitViewController

The iPad runs on the same OS used by the iPhone and the iPod touch, which means that developers who are already familiar with iPhone programming are able to quickly write applications for the iPad. In fact, most (if not all) iPhone applications should run without any problem on the iPad, albeit in a smaller frame size, with an option to pixel-double. However, to really make full use of the large size screen afforded by the iPad, you would need to redesign your UI to take into account the extra screen space. The iPhone SDK 3.2, which is needed for iPad development, comes with a new application template - Split View-based Application, which allows you to build applications wide-screen application.
The Split View-based Application template allows you to create a split-view interface for your application, which is essentially a master-detail interface. The left side of the screen displays a list of selectable items where the right-side displays the details of the item selected. This article shows you how you can develop a Split View-based application for the iPad.

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>
 
@class RootViewController;
@class DetailViewController;
 
@interface splitViewBasedAppAppDelegate : NSObject <UIApplicationDelegate> {
 
    UIWindow *window;
 
    UISplitViewController *splitViewController;
 
    RootViewController *rootViewController;
    DetailViewController *detailViewController;
}
 
@property (nonatomic, retain) IBOutlet UIWindow *window;
 
@property (nonatomic,retain) IBOutlet UISplitViewController *splitViewController;
@property (nonatomic,retain) IBOutlet RootViewController *rootViewController;
@property (nonatomic,retain) IBOutlet DetailViewController *detailViewController;
 
@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"
 
#import "RootViewController.h"
#import "DetailViewController.h"
 
@implementation splitViewBasedAppAppDelegate
 
@synthesize window, splitViewController, rootViewController, detailViewController;
 
- (BOOL)application:(UIApplication *)application 
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
 
    // Override point for customization after app launch    
 
    // Add the split view controller's view to the window and display.
    [window addSubview:splitViewController.view];
    [window makeKeyAndVisible];
 
    return YES;
}
 
- (void)applicationWillTerminate:(UIApplication *)application {
    // Save data if appropriate
}
 
- (void)dealloc {
    [splitViewController release];
    [window release];
    [super dealloc];
}
 
@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>
 
@class DetailViewController;
 
@interface RootViewController : UITableViewController {
    DetailViewController *detailViewController;
}
 
@property (nonatomic, retain) IBOutlet DetailViewController *detailViewController;
 
@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>
 
@interface DetailViewController : UIViewController 
    <UIPopoverControllerDelegate, UISplitViewControllerDelegate> {
 
    UIPopoverController *popoverController;
    UIToolbar *toolbar;
 
    id detailItem;
    UILabel *detailDescriptionLabel;
}
 
@property (nonatomic, retain) IBOutlet UIToolbar *toolbar;
 
@property (nonatomic, retain) id detailItem;
@property (nonatomic, retain) IBOutlet UILabel *detailDescriptionLabel;
 
@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"
 
@interface DetailViewController ()
@property (nonatomic, retain) UIPopoverController *popoverController;
- (void)configureView;
 
@end
 
@implementation DetailViewController
@synthesize toolbar, popoverController, detailItem, detailDescriptionLabel;
 
/*
     ---Other commented out code are omitted from this code listing---
*/
 
/*
 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];
        // Update the view.
        [self configureView];
    }
    if (popoverController != nil) {
        [popoverController dismissPopoverAnimated:YES];
    }        
}
 
- (void)configureView {
    // Update the user interface for the detail item.
    detailDescriptionLabel.text = [detailItem description];   
}
 
- (void)splitViewController: (UISplitViewController*)svc 
     willHideViewController:(UIViewController *)aViewController 
     withBarButtonItem:(UIBarButtonItem*)barButtonItem 
     forPopoverController: (UIPopoverController*)pc {
 
    barButtonItem.title = @"Root List";
    NSMutableArray *items = [[toolbar items] mutableCopy];
    [items insertObject:barButtonItem atIndex:0];
    [toolbar setItems:items animated:YES];
    [items release];
    self.popoverController = pc;
}
 
 
// Called when the view is shown again in the split view, 
// invalidating the button and popover controller.
 
- (void)splitViewController: (UISplitViewController*)svc 
     willShowViewController:(UIViewController *)aViewController 
  invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem {
 
    NSMutableArray *items = [[toolbar items] mutableCopy];
    [items removeObjectAtIndex:0];
    [toolbar setItems:items animated:YES];
    [items release];
    self.popoverController = nil;
}
 
- (void)dealloc {
    [popoverController release];
    [toolbar release];
 
    [detailItem release];
    [detailDescriptionLabel release];
    [super dealloc];
}
 
@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>
 
@interface DetailViewController : UIViewController <UIPopoverControllerDelegate, UISplitViewControllerDelegate> {
 
    UIPopoverController *popoverController;
    UIToolbar *toolbar;
 
    id detailItem;
    UILabel *detailDescriptionLabel;
 
    IBOutlet UIImageView *imageView;
}
 
@property (nonatomic, retain) IBOutlet UIToolbar *toolbar;
 
@property (nonatomic, retain) id detailItem;
@property (nonatomic, retain) IBOutlet UILabel *detailDescriptionLabel;
 
@property (nonatomic, retain) UIImageView *imageView;
 
@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"
 
@implementation RootViewController
 
@synthesize detailViewController;
 
NSMutableArray *listOfMovies;
 
- (void)viewDidLoad {   
 
    //---initialize the array---
    listOfMovies = [[NSMutableArray 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"];
 
    //---set the title---
    self.navigationItem.title = @"Movies";    
 
    [super viewDidLoad];
    self.clearsSelectionOnViewWillAppear = NO;
    self.contentSizeForViewInPopover = CGSizeMake(320.0, 600.0);
}
 
- (NSInteger)tableView:(UITableView *)aTableView 
numberOfRowsInSection:(NSInteger)section {
    // Return the number of rows in the section.
    //return 10;
    return [listOfMovies count];
}
 
- (UITableViewCell *)tableView:(UITableView *)tableView 
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 
    static NSString *CellIdentifier = @"CellIdentifier";
 
    // Dequeue or create a cell of the appropriate type.
    UITableViewCell *cell = [tableView 
        dequeueReusableCellWithIdentifier:CellIdentifier];
 
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] 
                   initWithStyle:UITableViewCellStyleDefault 
                   reuseIdentifier:CellIdentifier] autorelease];
        cell.accessoryType = UITableViewCellAccessoryNone;
    }
 
    // Configure the cell.    
    // cell.textLabel.text = 
    //     [NSString stringWithFormat:@"Row %d", indexPath.row];
 
    cell.textLabel.text = [listOfMovies objectAtIndex:indexPath.row];
 
    return cell;
}
 
- (void)tableView:(UITableView *)aTableView 
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
 
    /*
     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];
 
    detailViewController.detailItem = 
        [NSString stringWithFormat:@"%@", 
            [listOfMovies objectAtIndex:indexPath.row]];    
}
 
- (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"
 
@interface DetailViewController ()
 
@property (nonatomic, retain) UIPopoverController *popoverController;
 
- (void)configureView;
 
@end
 
@implementation DetailViewController
 
@synthesize toolbar, popoverController, detailItem, detailDescriptionLabel;
 
@synthesize imageView;
 
/*
 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];
 
        // Update the view.
        NSString *imageName = 
            [NSString stringWithFormat:@"%@.jpg",
            [detailItem description]];
        imageView.image = [UIImage imageNamed:imageName];
 
        [self configureView];
    }
 
    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

Summary

In this article, you have seen the magic of the new Split View-based Application template included with the iPhone SDK 3.2. In the next article, I will dive into the details of the PopoverView and show you how you can make use of the PopoverView to display context sensitive menus in your iPad application.

Getting Started with the PopoverView

In the previous article, we saw how to develop a new type of application known as the Split View-based application on the iPad. And one of the new views introduced for the iPad is the PopoverView, which you had a quick glimpse of it in action in the previous article. In this article, I will show you how you can use the PopoverView in your iPad application, not just in a Split View-based application.

Getting Started with the PopoverView

The iPhone SDK 3.2 includes a new view known as the PopoverView. The PopoverView is a view designed specifically for use on the iPad and it is used to display information in a popover window. The PopoverView displays the content of a view controller, and hence anything that can be displayed in a view controller can be displayed by the PopoverView.
In this section, you will see how to use the PopoverView in your iPad application. You will use the PopoverView to display a list of movies represented by a Table View.
Using Xcode, create a View-based Application (iPad) project and name it PopOverExample1.
Right-click on the Classes folder in Xcode and select Add -> New File…. (see Figure 1).

Figure 1. Adding new files to the project
Select the Cocoa Touch Class item and then select the UIViewController subclass template. Click Next (see Figure 2). Ensure that all the options for the UIViewController subclass item are selected.

Figure 2. Adding a UIViewController subclass item
Name the new class MoviesViewController.m.
In the MoviesViewController.h file, code the following:
#import <UIKit/UIKit.h>
 
@interface MoviesViewController : UITableViewController {
    //---add this---
    NSMutableArray *listOfMovies;
}
 
@end
In the MoviesViewController.m file, code the following:
#import "MoviesViewController.h"
 
//---add this---
#import "PopOverExample1ViewController.h"
#import "PopOverExample1AppDelegate.h"
 
@implementation MoviesViewController
 
- (void)viewDidLoad {
 
    //---add this---
    //---initialize the array---
    listOfMovies = [[NSMutableArray 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"];
 
    [super viewDidLoad];
    self.clearsSelectionOnViewWillAppear = NO;
}
 
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    // Return the number of sections.
    //---add this---
    return 1;
}
 
- (NSInteger)tableView:(UITableView *)tableView 
 numberOfRowsInSection:(NSInteger)section {
    // Return the number of rows in the section.
    //---add this---
    return [listOfMovies 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] autorelease];
    }
 
    // Configure the cell...
    //---add this---
    cell.textLabel.text = [listOfMovies objectAtIndex:indexPath.row];
 
    return cell;
}
 
- (void)tableView:(UITableView *)tableView 
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
 
    //---add this---
    PopOverExample1AppDelegate *appDelegate = 
        [[UIApplication sharedApplication] delegate];
 
    appDelegate.viewController.detailItem = 
        [listOfMovies objectAtIndex:indexPath.row];     
 
}
 
- (void)dealloc {
    //---add this---
    [listOfMovies release];
    [super dealloc];
}
Essentially, the MoviesViewController class displays a list of movie titles using a Table View. The list of movie titles is stored in the listOfMovies array.
When the item in the Table View is selected, you will display the name of the movie selected in the View window [that is hosting the PopoverView, and hence the Table View] using the detailItem property (which you will define in the PopOverExample1ViewController.m file):
- (void)tableView:(UITableView *)tableView 
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
 
     PopOverExample1AppDelegate *appDelegate = 
         [[UIApplication sharedApplication] delegate];
 
     appDelegate.viewController.detailItem = 
        [listOfMovies objectAtIndex:indexPath.row];     
 
}
Double-click the PopOverExample1ViewController.xib file to edit it in Interface Builder.
Populate the View window with the following views (see also Figure 3):
  • Toolbar
  • Bar Button Item
  • Round Rect Button
  • Label

Figure 3. Populating the View window
In the PopOverExample1ViewController.h file, code the following:
#import <UIKit/UIKit.h>
 
@interface PopOverExample1ViewController : UIViewController 
    <UIPopoverControllerDelegate> {    //---add this---
    //---add this---
    UIPopoverController *popoverController;
    IBOutlet UIButton *btn;
    IBOutlet UILabel *movieSelected;
    id detailItem;    
}
 
//---add this---
@property (nonatomic, retain) UIPopoverController *popoverController; 
@property (nonatomic, retain) UIButton *btn;
@property (nonatomic, retain) UILabel *movieSelected;
 
@property (nonatomic, retain) id detailItem;
 
-(IBAction) showMovies:(id) sender;
-(IBAction) btnShowMovies:(id) sender;
 
@end
Here, you implement the UIPopoverControllerDelegate protocol so that you can handle the events raised by the PopoverView. You will use an instance of the UIPopoverController class to display a PopoverView.
Back in Interface Builder, perform the following actions:
  • Control-click the File’s Owner item and drag it over the Label view. Select movieSelected
  • Control-click the File’s Owner item and drag it over the Round Rect Button. Select btn
  • Control-click the Bar Button Item and drag it over the File’s Owner item. Select showMovies
  • Control-click the Bar Button Item and drag it over the File’s Owner item. Select btnShowMovies
In the PopOverExample1ViewController.m file, code the following:
#import "PopOverExample1ViewController.h"
 
//---add this---
#import "MoviesViewController.h"
 
@implementation PopOverExample1ViewController
 
//---add this---
@synthesize btn, movieSelected;
@synthesize popoverController;
@synthesize detailItem;
 
-(IBAction) showMovies:(id) sender {
 
    if (self.popoverController == nil) {
        MoviesViewController *movies = 
            [[MoviesViewController alloc] 
                initWithNibName:@"MoviesViewController" 
                         bundle:[NSBundle mainBundle]]; 
 
        UIPopoverController *popover = 
            [[UIPopoverController alloc] initWithContentViewController:movies]; 
 
        popover.delegate = self;
        [movies release];
 
        self.popoverController = popover;
        [popover release];
    }
 
    [self.popoverController 
        presentPopoverFromBarButtonItem:sender 
               permittedArrowDirections:UIPopoverArrowDirectionAny 
                               animated:YES];    
}
 
-(IBAction) btnShowMovies:(id) sender {
 
    if (self.popoverController == nil) {
        MoviesViewController *movies = 
            [[MoviesViewController alloc] 
                initWithNibName:@"MoviesViewController" 
                         bundle:[NSBundle mainBundle]]; 
 
        UIPopoverController *popover = 
            [[UIPopoverController alloc] initWithContentViewController:movies]; 
 
        popover.delegate = self;
        [movies release];
 
        self.popoverController = popover;
        [popover release];
    }
 
    CGRect popoverRect = [self.view convertRect:[btn frame] 
                                       fromView:[btn superview]];
 
    popoverRect.size.width = MIN(popoverRect.size.width, 100); 
    [self.popoverController 
        presentPopoverFromRect:popoverRect 
                        inView:self.view 
      permittedArrowDirections:UIPopoverArrowDirectionAny 
                      animated:YES];
}
 
- (void)setDetailItem:(id)newDetailItem {
 
    if (detailItem != newDetailItem) {
        [detailItem release];
        detailItem = [newDetailItem retain];
 
        //---update the view---
        movieSelected.text = [detailItem description];
    }
 
    if (popoverController != nil) {
        [popoverController dismissPopoverAnimated:YES];
    }        
 
}
 
//---called when the user clicks outside the popover view---
- (BOOL)popoverControllerShouldDismissPopover:(UIPopoverController *)popoverController {
 
    NSLog(@"popover about to be dismissed");
    return YES;
}
 
//---called when the popover view is dismissed---
- (void)popoverControllerDidDismissPopover:
(UIPopoverController *)popoverController {
 
    NSLog(@"popover dismissed");    
}
 
- (void)dealloc {
    //---add this---
    [popoverController release];
    [btn release];
    [detailItem release];
    [movieSelected release];
    [super dealloc];
}
In this class, I show two ways in which you can display a PopoverView. The first way is to click on a Bar Button Item view. This is the most common way to display a PopoverView. To display a PopoverView, you first create an instance of the view controller that you want to display in the PopoverView. In this case, it is the MoviesViewController class:
    MoviesViewController *movies = 
        [[MoviesViewController alloc] 
            initWithNibName:@"MoviesViewController" 
                     bundle:[NSBundle mainBundle]];
You then create an instance of the UIPopoverController class and initialize it with the view controller you want to display. You will also need to set the delegate property to self so that all events fired by the PopoverView can be handled in the current view controller:
    UIPopoverController *popover = 
        [[UIPopoverController alloc] initWithContentViewController:movies]; 
 
    popover.delegate = self;
    [movies release];
 
    self.popoverController = popover;
    [popover release];
To display the PopoverView, use the presentPopoverFromBarButtonItem:permittedArrowDirections:animated: method from the UIPopoverController object:
    [self.popoverController 
        presentPopoverFromBarButtonItem:sender 
               permittedArrowDirections:UIPopoverArrowDirectionAny 
                               animated:YES];
The second way to display a PopoverView is when you click on a Round Rect button. In this case, you display it using the presentPopoverFromRect:inView:permittedArrowDirections:animated: method:
    [self.popoverController 
        presentPopoverFromRect:popoverRect 
                        inView:self.view 
      permittedArrowDirections:UIPopoverArrowDirectionAny 
                      animated:YES];
Notice that for this method, you need to specify the exact position and size of the PopoverView to display:
    CGRect popoverRect = [self.view convertRect:[btn frame] 
                                       fromView:[btn superview]];    
    popoverRect.size.width = MIN(popoverRect.size.width, 100);
The setDetailItem: method allows the movie that is selected in the PopoverView to be displayed in the current View window:
- (void)setDetailItem:(id)newDetailItem {
 
    if (detailItem != newDetailItem) {
        [detailItem release];
        detailItem = [newDetailItem retain];
 
        //---update the view---
        movieSelected.text = [detailItem description];
    }
 
    if (popoverController != nil) {
        [popoverController dismissPopoverAnimated:YES];
    }        
 
}
When an item is selected, you would need to dismiss the PopoverView programmatically - you can use the dismissPopoverAnimated: method of the UIPopoverController object. If not, the PopoverView will be automatically dismissed when the user touches the area outside the PopoverView. When a PopoverView dismissed, there are two events that will be fired:
//---called when the user clicks outside the popover view---
-(BOOL)popoverControllerShouldDismissPopover:(UIPopoverController *)popoverController;
 
//---called when the popover view is dismissed---
-(void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController;
To test the application, press Command-R to test the application on the iPad Simulator. Figure 4 shows the PopoverView when you click on the Movies and Show Movie buttons:

Figure 4. Displaying the PopoverView

Displaying a Navigation Controller within a PopoverView

As shown in the previous section, you displayed the content of the UITableViewController class inside a PopoverView. The Table view is commonly associated with a navigation controller, thereby allowing users to drill down the list of items. A good example is the Bookmark button in Mobile Safari (see figure 5).

Figure 5. The Bookmarks PopoverView on the Mobile Safari
In this section, you will learn how to display a navigation controller within the PopoverView.
Using Xcode, create a View-based Application (iPad) project and name it PopOverExample2.
Right-click on the Classes folder in Xcode and select Add->New File….: Select the Cocoa Touch Class item and then select the UIViewController subclass template. Ensure that all the three options for creating the UIViewController subclass item are selected. Name the new class BookMarksViewController.m.
Add another UIViewController subclass item and name it HistoryViewController.m.
In the BookMarksViewController.h file, add in the following code:
#import <UIKit/UIKit.h>
 
@interface BookMarksViewController : UITableViewController {
    NSMutableArray *bookMarksItems;   //---add this---
}
 
@end
In the BookMarksViewController.m file, add in the following code:
#import "BookMarksViewController.h"
#import "HistoryViewController.h"
 
@implementation BookMarksViewController
 
- (void)viewDidLoad {
 
    //---add this---
    //---initialize the array---
    bookMarksItems = [[NSMutableArray alloc] init];
    [bookMarksItems addObject:@"History"];
    [bookMarksItems addObject:@"Bookmarks Bar"];
    [bookMarksItems addObject:@"Apple"];
    [bookMarksItems addObject:@"Yahoo!"];    
    [bookMarksItems addObject:@"Google"];
    [bookMarksItems addObject:@"iPad User Guide"];    
 
    [super viewDidLoad];
    self.clearsSelectionOnViewWillAppear = NO;
}
 
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    // Return the number of sections.
    //---add this---
    return 1;
}
 
- (NSInteger)tableView:(UITableView *)tableView  
 numberOfRowsInSection:(NSInteger)section {
    // Return the number of rows in the section.
    //---add this---
    return [bookMarksItems 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] autorelease];
    }
 
    // Configure the cell...
    //---add this---
    cell.textLabel.text = [bookMarksItems objectAtIndex:indexPath.row];
 
    if (indexPath.row <= 1)
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
 
    return cell;
}
 
- (void)tableView:(UITableView *)tableView 
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
 
    //---add this---
    switch (indexPath.row) { 
        case 0: {
            HistoryViewController *historyViewController = 
                [[HistoryViewController alloc] 
                    initWithNibName:@"HistoryViewController" bundle:nil];
 
            [self.navigationController 
                pushViewController:historyViewController animated:YES];
 
            [historyViewController release];
            break;
        }
        case 1:
            break;
    }
}
 
- (void)dealloc {
    //---add this---
    [bookMarksItems release];
    [super dealloc];
}
Basically, this class displays a Table view with a list of items, such as History, Bookmarks Bar, Apple, etc. When an item is selected (specifically, the first item in the Table view), it will use the pushViewController: method of the navigation controller to navigate to the HistoryViewController object.
In the HistoryViewController.h file, add the following code:
#import <UIKit/UIKit.h>
 
@interface HistoryViewController : UITableViewController {
    NSMutableArray *historyItems;   //---add this---
}
 
@end
In the HistoryViewController.m file, add the following code:
#import "HistoryViewController.h"
 
@implementation HistoryViewController
 
- (void)viewDidLoad {
 
    //---add this---
    //---initialize the array---
    historyItems = [[NSMutableArray alloc] init];
    [historyItems addObject:@"Google"];
    [historyItems addObject:@"Apple"];
    [historyItems addObject:@"Developer Learning Solutions"];
 
    [super viewDidLoad];
    self.clearsSelectionOnViewWillAppear = NO;
}
 
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    // Return the number of sections.
    //---add this---
    return 1;
}
 
- (NSInteger)tableView:(UITableView *)tableView 
 numberOfRowsInSection:(NSInteger)section {
    // Return the number of rows in the section.
    //---add this---
    return [historyItems 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] autorelease];
    }
 
    // Configure the cell...
    //---add this---
    cell.textLabel.text = [historyItems objectAtIndex:indexPath.row];
 
    return cell;
}
 
- (void)dealloc {
    //---add this---
    [historyItems release];
    [super dealloc];
}
Like the BookMarksViewController class, this class also displays a list of items – Google, Apple, and Developer Learning Solutions.
Double-click on the PopOverExample2ViewController.xib file to edit it in Interface Builder.
Add the following views to the View window (see also Figure 6):
  • Toolbar
  • Bar Button Item (set its Identifier property to Bookmarks)

  • Figure 6. Populating the View window
    In the
  • PopOverExample2ViewController.h
  • file, add the following code:
    #import <UIKit/UIKit.h>
     
    @interface PopOverExample2ViewController : UIViewController 
        <UIPopoverControllerDelegate> { //---add this---
        UIPopoverController *popoverController;
    }
     
    //---add this---
    @property (nonatomic, retain) UIPopoverController *popoverController; 
    -(IBAction) btnShowBookmarks:(id) sender;
     
    @end
    Back in Interface Builder, control-click and bookmark Bar Button item to the File’s Owner item. Select
  • btnShowBookmarks
  • .
    In the
  • PopOverExample2ViewController.m
  • file, add the following code:
    #import "PopOverExample2ViewController.h"
     
    //---add this---
    #import "BookMarksViewController.h"
     
    @implementation PopOverExample2ViewController
     
    //---add this---
    @synthesize popoverController;
     
    -(IBAction) btnShowBookmarks:(id) sender {     
     
        if (self.popoverController == nil) {
            BookMarksViewController *bookMarksViewController = 
                [[BookMarksViewController alloc]      
                    initWithNibName:@"BookMarksViewController" 
                             bundle:[NSBundle mainBundle]]; 
     
            bookMarksViewController.navigationItem.title = @"Bookmarks";
            UINavigationController *navController = 
                [[UINavigationController alloc] 
                    initWithRootViewController:bookMarksViewController];
     
            UIPopoverController *popover = 
                [[UIPopoverController alloc] 
                    initWithContentViewController:navController]; 
     
            popover.delegate = self;
            [bookMarksViewController release];
            [navController release];
     
            self.popoverController = popover;
            [popover release];
        }
     
        [self.popoverController 
            presentPopoverFromBarButtonItem:sender                                    
                   permittedArrowDirections:UIPopoverArrowDirectionAny 
                                   animated:YES];
     
    }
     
    - (void)dealloc {
        //---add this---
        [popoverController release];
        [super dealloc];
    }
    The important part of the code above is the following:
            bookMarksViewController.navigationItem.title = @"Bookmarks";
            UINavigationController *navController = 
                [[UINavigationController alloc] 
                    initWithRootViewController:bookMarksViewController];
     
            UIPopoverController *popover = 
                [[UIPopoverController alloc] 
                    initWithContentViewController:navController];
    The above block of code uses a UINavigationController object to display the BookMarksViewController object. The UINavigationController object is then displayed by the PopoverViewController object.
    Press Command-R to test the application on the iPad Simulator. You can now navigate the list shown in the Bookmarks PopoverView (see Figure 7).

    Figure 7. Displaying the navigation controller in the PopoverView

    Summary

    In this article, you had a good look at the new PopoverView available for the iPad. The PopoverView is a very versatile view that you can use to display context-sensitive options for your application. Use the comments below to share with us how you are using the PopoverView.