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.

25 Responses to “Your first iPad split view application”


  • Thank you for this article. I have been looking for some code that will allow me to KEEP the split view in portrait mode as does the SETTINGS app.

    Apple's docs say that it is possible, but I have not been able to figure out or find an example of how to do this. I bet its probably something simple i am missing. Any ideas?

  • any way to change the width of the master view?

    • I would like to change width of master view, too. 256 would be nice, so that the width of detailView is always 768 not matter if it is shown in Portrait oder Landscape mode. Anybody any idea how to change width of masterView?

  • Trying to figure out the same thing as fgrios. I need custom views for both the viewcontrollers, so am creating a splitview programatically. But am not able to keep the split view as is in both case. Any help would be appreciated.

  • Thanks for that update. it works, you just need to take into account frame borders in the detail origin and width. Also, resizing the frames should probably take into account the visibility of the statusbar and rotation back to landscape view. But the technique does do exactly what is needed.

  • Do you know what is the best way to show different detailviews. I want to use the masterview as a navigation system to bring up different views on the right …

    • Hi,

      I don't know if I get you question right. But the split view is the standard way within iPad apps to show master-detail views. So for example use a standard table view at your master view and when the method <code>tableView: didSelectRowAtIndexPath:</code> is called you can access the split view controller with <code>self.splitViewController</code> and change the detail view or even the detail view controller by editing the property <code>viewControllers</code>.

      Hope that helps,
      Andreas

  • There was an example on developer.apple.com on how to use multiple views. http://developer.apple.com/iphone/library/samplec...

  • Is it possible to have the TableView include a Tool Bar or Tab Bar so that the user can change the way the table is displayed (like how the iPod app has "Playlists, Artists, Songs, Videos, More" at the bottom in a Tab Bar)? I can't seem to drag the Tool Bar (or Tab Bar) into the RootViewController that has the TableView in IB.

    • Hi,
      a simple workaround for this would be to create a basic UIView as your master view which contains your toolbar and buttons and also contains a UITableView. On any toolbar action you change the datasource for your table view and call the method reloadData.

      Cheers,
      Andreas

  • I can't understand how to make the UISplitView in portrait mode. What am I doing wrong?

    1). I create .h and .m files and make the class a subclass of UISplitViewController
    2). I add exactly your code, without corrections to my app

    It works just like an ordinary UISplitView.

  • This is a very good article. What about if the app starts in portrait mode? How do you get the master view to display at startup in portrait mode? Using the code above, the master view only displays in portrait orientation after the app has been rotated to landscape orientation at least once.

    Thanks, Art

  • I also had problems making the code work in portrait mode. Do you mind putting up and example project for download?

    • This is what I did and it works fine.

      -(void) viewWillAppear:(BOOL)animated {
      if (self.interfaceOrientation == UIInterfaceOrientationPortrait || self.interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
      UIViewController* master = [self.viewControllers objectAtIndex:0];
      UIViewController* detail = [self.viewControllers objectAtIndex:1];
      [self setupPortraitMode: master detail:detail];
      }

      }

      - (void)setupPortraitMode: (UIViewController *)master detail:(UIViewController *)detail {
      //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 = 448;
      f.size.height = 1024;
      f.origin.x = 321;
      f.origin.y = 0;

      [detail.view setFrame:f];
      }

      - (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) {
      [self setupPortraitMode: master detail:detail];
      }
      else {
      //re-adjust detail view
      CGRect f = detail.view.frame;
      f = detail.view.frame;
      f.size.width = 704;
      f.size.height = 768;
      f.origin.x = 321;
      f.origin.y = 0;

      [detail.view setFrame:f];
      //call super method
      [super willAnimateRotationToInterfaceOrientation:interfaceOrientation duration:duration];
      }
      }

      • Thank you for this code – it works great, and it has saved me a lot of time, ….my app just got rejected because of that private API call to setHidesMasterViewInPortrait, …which I completely forgot about.

        Anyhow, here is a small bug fix in the code. It calls the super viewWillAppear when we're not handling portrait mode. Without that call to super, the main view can disappear.

        -(void) viewWillAppear:(BOOL)animated {
        if (self.interfaceOrientation == UIInterfaceOrientationPortrait || self.interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
        UIViewController* master = [self.viewControllers objectAtIndex:0];
        UIViewController* detail = [self.viewControllers objectAtIndex:1];
        [self setupPortraitMode: master detail:detail];
        }
        else {
        [super viewWillAppear:animated];
        }
        }

        THANKS AGAIN!!!
        -arnox

        • If you're having trouble with subviews not getting initialized properly, you may need a few more things. Here are all the methods that I've added/modified:

          - (void)viewDidLoad {

          self.view.backgroundColor = [[[UIColor alloc] initWithWhite:0.2 alpha:1.0] autorelease];

          if (self.interfaceOrientation == UIInterfaceOrientationPortrait || self.interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
          UIViewController* master = [self.viewControllers objectAtIndex:0];
          UIViewController* detail = [self.viewControllers objectAtIndex:1];
          [master viewDidLoad];
          [detail viewDidLoad];
          } else {
          [super viewDidLoad];
          }
          }

          - (void)viewDidAppear:(BOOL)animated {

          if (self.interfaceOrientation == UIInterfaceOrientationPortrait || self.interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
          UIViewController* master = [self.viewControllers objectAtIndex:0];
          UIViewController* detail = [self.viewControllers objectAtIndex:1];
          [master viewDidAppear:animated];
          [detail viewDidAppear:animated];
          } else {
          [super viewDidAppear:animated];
          }
          }

          -(void) viewWillAppear:(BOOL)animated {

          if (self.interfaceOrientation == UIInterfaceOrientationPortrait || self.interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
          UIViewController* master = [self.viewControllers objectAtIndex:0];
          UIViewController* detail = [self.viewControllers objectAtIndex:1];
          [self setupPortraitMode: master detail:detail];
          } else {
          [super viewWillAppear:animated];
          }
          }

  • I suggest creating a category on UISplitViewController that implements a setter for the
    _hidesMasterViewInPortrait member variable.

    @interface UISplitViewController (ShowMasterInPortrait)
    - (void)setHidesMasterViewInPortrait:(BOOL)hidden;
    @end

    @implementation UISplitViewController (ShowMasterInPortrait)
    - (void)setHidesMasterViewInPortrait:(BOOL)hidden
    {
    _hidesMasterViewInPortrait = (hidden) ? 0 : 1;
    }
    @end

  • I've used your code to have a splitview display both controllers in portrait mode but I have a UITableView that will not load until I rotate the view, any ideas? I'm fairly lost as it seems to be fetching the data just not populating the cells until it is rotated.

    • Hi,
      i am wondering, don't you have the code for this table view controller? If you have it you should be able to control the reload of the table view data on your own.
      Cheers,
      Andreas

  • I just want to be able to start with my master view showing when in portrait mode.

    Just as if the user clicked the button, similar to the email application. This has got to be simple, right? (For extra credit, I'd like to be able to force it to show from the code at a certain point – after some data has been loaded…)

  • is there any way to make the RootViewController displayed on the right side ? please advise

Leave a Reply