10 May 2010

iPad for iPhone Developers 101: UISplitView Tutorial

 

Let's Display a List of Monsters in a Split View!

Let's Display a List of Monsters in a Split View!

This is the first part of a three-part series to help get iPhone Developers up-to-speed with iPad development by focusing on three of the most interesting new classes (at least to me): UISplitView, UIPopoverController, and Custom Input Views. (Jump to Part 2 or Part 3 in the series.)

We will make an iPad app from scratch that makes use of all of these new capabilities. The app will display a list of monsters from the Cocos2D game I recently released, and let you change the color of the label using a popover view. Finally, you’ll be able to change your favorite way of killing each monster by using a custom input view.

By the end, you’ll have a good handle of some of the most important new features of iPad development and will be able to hit the ground running!

Adding a Split View

In iPad development, it would rarely make sense to have a full-screen UITableView like we do so often in iPhone programming – there’s just too much space. To make better use of that space, the Split View comes to the rescue!

The Split View lets you carve up the iPad screen into two sections and display a view controller in each side. It’s typically used to have navigation on the left hand side, and a detail view on the right hand side.

Screenshot of Split View App We'll Be Making

So let’s start making this!

Starting From Scratch

Although we could use the “Split View-based Application” template as a starting point, we’re going to start from scratch and pick the “Window-based Application” template. This is so we can get a better understanding of exactly how the UISplitView works. This knowledge will be helpful as we continue to use it in future projects (even if we choose the split view template in the future to save time!)

So create a new Project, and choose the “Window-based Application” template. Make sure to choose “iPad” in the Product dropdown – Universal Apps is a topic for another day.

Name your project “MathMonsters.” You can go ahead and compile and run if you want, but all you’ll see is a blank screen at this point.

Go ahead and open up MainWindow.xib. You’ll see a huge window the size of the iPad screen. Go ahead and add your Split View Controller by dragging it from the library into the MainWindow.xib.

Split View Controller in Interface Builder

Note that there are two elements underneath the Split View Controller. The first element is the view that appears on the left hand side – and in this case it’s a UINavigationController with a table view inside. The second element is the view that appears on the right hand side – and in this case it’s a plain UIViewController.

We’re going to want to have our own view controllers inside of here instead of the ones that are put in there by default. So let’s take a quick break to get some placeholders for those, and then we’ll come back here.

Custom View Controller Placeholders

We’re going to make two view controller placeholders to put inside our split view – a table view controller, and a “plain” view controller.

Let’s start with the table view controller. Go to File\New, choose UIViewController subclass, make sure “Targeted for iPad” and “UITableViewController subclass” are checked, and click “Next”. Name the file “LeftViewController” and click “Finish”.

Scroll down to “numberOfSectionsInTableView” and replace “number of sections” with “1″. Similarly, scroll down to “numberOfRowsInSection” and replace “number of rows in section” with “10″.

This way, we’ll just have 10 empty rows to look at when we test this thing out later.

Next, let’s make the placeholder for the right hand side. Go to File\New, choose UIViewController subclass, make sure “Targeted for iPad” and “With XIB for user interface” are checked and “UITableViewController subclass” is NOT checked, and click “Next”. Name the file “RightViewController” and click “Finish”.

Then double click RightViewController.xib and drag a label into the middle, and make it say “Hello, World!” or something so we know it’s working when we test it out later.

Ok that should do it! Now back to MainWindow.xib.

Adding a Split View Controller, Continued

Now that we have our placeholder view controllers in place, let’s hook them up.

Inside MainWindow.xib, expand the tree for the Split View Controller until you see the entry that reads “Table View Controller (Root View Controller)”. Click on that row, then go to the fourth tab in the inspector and change the Class to “LeftViewController”.

Settings for LeftViewController in Interface Builder

Next, click on the row right underneath in MainWindow.xib that reads “View Controller”. Similarly to above, in the fourth tab in the inspector change the Class to “RightViewController.” Also, in the first tab in the inspector change the NIB name to “RightViewController.”

Ok – we’ve made our split view controller, now we just need to add it to our window! To do this, we’ll need to make an outlet for the UISplitViewController. While we’re at it, we’ll make some outlets for our left/right View Controllers as well.

Open up MathMonstersAppDelegate.h and modify it to look like the following:

#import <UIKit/UIKit.h>
 
@class LeftViewController;
@class RightViewController;
 
@interface MathMonstersAppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
    UISplitViewController *_splitViewController;
    LeftViewController *_leftViewController;
    RightViewController *_rightViewController;
}
 
@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UISplitViewController *splitViewController;
@property (nonatomic, retain) IBOutlet LeftViewController *leftViewController;
@property (nonatomic, retain) IBOutlet RightViewController *rightViewController;
 
@end

Note all we did here was declare a few outlets.

Then open up MathMonstersAppDelegate.m and modify it to look like the following:

#import "MathMonstersAppDelegate.h"
 
@implementation MathMonstersAppDelegate
 
@synthesize window;
@synthesize splitViewController = _splitViewController;
@synthesize leftViewController = _leftViewController;
@synthesize rightViewController = _rightViewController;
 
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
    (NSDictionary *)launchOptions {    
 
    // Override point for customization after application launch
    [window addSubview:_splitViewController.view];
    [window makeKeyAndVisible];
 
    return YES;
}
 
 
- (void)dealloc {
    [window release];
    self.splitViewController = nil;
    self.leftViewController = nil;
    self.rightViewController = nil;
    [super dealloc];
}
 
@end

Again, no big surprises here – we just synthesized our outlets, did some memory cleanup, and added the split view controller to the main window.

Ok – make sure those files are saved, and head back to MainWindow.xib. Control-drag from “Math Monsters App Delegate” to the “Split View Controller”, “Left View Controller”, and “Right View Controller”, connecting each to the appropriate outlet.

That’s it! Compile and run the app, and you’ll see your “Hello, World!” UISplitView.

Making Our Model

The next thing we need to do is define a model for the data we want to display. I was tempted to use Core Data for this since we just had a tutorial series on the matter, but didn’t want to complicate things so we’re going with a simple model with no data persistence.

So anyway – let’s make a class representing the Monsters we want to display. Go to File\New, choose Objective-C class, make sure “Subclass of” is “NSObject”, and click “Next”. Name the file “Monster” and click “Finish”.

We’re just going to create a simple class with some member variables with attributes about each Monster we want to display. Replace Monster.h with the following contents:

#import <Foundation/Foundation.h>
 
typedef enum {
    Blowgun = 0,
    NinjaStar,
    Fire,
    Sword,
    Smoke,
} Weapon;
 
@interface Monster : NSObject {
    NSString *_name;
    NSString *_descr;
    NSString *_iconName;
    Weapon _preferredWayToKill;
}
 
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *descr;
@property (nonatomic, copy) NSString *iconName;
@property (nonatomic, assign) Weapon preferredWayToKill;
 
- (Monster *)initWithName:(NSString *)name descr:(NSString *)descr 
    iconName:(NSString *)iconName preferredWayToKill:(Weapon)preferredWayToKill;
 
@end

And Monster.m with the following:

#import "Monster.h"
 
@implementation Monster
@synthesize name = _name;
@synthesize descr = _descr;
@synthesize iconName = _iconName;
@synthesize preferredWayToKill = _preferredWayToKill;
 
- (Monster *)initWithName:(NSString *)name descr:(NSString *)descr 
    iconName:(NSString *)iconName preferredWayToKill:(Weapon)preferredWayToKill {
    if ((self = [super init])) {
        self.name = name;
        self.descr = descr;
        self.iconName = iconName;
        self.preferredWayToKill = preferredWayToKill;
    }
    return self;
}
 
- (void) dealloc
{
    self.name = nil;
    self.descr = nil;
    self.iconName = nil;
    [super dealloc];
}
 
@end

That’s it for defining the model – so let’s hook it up to our left side view!

Displaying the Monster List

Open up LeftViewController.h and add a new member variable/property for a monsters arary like the following:

#import <UIKit/UIKit.h>
 
@interface LeftViewController : UITableViewController {
    NSMutableArray *_monsters;
}
 
@property (nonatomic, retain) NSMutableArray *monsters;
 
@end

Then add a few tweaks to LeftViewController.m to start using this new array:

// At top, under #import
#import "Monster.h"
 
// Under @implementation
@synthesize monsters = _monsters;
 
// In numberOfRowsInSection, replace return 10 with:
return [_monsters count];
 
// In cellForRowAtIndexPath, after "Configure the cell..."
Monster *monster = [_monsters objectAtIndex:indexPath.row];
cell.textLabel.text = monster.name;
 
// In dealloc
self.monsters = nil;

That’s it for the table view. Now let’s just populate it with some default monsters!

First, download some art from my most recent app. Drag the images into your Resources folder, make sure “Copy items into destination group’s folder (if needed)” is checked, and click “Add.”

Then open up MathMonstersAppDelegate.m and add the following code:

// At top, under #import
#import "Monster.h"
#import "LeftViewController.h"
 
// At top of application didFinishLaunchingWithOptions:
NSMutableArray *monsters = [NSMutableArray array];
[monsters addObject:[[[Monster alloc] initWithName:@"Cat-Bot" 
    descr:@"MEE-OW" iconName:@"meetcatbot.jpg" 
    preferredWayToKill:Sword] autorelease]];
[monsters addObject:[[[Monster alloc] initWithName:@"Dog-Bot" 
    descr:@"BOW-WOW" iconName:@"meetdogbot.jpg" 
    preferredWayToKill:Blowgun] autorelease]];
[monsters addObject:[[[Monster alloc] initWithName:@"Explode-Bot" 
    descr:@"Tick, tick, BOOM!" iconName:@"meetexplodebot.jpg" 
    preferredWayToKill:Smoke] autorelease]];
[monsters addObject:[[[Monster alloc] initWithName:@"Fire-Bot" 
    descr:@"Will Make You Steamed" iconName:@"meetfirebot.jpg" 
    preferredWayToKill:NinjaStar] autorelease]];
[monsters addObject:[[[Monster alloc] initWithName:@"Ice-Bot" 
    descr:@"Has A Chilling Effect" iconName:@"meeticebot.jpg" 
    preferredWayToKill:Fire] autorelease]];
[monsters addObject:[[[Monster alloc] initWithName:@"Mini-Tomato-Bot" 
    descr:@"Extremely Handsome" iconName:@"meetminitomatobot.jpg" 
    preferredWayToKill:NinjaStar] autorelease]];
_leftViewController.monsters = monsters;

Compile and run the app, and if all goes well you should now see the list of bots on the left hand side!

Screenshot of Monsters Listed on Left Hand Side

Displaying Bot Details

Next, let’s set up the right hand side so we can see the details for a particular bot.

Open up RightViewController.xib and delete the placeholder label you put down earlier.

To help make layout easier, you may wish to select “Layout\Show Layout Rectangles”. Then drag the following controls onto the screen.

  • A 95×95 UIImageView
  • A UILabel up top of height 45, with font Helvetica Bold, size 36.
  • Two UILabels underneath of height 45, with font Helvetica, size 24.
  • A 70×70 IImageView

The next step is to make sure we set up the auto-resizing behaviour properly. This is especially important for the iPad since apps are required to handle autorotation properly for any orientation.

In our case, there are two views that we want to dynamically resize based on the size of the view: the top two labels. So click on those labels, go to the third tab in the Inspector and set the auto-resizing options as follows:

Autosize Settings for Toolbar

That way they will grow/shrink on the right hand side based on how much space is available for the view.

Ok that’s it for screen layout for now – let’s hook these up to some class outlets. Add some outlets to RightViewController.h like the following:

#import <UIKit/UIKit.h>
 
@class Monster;
 
@interface RightViewController : UIViewController {
    Monster *_monster;
    UILabel *_nameLabel;
    UILabel *_descrLabel;
    UIImageView *_iconView;
    UIImageView *_weaponView;
}
 
@property (nonatomic, retain) Monster *monster;
@property (nonatomic, retain) IBOutlet UILabel *nameLabel;
@property (nonatomic, retain) IBOutlet UILabel *descrLabel;
@property (nonatomic, retain) IBOutlet UIImageView *iconView;
@property (nonatomic, retain) IBOutlet UIImageView *weaponView;
 
@end

Here we added member variables for the various UI elements we just added (that need to dynamically change). Note we also added a member variable for the monster this view controller should display.

While we’re at it, add the following code to RightViewController.m to display the information from the monster:

// At top, under #import
#import "Monster.h"
 
// Under @implementation
@synthesize monster = _monster;
@synthesize nameLabel = _nameLabel;
@synthesize descrLabel = _descrLabel;
@synthesize iconView = _iconView;
@synthesize weaponView = _weaponView;
 
// In didReceiveMemoryWarning
self.nameLabel = nil;
self.descrLabel = nil;
self.iconView = nil;
self.weaponView = nil;
 
// In dealloc
self.monster = nil;
self.nameLabel = nil;
self.descrLabel = nil;
self.iconView = nil;
self.weaponView = nil;
 
// New method
- (void)refresh {
    _nameLabel.text = _monster.name;
    _iconView.image = [UIImage imageNamed:_monster.iconName];
    _descrLabel.text = _monster.descr;
    if (_monster.preferredWayToKill == Blowgun) {
        _weaponView.image = [UIImage imageNamed:@"blowgun.jpg"];
    } else if (_monster.preferredWayToKill == Fire) {
        _weaponView.image = [UIImage imageNamed:@"fire.jpg"];
    } else if (_monster.preferredWayToKill == NinjaStar) {
        _weaponView.image = [UIImage imageNamed:@"ninjastar.jpg"];
    } else if (_monster.preferredWayToKill == Smoke) {
        _weaponView.image = [UIImage imageNamed:@"smoke.jpg"];
    } else {
        _weaponView.image = [UIImage imageNamed:@"sword.jpg"];
    }
}
 
// Another new method
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self refresh];
}

Now, go back to RightViewController.xib and control-drag from “File’s Owner” to each of the controls to hook them up to their corresponding outlets.

Ok, we’re close enough to test this out! Go back to MathMonstersAppDelegate.m and make the following tweaks:

// Up top, under #import
#import "RightViewController.h"
 
// Inside application didFinishLaunchingWithOptions, 
// right after setting monsters on leftViewController...
_rightViewController.monster = [monsters objectAtIndex:0];

Compile and run the app, and if all goes well you should see some monster details on the right:

Screenshot of displaying Monster Details on Right Hand Side

Note that selecting the monsters on the right does absolutely nothing, however. That is what we’ll do next!

Hooking Up The Left With the Right

Time to play matchmaker and hook these two sides together.

There are many different strategies for how to best accomplish this. In the Split View Application template they give the left view controller a pointer to the right view controller, and the left view controller sets a property on the right view controller when a row gets selected. The right view controller overrides the property to update the view when the property is updated.

That works fine, but we’re going to follow the approach suggested in the UISplitViewController class reference here – use delegates.

The basic idea is we’re going to define a protocol with a single method – “selectedBotChanged.” Our right hand side will implement this method, and our left hand side will accept a delegate of somebody who wants to know about this.

So let’s see this in code. Go to File\New, choose Objective-C class, make sure “Subclass of” is “NSObject”, and click “Next”. Name the file “MonsterSelectionDelegate” and click “Finish”.

Replace MonsterSelectionDelegate.h with the following:

#import <Foundation/Foundation.h>
 
@class Monster;
 
@protocol MonsterSelectionDelegate
- (void)monsterSelectionChanged:(Monster *)curSelection;
@end

Then delete MonsterSelectionDelegate.m, since we don’t need it for a protocol.

Now, let’s modify the LeftViewController to take a MonsterSelectionDelegate. Make the following changes to LeftViewController.h:

// At top, under #import
#import "MonsterSelectionDelegate.h"
 
// Inside LeftViewController
id<MonsterSelectionDelegate> _delegate;
 
// Under @property
@property (nonatomic, assign) id<MonsterSelectionDelegate> delegate;

And the following changes to LeftViewController.m:

// Under @implementation
@synthesize delegate = _delegate;
 
// Inside didSelectRowAtIndexPath, remove commented stuff and put:
if (_delegate != nil) {
    Monster *monster = (Monster *) [_monsters objectAtIndex:indexPath.row];
    [_delegate monsterSelectionChanged:monster];
}
 
// Inside dealloc
self.delegate = nil;

Then, we’ll modify RightViewController to implement the delegate. Make the following changes to RightViewController.h:

// At top, under #import
#import "MonsterSelectionDelegate.h"
 
// Class declaration line
@interface RightViewController : UIViewController <MonsterSelectionDelegate> {

Then add the following to RightViewController.m:

- (void)monsterSelectionChanged:(Monster *)curSelection {
    self.monster = curSelection;
    [self refresh];
}

And for the final step, make the following modification to MathMonstersAppDelegate.m inside didFinishLaunchingWithOptions right after the last line we added:

_leftViewController.delegate = _rightViewController;

That’s it! Compile and run the app, and you now should be able to select between the monsters like the following:

Screenshot of a Different Monster Selected

In case you’re wondering what the advantages are for using delegates like we just did, note that there are no imports between the left side view and the right side view. This means that it would be a lot easier to re-use these views in other projects or swap out the views with other views, since we’ve been quite clear about what each one expects.

So far so good with split views! Except there’s one problem left – if we rotate the phone to vertical orientation, we can’t see the men of monsters anymore, so have no way to switch between them. Luckily, Apple has made an easy way to remedy this – which we’ll do next!

Adding a Toolbar and Popover List

Apple’s standard solution to this situation is for you to put a toolbar (or button of some kind) on the right view, and tap a button to bring up a popover view showing the content from the left view.

Apple has a built in API to make this pretty easy. The basic idea is you register the right hand side as a delegate of the UISplitViewController. This way the UISplitViewController can send the right side a notification when it hides the left hand side – as well as a handy button that you can add to the toolbar to use to display the left hand side in a popover.

So let’s give this a try. Open up RightViewController.xib and drag a UIToolbar to the top of the view. You might have to move everything else down a bit. Also, delete the default Bar Button Item it adds in.

Also and this is important – by default the auto-resizing behavior for the UIToolbar assumes it’s on the bottom of the view, so fix the settings to be proper for its location in the top of the view as follows:

Autosize for Toolbar

Then make the following changes to RightViewController.h:

// Add UISplitViewControllerDelegate to the list of protocols
@interface RightViewController : UIViewController <MonsterSelectionDelegate, 
    UISplitViewControllerDelegate> {
 
// Inside the class definition
UIPopoverController *_popover;
UIToolbar *_toolbar;  
 
// In the property section
@property (nonatomic, retain) UIPopoverController *popover;
@property (nonatomic, retain) IBOutlet UIToolbar *toolbar;

And the following to RightViewController.m:

// After @implementation
@synthesize popover = _popover;
@synthesize toolbar = _toolbar;
 
// In deallloc
self.popover = nil;
self.toolbar = nil;
 
// In monsterSelectionChanged
if (_popover != nil) {
    [_popover dismissPopoverAnimated:YES];
} 
 
 
// New functions
- (void)splitViewController: (UISplitViewController*)svc 
    willHideViewController:(UIViewController *)aViewController 
    withBarButtonItem:(UIBarButtonItem*)barButtonItem 
    forPopoverController: (UIPopoverController*)pc {
    barButtonItem.title = @"Monsters";
    NSMutableArray *items = [[_toolbar items] mutableCopy];
    [items insertObject:barButtonItem atIndex:0];
    [_toolbar setItems:items animated:YES];
    [items release];
    self.popover = pc;
}
 
- (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.popover = nil;
}

Two last things. First go back to RightViewConntroller.xib and control-drag from File’s Owner to the Toolbar to hook it up to its outlet. Secondly open up MainWindow.xib and control-drag from “Split View Controller” to “Right View Controller” to set the right view controller as the delegate of the split view controller.

That’s it! Compile and run the project and now when you rotate, a button will appear on your toolbar that you can tap for a popover as follows:

Popover for Split View Left Side

Pretty cool, except that’s a little long eh? There’s a trivial way to fix it – add the following to viewDidLoad in the LeftViewController:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.clearsSelectionOnViewWillAppear = NO;
    self.contentSizeForViewInPopover = CGSizeMake(320.0, 300.0);
}

Changing size for Popover View

Using a UINavigationController Instead

In this project, we’re using a custom view with a toolbar on the right hand side. However in your projects, you might want to use a UINavigationController on the other side instead. How can you set it up so that the bar button item goes into the UINavigationController’s toolbar?

To do this, all you need to do is store a reference to the navigation controller, and in the willHideViewController method simply set the left bar button item for the root view controller’s UINavigationItem as follows:

UINavigationItem *navItem = 
    [[_rightNavController.viewControllers objectAtIndex:0] navigationItem];
[navItem setLeftBarButtonItem:barButtonItem animated:YES];

And That’s A Wrap!

So – that’s how you can use UISplitViewController, from the ground up. Note in practice you’re likely to just use the UISplitViewTemplate to save time – but now you should know much better how it works.

Here’s a copy of all of the code we’ve developed so far.

Continue on with the next part of the series, where we cover how to use popovers on the iPad!


Category: iPad

Tags: , ,

74 Comments

  1. Alon Ezer (2 comments) says:

    Thanks so much! really cleared out the UISplitViewController for me!!

    Thanks again!

  2. Alex Caesar (3 comments) says:

    other than the split view controller, is there anything else that the ipad brings to the coding scene?

  3. Ray Wenderlich (874 comments) says:

    @Alex: Yeah, a bunch more… to me, the biggies are popover controllers, custom input views, more screen real estate, the requirement to handle all orientations, and improved text handling.

    The iPad programming guide by Apple is a good read!

  4. Billy Coover (2 comments) says:

    Fantastic tutorial!

  5. Raf (1 comments) says:

    Great article, thank you! It really helped me out. I was actually looking for an article that demonstrated how to make the left view something other than a subclass of UITableViewController. Reading this gave me the clues I needed. Thanks.

  6. Rolf (3 comments) says:

    Great tutorial! Thank you very much.
    Is it possible to add on the right side a cocs2d based game.
    Thanks.

  7. Mahesh (2 comments) says:

    hello rey,
    i have multiple root views in left pane of split view. when i click back on root view navigation button i come back but it losses the events. means when click on that table view there is no change at right side.
    Please help..

  8. Ray Wenderlich (874 comments) says:

    @Rolf: It should be – if you look at the Cocos2D templates it calls [[CCDirector sharedDirector] attachInView:xxx], you should be able to replace that with any view (rather than defaulting to the entire window). Haven’t tried it myself though.

    @Mahesh: I’m not sure what you’re asking, but maybe what you’re trying to do is get the right hand side to change when the user goes back in the table view to the left. If that’s the case, you’ll need to send an update to the right hand view to get it to clear itself (or switch out the view on the right hand side, depending on what you’re going for).

  9. Rolf (3 comments) says:

    Hello Ray

    I was not successful in attaching the right view to CCDirector. Could you demonstrate that in your next tutorial ? That would be great!

    Thanks in advance.

  10. Ray Wenderlich (874 comments) says:

    @Rolf: Btw it’s definitely doable, I just did it for a project I’m working on at the moment. The way I did it was to add a custom view controller on the right hand side of a UISplitView, with an empty UIView inside. Then I basically moved all of the CCDirector code from the App Delegate to my custom view’s viewDidLoad method, and attached the director to the empty UIView, and it worked great! I doubt I am going to write a tutorial on this though.

  11. Rolf (3 comments) says:

    Hallo Ray

    absolutely correct. I did it too last Friday. It would be really waste of time to write a tutorial on this straight forward implementation.

    Bye

  12. CyberGreg (2 comments) says:

    I’m curious why this:
    splitViewController = _splitViewController

    I’ve not seen this done before.
    Thanks

  13. Ray Wenderlich (874 comments) says:

    @CyberGreg: Check out my reply to @Jason in this tutorial, where I discuss why I use the underscore convention:

    http://www.raywenderlich.com/352/how-to-make-a-simple-iphone-game-with-cocos2d-tutorial

  14. vincefried (1 comments) says:

    Thank you so much for this tutorial! I’ve been searching days for such a tutorial. Now, my app is finished.

  15. pierre marais (1 comments) says:

    what a lesson, great, just one question, how do I setup my popover so that the UITableview (which has got more line items, not fitting onto the view) to scroll up and down within the popover window?
    thanks for your response

  16. Unknown (2 comments) says:

    Thank you so much for this tutorial!

    I have a question.

    how we could show Popover List under the click of more than one button.

    please help.

  17. Ray Wenderlich (874 comments) says:

    @vincefried: Congrats on finishing your app! :]

    @pierre: UITableView is a subclass of UIScrollView, so it should automatically scroll to show its content!

    @Unknown: I’m not quite sure what you’re asking here?

  18. Rick (7 comments) says:

    Well, I must be missing something. I have followed the tutorial and rechecked my code, but when I run, I get just the right side taking up the whole screen.
    The left side controller is getting called for the rows, sections, and cells, but doesn’t show up.
    Any ideas?

  19. Ray Wenderlich (874 comments) says:

    @Rick: Have you tried running the sample project – does that work for you?

    What happens if you launch in landscape mode? I’d start by double checking everything is set up right in the XIB.

  20. Rick (7 comments) says:

    Hmm, it works in landscape. So, in portrait, shouldn’t there be a popover button?
    Where is the sample project?
    Thanks

  21. Ray Wenderlich (874 comments) says:

    @Rick: The sample project is listed at the bottom of the post. If it works in landscape but not in portrait, it sounds like there’s an issue with the UISplitViewControllerDelegate methods – make sure you have set the right side as the delegate for the split view controller and that you’ve followed the instructions in “Adding a Toolbar and Popover List” correctly.

  22. Rick (7 comments) says:

    OK, I think I see. I created a UISplitViewController using the template and they supply the popover. I guess we add that later in the tutorial.
    Everything is cool now.

  23. Cod (2 comments) says:

    Hello Ray,

    Thanks for your tutorial, everything works fine for me.
    Still, for further development, i have a question: It is possible that the LeftViewController’s assigned class to be inherited from UIViewController and not from UITableViewController?

    This can become useful when we don’t need table in the left view but a text field or something else.

    Many thanks.

  24. Ray Wenderlich (874 comments) says:

    @Cod: Yes, you can put any kind of view controller in the left side of the split view. Just drag a regular view controller on top of the default table view controller in IB.

  25. Cod (2 comments) says:

    Working!

    Many thanks.

  26. Rick (7 comments) says:

    My _toolbar reference keeps getting lost before the calls to willHideViewController and willShowViewController so of course the barButtonItem doesn’t show up.
    It is being instantiated earlier as I have put breaks to check. Somewhere between didFinishLaunchingWithOptions and willHideViewController the pointer goes to zero. Also, all of the other IBOutlets are zeroed.

    I wonder if the issue is somewhere in here. You said:
    ————————————————-
    Secondly open up MainWindow.xib and control-drag from “Split View Controller” to “Right View Controller” to set the right view controller as the delegate of the split view controller.
    ————————————————-
    but MainWindow.xib did not have a rightViewController, so I dragged one onto the Doc window. Was that the wrong thing to do?

    Thanks

  27. Rick (7 comments) says:

    OK, I removed the rightViewController and dragged to the rightViewController under the splitViewController. I had been viewing in the icon mode and therefore didn’t see it. Now I have a toolbar; although when it shows up in portrait mode, there are no items on it.

    I’ll keep playing with it.

  28. Rick (7 comments) says:

    OK, got it. When I dragged to tool bar onto the view, I had the far left side off of the far left side of the view, so the item wasn’t showing.
    Working now.
    thanks

  29. Ray Wenderlich (874 comments) says:

    @Rick: w00t glad you got it working!

  30. Richard (6 comments) says:

    I could not find this explanation anywhere else. Thanks for doing this. I needed it because I did not like how the template names the classes , and I did not want to go through the steps to rename all of the classes. Plus it lets me more completely understand what is going on behind the IB.

    Keep up the great blog. Richard.

  31. Ray Wenderlich (874 comments) says:

    @Richard: Exactly, stuff like this is often skipped over in documentation or programming books you might read, hence why I wanted to cover this, as it confused me for a while! Glad it came in handy! :]

  32. Manjit Bedi (1 comments) says:

    Thanks very much for making this tutorial.

    It is really got the concepts associated with a split view controller across.

  33. Wikki (3 comments) says:

    Hi

    Ray. Great work!.

    My Question is :- How I can show leftViewController on right side and RightViewController on left side.

    please help me.

  34. Jone (3 comments) says:

    Hi Ray. Nice tutorial.

    I want to show UITableViewCells on Right side and Detailed View on left side when iPad in portrait view.
    It would be exactly same state in landscape mode as in this tutorial.

    I need help. Thanks in advance.

  35. Jone (3 comments) says:

    sorry Right question is:-

    Hi Ray. Nice tutorial.
    I want to show UITableViewCells on Right side and Detailed View on left side when iPad in landscape mode.

    It would show exactly same UITableViewCells in portrait mode as in this tutorial.
    I need help. Thanks in advance.

  36. Ray Wenderlich (874 comments) says:

    @Wikki: You should be able to use any view controller you want on the left side and/or right side. I just named them that way to keep things easily understandable in the tutorial.

    @Jone: Hm… it sounds like you want the table view to be on the right in landscape, but the left in portrait… UISplitView isn’t really designed to work like that, but if you really wanted to do that, one solution is to have a view controller that has two modes: table mode and detail mode, and then switches between the two as the interface rotates.

  37. Jone (3 comments) says:

    Hi Ray

    I did not understand. Please Elaborate this sentence so that i can work.

    “one solution is to have a view controller that has two modes: table mode and detail mode, and then switches between the two as the interface rotates.”

    please arrange the tutorial for that so that most of people wanted this thing.

    Please help us.
    Thanks.

  38. Ray Wenderlich (874 comments) says:

    @Jone: When I write tutorials, I tend to write them on general concepts that can be useful to a wide variety of developers, rather than smaller topics to solve one particular app’s problems. I doubt I will be writing a tutorial on this, as it is not a general case issue. Thanks for the idea though!

  39. Jonh (1 comments) says:

    Hi Ray

    if does not want to wright the tutorial for this then

    please help. I need this. I tried but no succeed. please write simply the steps for this problem.

    Thanks

  40. elpuerco (16 comments) says:

    Hi Ray, great tutorial as usual and much better that some of the book stuff I have thanks ;-)

    Wondering if you can help with this?

    The code below is my standard way to setup a splash screen which thus far has worked a treat in the iPhone apps I have created, but there is a problem in the iPad setup.

    Although the Default-Landscape.jpg is initially displayed as desired it almost immediately flips to the splitviewcontroller with the leftviewcontroller blank and the rightviewcontroller displaying the landscape image in portrait?

    I have tried placing the splitviewcontroller into view at a later time with no joy.

    Any ideas?

    Thanks

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
    
        // Override point for customization after application launch.
    	[window addSubview:splitViewController.view];
        [window makeKeyAndVisible];
    
    	// create the splash view and add to window, Default.jpg will already be visible remember
    	UIImageView *i = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Default-Landscape.jpg"]];
    	[self setSplash:i];
    
    	[i release];
    	[splash setFrame:CGRectMake(0, 0, 1024, 768)];
    
    	// disable user interaction while splash is visible
    	[splash setUserInteractionEnabled:NO];
    
    	[window addSubview:splash];
    	[window bringSubviewToFront:splash];	
    
    	// create a timer to hide the splash screen
    	[NSTimer scheduledTimerWithTimeInterval: 2.0f
    									 target: self
    								   selector: @selector(hideSplash)
    								   userInfo: nil
    									repeats: NO];
    	return YES;
    }
    
    -(void) hideSplash {
    	// animation block begins to fade splash screen out
    	[UIView beginAnimations: nil context: nil];
    		[UIView setAnimationCurve: UIViewAnimationCurveEaseInOut];
    		[UIView setAnimationDuration: 0.75];
    		[splash setAlpha:0];
    	[UIView commitAnimations];
    
    	// create timer to remove and release splash from memory
    	[NSTimer scheduledTimerWithTimeInterval: 1.0f
    									 target: self
    								   selector: @selector(releaseSplash)
    								   userInfo: nil
    									repeats: NO];
    }
    
    -(void) releaseSplash {
    	// tidy up
    	[splash removeFromSuperview];
    	splash = nil;
    }
    
  41. Ray Wenderlich (874 comments) says:

    @elpuerco: At a quick glance I’m not sure, does it work as expected without your splash screen code?

  42. elpuerco (16 comments) says:

    Hi,

    yes your code works a treat!

    I am now trying to include the splash and find this problem.

    I wonder is it because it is a window based app rather than a navigation controller app?

  43. Ray Wenderlich (874 comments) says:

    @elpuerco: I don’t think that has anything to do with it – a navigation controller app is the same thing as a window based app, except it has some premade files to help get you started more quickly.

    The issue at hand is that the OS is always placing your UIView as-if you added it in portrait orientation, while you want to always be displayed in landscape. You could intercept the didRotate notifications and transform your view appropriately, but a perhaps easier way is to just use a UIViewController for your splash screen with a UIImageView inside, with autoresizing set up to resize the view to fit the whole screen, and make sure to return YES to all cases in shouldAutorotateToInterfaceOrientation.

    It looks like there’s plenty of tutorials out there on how to do this if you get stuck, try searching “uiviewcontroller splash screen” on Google.

  44. raj (3 comments) says:

    Ray,

    Thanks for the tutorial. Just FYI, I noticed a typo in the code listing for MathMonstersAppDelegate.m. The method code for didFinishLaunchingWithOptions has an incomplete chunk of code that starts “(NSDictionary *)launchOptions {” but never finishes.

    I’ll see your download code, but for others like me who prefer to type and learn instead of copy and paste, watch out for that.

  45. raj (3 comments) says:

    Whoooops. Sorry. I’m hallucinating. Ignore my previous comment.

  46. bijin (1 comments) says:

    Great tutorial. Perfectly understood all of that. But the way I am trying to use it in my app is different. Don’t know why, but its not working. Can u plz answer my doubts?

    1. Does the UISplitViewController work only on root level?

    2. I started with a normal view controller application and when I click a button on my main view, another UIViewController’s view is shown. I want to change this into a UISplitViewController.
    For example, like the detailed news in Reuters app.
    Can i do this? OR is there any alternate solution?

  47. Ray Wenderlich (874 comments) says:

    @bijin: I haven’t tried this myself, but I don’t see why it shouldn’t work.

    By the way, for anyone who may be interested, it’s recently come to my attention that Matt Gemmell has written an open source alternative to UISplitViewController that has some extra features and looks pretty useful:

    http://mattgemmell.com/2010/07/31/mgsplitviewcontroller-for-ipad

  48. Erdem Batsaikhan (1 comments) says:

    Hi, Ray. Thanks for the great tutorial.

    I’m an ipad newbie. I’m trying to develop split view based app and followed your tutorial. The application builds successfully, but the problem is when i run the code, most of the time it terminates on its own. And in the debugger console shows no sign of error except sometimes it shows gdb signal: EXC_BAD_ACCESS. I’ve populated left side table view from SQLite database. Could u please tell me where I might got mistake?

  49. Ray Wenderlich (874 comments) says:

    @Erdem: Here’s my general advice for how to diagnose a crash issue on iOS:

    1) Set a breakpoint in your code and step through until you narrow down where it’s crashing
    2) Set the NSZombieEnabled argument in your executable options, which sometimes helps narrow down the cause
    3) Run with Apple Instruments such as leaks to look for memory issues
    4) Tried and true “comment out code till it works” then backtrack from there :]

  50. jeff (15 comments) says:

    I did something similar to this with using your rateView code to allow me to rate an item in the details view. I saved the rating using core data and that all works. But once I rate one item in detailview my tableview in the rootview controller looses everything but the content that is currently on screen. I’m guessing I need to tell the tableview to update but how do I do that from the detailview controller?

  51. Ray Wenderlich (874 comments) says:

    @jeff: There are a tons of ways to do that. The simplest way is to give the detail view controller a pointer to the table view controller, and calling a method you write on it such as ‘refresh’ to start the update process.

    A cleaner way is to add a layer of abstraction – create a protocol on the table view controller such as “MyTableViewControllerDelegate”, and have a method such as “dataChanged” in the protocol. The table view controller will implement the protocol and set itself as the delegate of the detail view, and the detail view will notify its delegate when the data changes.

  52. shri (2 comments) says:

    Hi Ray …Can u Explain how can i add splitViewController into one of the TabBarcontroller tab. programmetically

  53. Siju (2 comments) says:

    Is it possible to change the size of the SplitView ‘s Left View Controller .I wana to make its size to Custom Size?

  54. Ray Wenderlich (874 comments) says:

    @shri: Here’s some info on that:

    http://stackoverflow.com/questions/2475139/uisplitviewcontroller-in-a-tabbar-uitabbarcontroller

    @Siju: You can’t change the left hand side size with UISplitViewController. However Matt Gemmell has written a split view controller replacement where you can do that:

    http://mattgemmell.com/2010/07/31/mgsplitviewcontroller-for-ipad

  55. Kevin (4 comments) says:

    Hi Ray,

    Thanks for a great tutorial; I worked through it and learned quite a bit (newbie iPad/iPhone developer). The only thing that stumps me is the very last part where you talk about using a UINavigationController on the right side instead of a UIToolbar. How exactly can I modify the project to use a NavigationController on the right side ? Is it just a matter of changing the class declaration of RightViewController to inherit from UINavigationController or is it more involved ?

    Thanks again.

  56. amzang (1 comments) says:

    hi, ray
    wow, this is what i want to look for.
    thanks a lot.

    i have one question.
    this question is very simple and stupid. but it’s very difficult to me.

    “Control-drag from “Math Monsters App Delegate” to the “Split View Controller”, “Left View Controller”, and “Right View Controller”, connecting each to the appropriate outlet.”

    i can’t connect ‘app delegate’ and ‘Left View Controller’ with outlet.

    how can i fix the problem?
    i want to see the your monsters.

    bye.

  57. Ray Wenderlich (874 comments) says:

    @Kevin: It’s a little more involved than that, but still pretty easy – since you can do it all from Interface Builder! In this tutorial, we set the right side of the split view in Interface Builder to “RightViewController”. If you want to use a Navigation Controller instead, you’d drag a Navigation Controller on top of that, and set the first view controller inside the Navigation Controller to the “RightViewController.”

    @amzang: When you control-drag from the app delegate to the left view controller, a popup should appear for “leftViewController”. If this does not appear, check that:

    * You’ve declared a leftViewController property in your MathMonstersAppDelegate and that it is marked as an IBOutlet.
    * You’ve saved your MathMonstersAppDelegate.h
    * You’ve set the Class of the View Controller to “LeftViewController” in the identity inspector

    Hope this helps!

  58. Jeff (15 comments) says:

    Ray –

    I’ve been chugging along through learning this iOS development stuff, and I have to say I keep on coming back to your tutorials as my go-to reference source. You seriously do a fantastic job of making some of this pretty dense stuff accessible, fun, and interesting to learn… and on top of it, you’re a computer guy who can write?? Truly a miracle.

    Anyhow, I’ve run into a weird issue in playing with the split view controller, and I was wondering if you had any input. I’m “building” the split view controller programmatically, with a navigation controller on the left, and a garden-variety view controller on the right, and then adding it to the split view controller as an array. The first screen is a login screen, then it sets the window’s subview be the Split View. …. and it works! Sort of.

    When the Split View first loads (and the simulator is in landscape), the right hand 1/3 of the detail view is cut-off (just black). BUT… if I rotate the bugger back and forth, all of a sudden everything’s there. Any ideas what the problem might be?

    (Sorry to tangle up the comments, but similar questions from others out there in the interweb have received no response…)

  59. Jeff (15 comments) says:

    Found it!

    For those of you interested, apparently it’s because the orientation is not detected if you load the splitViewController sometime after didFinishLaunchingWithOptions is called. Instead, if you’re going to load the splitViewController later, you have to determine the orientation and manually-ish set the frame of the splitViewController. See explanation and helpful code here: http://stackoverflow.com/questions/2686977/how-can-i-set-the-inital-orientation-for-a-uisplitviewcontroller-based-ipad-app

    Also, forgot to say thanks to you, Ray. Thanks!!!

    Jeff

  60. Binod Singh (3 comments) says:

    Yes its pretty cool exp.
    I have one problem when I see in portrait view its just display Hello world(rightViewControllerContent).
    may I know whats the problem or how to fix it.

    Many Thanks

  61. Ray Wenderlich (874 comments) says:

    @Jeff: Great glad you got it working, and thanks for posting the solution here so others may benefit!

    @Binod: Did you try comparing your code to the sample project?

  62. Binod Singh (3 comments) says:

    I have not completed the tut, I was stuck there only when it didnt show properly in portrait view. :) Luckily I change the view and everything was there. So heading up to next from that ;

    Thanx much for reply and its a great post.
    I have one more prob would like to here from you.
    let say I have a view with a button at start and when I click on that I would like to load the split view. Please let me know how can i do that I am new bee here………thanks in advance

  63. durai (1 comments) says:

    @Ray: Thankyou very much for this nice article on split view controller. Though I have some issues like icon image & name text are stretching and/or shrinking when changing the orientations, I will spend some more time to identify and fix the issues.

    I need a help. I want to do this splitview example without NIB i.e, programatically. Of-course, Jeff have done that. If you could provide another version of this project without using NIB, that would be really helpful or else guide me on how to achieve that?.

    @jeff: Can you share the splitview project that you have done programatically?.

  64. spoolup (9 comments) says:

    great tutorial as always.
    quickie… how could i add a tabbar on the left side?
    i’d like 5 tabs with the tableviews “feeding” in the detailview…

  65. Mahmud Ahsan (6 comments) says:

    Ray, this is an excellent tutorial. Very helpful to understand. Your tutorials are really easy to learn. Thank You.

  66. Ray Wenderlich (874 comments) says:

    @Binod: The easiest way to accomplish that would be to have a navigation controller as a subview of your window, with the root view controller of the navigation controller a view controller with your button. Then, when the button is clicked, you could push a split view controller onto the navigation stack.

    @durai: Here’s a post with some information on that which may help:

    http://stackoverflow.com/questions/2757489/uisplitviewcontroller-programmtically-without-nib-xib-thank-you

    @spoolup: Simply set the left side view to be a tab bar controller instead in Interface Builder. You should be able to just drag it over.

    @Mahmud: Thanks man! :]

  67. Tim Masterson (1 comments) says:

    Excellent tutorial. I really appreciate the time and effort you put in to this article. Great work! Thanks!

  68. Ray Wenderlich (874 comments) says:

    @Tim: Thanks, glad you liked it! :]

  69. Marius Groen (1 comments) says:

    Maybe a stupid question: but in the section Custom View Controller Placeholders I already got something that I don’t understand. It’s about adding a UIViewController subclass. First of all, what application is this about: still Interface Builder, or Xcode? Second, in both applications the file/new doesn’t give me anything like a UIViewController Subclass. What is the missing step for me?

    Running Xcode 3.2.3 using the iPad Simulator 3.2

    Thanks in advance!

  70. Ray Wenderlich (874 comments) says:

    @Marius: No not a stupid question at all! To create a subclass of UIViewController, you go to XCode, go to File\New File, choose iOS\Cocoa Touch Class\UIViewController subclass, make sure “Targeted for iPad” and “UITableViewController subclass” are checked, and click “Next”. Name the file “LeftViewController” and click “Finish”.

    My bet is that you may have been in a different category rather than iOS\Cocoa Touch class so didn’t see the option for the UIViewController subclass template. Hope this helps!

  71. Mako (4 comments) says:

    Hi Ray , thanks so much!
    the tutorial works excelent, but I noticed it is asociated with the main.xib,
    my app needs a split view in another xib, how can I show the splitview in a non main.xib?
    I have tried but I find some things that my new xib dont have, like the delegate,
    how can I do the split view in another xib? please?
    Im very noob to all of this, thank you!

  72. asef noor (2 comments) says:

    Hello Ray,
    What we normally do is, there is a fixed master controller and a detail controller. But is it possible that I can change master and detail controller at anytime programmatically?
    Because In my case left side of splitview controller totally changes and so is detail view.
    Thanks,

I'd love to hear your thoughts!