Archive for the 'iPad' Category

Show a custom popover view within your iPad App

The possibility to show popover views (modal or non modal) is a really cool thing within the new iPhone OS 3.2. It allows you to display important information, request some input from the user or present some kind of navigation structure. Anyway, it depends on your application but it helps to focus the user on your main content and to avoid too many context (or better: view layout) changes.

Now I am going to give you just a quick example of how we can display a customized popover view within our application. First of all I want to present a simple popover view when a given toolbar button was pressed. The class UIPopoverController offers the functionality to show a popover with any given view controller.

We are also able to limit the popover’s view size with the new property contentSizeForViewInPopover in all UIViewController classes. If you don’t customize this property, the popover will be shown in its default width of 320 px and in full screen height.

After initialization of your UIPopoverController instance you can call the method presentPopoverFromBarButtonItem: permittedArrowDirections: animated:. The first parameter is used to locate the control which creates the popover view. The second parameter permittedArrowDirections allows you to restrict the direction of the shown arrow of the popover. To support all orientations and layout changes, UIPopoverArrowDirectionAny is the best way to go.

-(void) toolbarAction:(id)sender {
   if([self.popoverController isPopoverVisible])
   {
      //close the popover view if toolbar button was touched
      //again and popover is already visible
      //Thanks to @chrisonhismac
 
      [self.popoverController dismissPopoverAnimated:YES];
      return;
   }
 
   //build our custom popover view
   UIViewController* popoverContent = [[UIViewController alloc]
                              init];
   UIView* popoverView = [[UIView alloc]
                        initWithFrame:CGRectMake(0, 0, 300, 400)];
   popoverView.backgroundColor = [UIColor blueColor];
   popoverContent.view = popoverView;
 
   //resize the popover view shown
   //in the current view to the view's size
   popoverContent.contentSizeForViewInPopover =
                              CGSizeMake(300, 400);
 
   //create a popover controller
   self.popoverController = [[UIPopoverController alloc]
               initWithContentViewController:popoverContent];
 
   //present the popover view non-modal with a
   //refrence to the toolbar button which was pressed
   [self.popoverController presentPopoverFromBarButtonItem:sender
               permittedArrowDirections:UIPopoverArrowDirectionUp
               animated:YES];
 
   //release the popover content
   [popoverView release];
   [popoverContent release];
}

In my example application I get the following screen:

So, while showing popover views from toolbar items is very common, it is also often necessary to show popovers when interacting with other UI controls within your screen. Another example will show a popover view triggered from a standard button within my main content view.

I placed a button called popoverButton which performs the following method when activated. This time we use the method presentPopoverFromRect and use the button’s frame as source for the direction of the popover’s arrow. The popover implementation is very sophisticated and places the popover so it fits best at the current screen.

-(void) buttonAction:(id)sender {
   //build our custom popover view
   UIViewController* popoverContent = [[UIViewController alloc]
                  init];
   UIView* popoverView = [[UIView alloc]
                  initWithFrame:CGRectMake(0, 0, 200, 300)];
   popoverView.backgroundColor = [UIColor greenColor];
   popoverContent.view = popoverView;
 
   //resize the popover view shown
   //in the current view to the view's size
   popoverContent.contentSizeForViewInPopover =
                  CGSizeMake(200, 300);
 
   //create a popover controller
   self.popoverController = [[UIPopoverController alloc]
               initWithContentViewController:popoverContent];
 
   //present the popover view non-modal with a
   //refrence to the button pressed within the current view
   [self.popoverController presentPopoverFromRect:popoverButton.frame
               inView:self.view
               permittedArrowDirections:UIPopoverArrowDirectionAny
               animated:YES];
 
   //release the popover content
   [popoverView release];
   [popoverContent release];
}

Using this code will create another popover which will look as shown in the picture below:

I really like popovers and also the possibility to show them in a modal manner. It is recommended to use modal popover views very rarely because the user’s focus gets lost from the main content. Use them only when it is really necessary and some kind of mandatory input is needed (i.e. accepting terms of use).

You will find the source code of this example at my github repository. The project is called PopoverView.

Cheers,
Andreas

Your first iPad split view application

The split view is something very cool within the new iPhone OS 3.2. It allows us to visualize a master-detail view in a very simple manner. Combined with iPads’ big display of 1024×748 pixels it will be possible to create even better, more user-friendly and valuable applications.

Basically, the split view consists of two separate views. The master view will be shown in a 320 pixel width part on your screen if your iPad is at landscape orientation, otherwise the master view will be accessible as a popover view. The details view should show your main content and will be at full size if your iPad is at portrait orientation. Mainly the user will be focused at your details view and this fact you should keep in mind.

Creating a split view pragmatically within your application is a easy task. The following code will show this:

//create the master view
MasterViewController *masterView = [[MasterViewController alloc]
                initWithNibName:@"Master"
                bundle:[NSBundle mainBundle]];
 
//create the details view
DetailsViewController *detailsView = [[DetailsViewController alloc]
                initWithNibName:@"Details"
                bundle:[NSBundle mainBundle]];
 
//create the split view
UISplitViewController *splitController =
                [[UISplitViewController alloc] init];
 
//set the view controllers array
splitController.viewControllers = [NSArray
                arrayWithObjects:masterView, detailsView, nil];
 
//show split view as the main view
[window addSubview:splitController.view];
[window makeKeyAndVisible];
 
//release view
[masterView release];
[detailsView release];

Next we want to access the master view via a popover view, when the iPad is in portrait mode. The shouldAutorotateToInterfaceOrientation: method of your master and detail controller must support all orientations, otherwise it wont be possible to rotate a split view. After that implement the UISplitViewControllerDelegate protocol in any class and set the delegate of your split view controller. The methods within this protocol will be called whenever the master view will be hidden or shown again.

In my example I created another class which gets a reference to the details view controller and holds the toolbar object we will use to present the popover button. The following code is pretty simple and contains the three methods of the UISplitViewControllerDelegate protocol. The first method is called whenever the master view will be hidden. So in this case we will add a toolbar with the given popover button to our detail view. You are free to label the button as you like. The second method is called whenever the master view will be shown again. Now we simply hide the toolbar. So, that’s pretty all. The displaying logic of your master view within a popover view is done automatically, but you can still modify it within the last method (which is empty in my case).

//the master view controller will be hidden
- (void)splitViewController:(UISplitViewController*)svc
    willHideViewController:(UIViewController *)aViewController
    withBarButtonItem:(UIBarButtonItem*)barButtonItem
    forPopoverController:(UIPopoverController*)pc {
 
  if(toolBar == nil) {
    //set title of master button
    barButtonItem.title = @"Show Master";
 
    //create a toolbar
    toolBar = [[UIToolbar alloc]
                    initWithFrame:CGRectMake(0, 0, 1024, 44)];
    [toolBar setItems:[NSArray arrayWithObject:barButtonItem]
                    animated:YES];
  }
 
  //add the toolbar to the details view
  [detailController.view addSubview:toolBar];
}
 
//the master view will be shown again
- (void)splitViewController:(UISplitViewController*)svc
    willShowViewController:(UIViewController *)aViewController
    invalidatingBarButtonItem:(UIBarButtonItem *)button {
 
  //remove the toolbar
  [toolBar removeFromSuperview];
}
 
// the master view controller will be displayed in a popover
- (void)splitViewController:(UISplitViewController*)svc
    popoverController:(UIPopoverController*)pc
    willPresentViewController:(UIViewController *)aViewController {
 
  //empty for now
 
}

Well, you need to do something more than this if you want to create really useful split view applications. Often it will be wise to use a table view as your master. Some things I found out while playing around with the split view are:

  • You are not able to present a split view as a modal view
  • If the master or detail view doesn’t allow all interface orientations, the split view will not work properly. So if you don’t see the master view check your shouldAutorotateToInterfaceOrientation method within your view controller.

You will find the source code of this example at my github repository. The project is called MasterDetail.

Cheers,
Andreas

Update:

Some people asked me if it is possible to keep the master view visible even in portrait mode. I tried around and found following working solution:

Create a subclass of UISplitViewController and just overwrite the method willAnimateRotationToInterfaceOrientation: duration:. This method will be called whenever the orientation of a view is going to be changed. Everything you have to do is to check if the interface will change to a portrait method and if that is true, adjust the visible frames of your master and detail views. Find below the code I used to get this working:

/**
 * Sent to the view controller just before
 * the user interface begins rotating.
 */
- (void)willAnimateRotationToInterfaceOrientation:
            (UIInterfaceOrientation)interfaceOrientation
            duration:(NSTimeInterval)duration {
 
      //get master and detail view controller
      UIViewController* master = [self.viewControllers objectAtIndex:0];
      UIViewController* detail = [self.viewControllers objectAtIndex:1];
 
      //only handle the interface orientation
      //of portrait mode
      if(interfaceOrientation == UIInterfaceOrientationPortrait ||
            interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
            //adjust master view
            CGRect f = master.view.frame;
            f.size.width = 320;
            f.size.height = 1024;
            f.origin.x = 0;
            f.origin.y = 0;
 
            [master.view setFrame:f];
 
            //adjust detail view
            f = detail.view.frame;
            f.size.width = 830;
            f.size.height = 1024;
            f.origin.x = 320;
            f.origin.y = 0;
 
            [detail.view setFrame:f];
      }
      else {
            //call super method
            [super willAnimateRotationToInterfaceOrientation:interfaceOrientation
                        duration:duration];
      }
}

Now, just use your own UISplitViewController subclass within your code and the master view should stay visible even in portrait mode. I hope this code may answer your questions and will be helpful for you.