24
May
2010
 

Fixing the UISplitViewController Template

by Matt Long

The default implementation of the UISplitViewController based template in Xcode does not provide a navigation controller stack in the detail view. Instead it is just a regular old view with a navigation bar at the top. I suppose there are cases when you might want such an implementation, however, i think you would more commonly want there to be a navigation stack for cases when you wan to push new view controllers for your users to see. In this post i intend to demonstrate how to convert the default template to something more useable.

Gut The Default Template

It’s not quite as drastic as the work ‘gut’ might imply, but you want to remove some things from the default template nib. To get started create a Split View project, by selecting File | New Project… in Xcode. Then choose Split View-based Application and click Choose…. Name your application. I called mine ‘SpiffySplitView’.

Double-click DetailView.xib under the Resources group in your newly created project. This will open Interface Builder.

The first thing you will notice in the Interface Builder editor is that there is a UINavigationBar at the top. Select this bar and delete it. We will be switching the detail view of this split view project to use a UINavigationController which will add this nav bar back in for us but will provide the whole navigation stack along with it providing us the flexibility to push new view controllers in the detail view.

In the View inspector you will also want to make the Orientation Landscape, make the Top Bar a Navigation Bar, and set the Split View option to “Detail”.

Once you have made these changes, save the nib and switch back to Xcode. Now, double click the MainWindow.xib file in the Resources section of your Xcode project which will open it in Interface Builder.

Implementing a Navigation Controller In The Detail View

When MainWindow.xib opens in Interface Builder, take a look at the MainWindow.xib resources and switch to the detailed list view if you haven’t already so that the window looks something like this:

Notice that the Split View Controller contains two view controllers. The first one is a navigation controller, but the second one is our detail view controller. The first thing you want to do is change that. From the Library palette in Interface Builder, select the Navigation Controller object and drag and drop it on top of the detail view controller in the resources window.

The detail view controller will automatically switch to a navigation controller. Now you need to twirl down the new nav controller and change the root view controller type to a DetailViewController by switching to the Identity tab in the inspector.

Finally we are going to need to make our new navigation controller a delegate of the UISplitViewController in order to receive split view change notifications. Drag a connection from the Split View Controller to the Detail View Controller and select delegate.

Save your work. If you switch back to Xcode and run the application, you may not notice much of a difference in the look of the application, however, we can now make some changes in code that will give us a lot more flexibility.

Changes In Code

Edit DetailViewController.m in Xcode and change the -viewDidLoad method to set a title for the view controller. This will get passed up to the navigation controller which will display the title in the navigation bar.

- (void)viewDidLoad
{
  [super viewDidLoad];
  [self setTitle:@"Spiffy"];
}

This code will allow you to see the word ‘Spiffy’ in the navigation bar.

Next, you will notice that in portrait view, we have no button to tap to see our table view. It is only available in landscape view. To remedy this, you need to change a few lines of code–again in the DetailViewController you will modify the split view delegate methods. These methods simple add and remove the bar button item depending upon which mode we’re in, landscape or portrait or more specifically whether we are showing or hiding our list view controller.

- (void)splitViewController:(UISplitViewController*)svc 
     willHideViewController:(UIViewController *)aViewController 
          withBarButtonItem:(UIBarButtonItem*)barButtonItem 
       forPopoverController:(UIPopoverController*)pc
{  
  [barButtonItem setTitle:@"Root List"];
  [[self navigationItem] setLeftBarButtonItem:barButtonItem];
  [self setPopoverController:pc];
}


- (void)splitViewController:(UISplitViewController*)svc 
     willShowViewController:(UIViewController *)aViewController 
  invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
  [[self navigationItem] setLeftBarButtonItem:nil];
  [self setPopoverController:nil];
}

Now you can see our button show and hide correctly.

A problem you my run into now is that when you select one of the items in the list view, the detail view doesn’t actually update. This is due to the fact that we disconnected things when we changed our detail view controller in the split view to a navigation controller and we never re-connected it. To re-connect, open MainWindow.xib in Interface Builder again by double clicking it from the Resources group in Xcode. Then drag a connection from the root view controller to the Detail View Controller and select detailViewController.

The Point

So now what? The project is, in essence, now back to where it was when we started, right? Except now we can push new view controllers onto the stack. To demonstrate this, let’s add a button to the detail view controller that will be the trigger for pushing a new view controller and then we can create the new view controller to push.

In Xcode, create an action in the detail view controller that we will connect to. The code will look something like this.

- (IBAction)pushNewViewController:(id)sender;
{
  // Create and push new view controller here
  
}

In Xcode, double click the DetailView.xib to open it in Interface Builder. Then add a button to the view. Drag a connection from the new button to the action we just created.

Save your changes and go back into Xcode. Select File | New File…. In the ensuing dialog, choose UIViewController subclass in the Cocoa Touch Class group and make sure you have Targeted for iPad and With XIB for user interface selected. Click Next and then provide a name. I called my NewViewController.

Double-click the NewViewController.xib file to open it in Interface Builder. Change the settings on the view to do the following:

  • Set orientation to landscape
  • Set Top Bar to Navigation Bar
  • Set Split View to Detail

Save your changes and switch back to Xcode. In the -viewDidLoad of your NewViewController.m file, set the title again.

- (void)viewDidLoad
{
  [super viewDidLoad];
  [self setTitle:@"New View Controller"];
}

Now, in your DetailViewController.m file, #import the NewViewController.h file and then implement the push new view controller action.

- (IBAction)pushNewViewController:(id)sender;
{
  // Create and push new view controller here
  NewViewController *controller = [[NewViewController alloc] init];
  [[self navigationController] pushViewController:controller animated:YES];
  [controller release], controller = nil;
}

Now, when you tap the button on the detail view controller, a NewViewController will be pushed onto the navigation stack.

Conclusion

There are certainly legitimate reasons to place a navigation bar into your view manually. You might want to just use it as a place for tool bar items or other information. You may not need the navigation controller stack functionality, but providing yourself a way to push a new view controller on the navigation stack opens up additional possibilities while retaining the capabilities you have by using only a navigation bar. As the kids say, “it’s all good”. Until next time.

SpiffySplitView Xcode Project

Comments

mmalc says:

Unfortunately, I don’t think this “fixes” the template.

There are numerous possible configurations for a split view-based application. You might want to use a toolbar rather than a navigation bar in the second view controller (iWork style). You might want to use a navigation hierarchy in the first view controller (Mail style). You might want to replace the second view controller wholesale rather than in a navigation hierarchy (Settings style). None of these is addressed by this template. (Or, to be fair, by the currently shipping one.)

The most flexible template would use a third “content controller” object to serve as the split view controller’s delegate. It could support any configuration you like, but you’d still typically have to tailor the generated application to your particular requirements for any given situation, and it comes at the cost of increased complexity at the outset.

The current template is intended to cover what is hopefully overall a common enough pattern (a simple master-detail) that many will find it useful, and importantly be readily approachable and comprehensible. There is also sample code to illustrate one of the other patterns ( http://developer.apple.com/iphone/library/samplecode/MultipleDetailViews ). If you think, though, that a different configuration would be a more generally-useful starting point for the template, please send in feedback ( http://bugreport.apple.com/ ).

philippec says:

Pardon my newbishness, but to get the detailViewController to update properly, I had to set the RootViewController’s detailViewController outlet to the DetailViewController, not the application delegate’s.

Also, there’s a typo: “NewViewContrller.h”…

nwhitehead says:

Thanks for posting this. When I started using “Split view-based application” I was pretty confused about how you go about extending the functionality. This tutorial helped me get started on the right path.

Also, philippec is right about setting the RootViewController’s detailViewController outlet. Without that comment I would have been scratching my head for a while!

Matt Long says:

Thanks for the feedback. I’ve fixed the post.

-Matt

[…] Fixing the UISplitViewController Template […]

Leddo says:

Thanks for the post, it was very useful. One thing I have noticed, is that when in portrait mode, and have the new view pushed on the stack, the popover button has been replaced with the back navigation button.

What’s the best way to show both the popover button, and also the back button?

Chris

jassionly4u says:

How to implement SplitViewController on second level.

Actually what i want is to launch app with a login page and after login. I need SplitViewController.

Please help…

rennarda says:

There is a major problem with this implementation though: if you navigate into detail views of the right-hand navigation view controller whilst in portrait mode, you will not be able to navigate back again without rotating the device back to landscape mode ! I’m interested in solutions to this problem, but I think they are all going to be unconventional – the user expects (and convention dictates) the presence of a button in the top-left that will display a popup with the master view controller’s contents.

Matt Long says:

@rennarda

I’m not sure what you mean exactly. In the sample app, if you drill down into a new view controller while in portrait, you have to tap the back button that shows up in the nav controller to get back to the list of items. Of course, you could pass the bar button item along to each view controller you push onto the navigation stack and just load it into the nav bar in each -viewDidLoad. In that case, though, you also need to set each subsequent view controller as the delegate for the split view controller or you won’t receive the split view controller delegate callbacks and be able to respond properly.

-Matt

myoung says:

I’m very new to iPad programming so please pardon what may be a dumb question. I like the idea of pushing a new view controller on the stack. Could this concept be extended to push a new root view controller as well. This pattern or sequence would seem to be best for deeply layered data, in particular, the commercial real estate app I would like to implement.

I have a series of root view controllers each row of which would invoke different detail view controllers. I would be nice to have a nav button on the detail view controller that would take the user up the stack to the appropriate root view controller.

In simple form it looks like this:

First Root View Controller is a list of commercial properties
Each row of the First Root View Controller invokes a Property Detail View

(but the is more detail about each commercial property, so I need more root view controllers that can be invoked by touching a button in the nav bar of the Property Detail View)

Second Root View Controller contains rows of More Property Info such as a row for Tenants, a row for Revenues, a row for Expenses, etc.
Each row of the Second Root View Controller invokes an appropriate detail view: Tenants List Detail View, Revenues Detail View, Expenses Detail View, etc.

(in some cases, like in Tenants List Detail View, there would have to be an entirely new Third Root View Controller)

Okay, I’ve already made this more complicated and lengthy than I should have, but I hope that it makes enough sense for you to reply, or more simply I could send you the PDF file with my mockups to make the problem more clear. Thanks in advance.

Matt Long says:

@myoung

We are two people here who actually see your comments and could attempt an answer, but I would suggest you go ask your question at Stack Overflow (http://stackoverflow.com/questions/tagged/iphone). Just make sure you give your question the ‘iPhone’ tag and you will have many more people see your question who will be able to help you.

leejo says:

The issue with putting the detail view controller in a nav controller is that the detail view’s viewWillAppear, viewDidAppear, etc, functions are not called as they normally would be on a view controller. The ‘known’ ways around this
http://davidebenini.it/2009/01/03/viewwillappear-not-being-called-inside-a-uinavigationcontroller/
do not seem to work in the case of a SplitViewController. Any chance you can do a post around how to address the weakness of this architecture?

Maxim Korobov says:

The article consists all the steps? I tried several times to repeat the action in accordance with the article, but when I reached the test it detects a problem when call such code:
NewViewController *controller = [[NewViewController alloc] init];
UINavigationController *a = [self navigationController];
[a pushViewController:controller animated:YES];
[controller release], controller = nil;

UINavigationController * a is null after second line. Do you know why?
I checked the delegates and other links in IB a few times.

Maxim Korobov says:

I’m foolin around with PushButton code and found that it works with this second line:
UINavigationController * a = self.parentViewController;

Maxim Korobov says:

Oh, that’s OK!
I found that I use variable “navigationController” in detailview.h and .m.

Stan says:

Hi,
I use a RootViewController that switches a login view to a view that uses a SplitViewController.
When the device is set in Portrait mode, the Popover button is not displayed.
Everything works well in landscape mode.

When I use the view using the SplitViewController without the RootViewController, it works well.

Any idea, how can I fix that?

Thanks…

yasirmturk says:

Hi, this is a priceless article indeed, i have a specific problem…

I created a universal App in which my iPhone portion has tabbar controller as root and then navigationcontroller in side…

now, i want to use the same views for ipad version and i am thinking of it this way
use the splitview controller instead of tabbar controller when ipad, that is convert tabbar to splitview.. and navigation controller in detailview is working fine..

but the problem is with the splitview controller, i want that for each item in rootview of splitview controller there exist a view that should load in the detailview side.. stuck and confused, please help me out….

otherwise let me know of any useful article which expains how should we port out tabbar iphone apps to ipad paradigm
thanx in advance

spoolup says:

great tut!
got a question for ya….
i’ve added a few more views to my detail view which is what i was looking for, BUT (there’s always a but)
is there a way, to add a button on the last view, that once pressed will return to the initial view?
not load the initial view, but “rewind” so then there’s no need to press back multiple times in order to get the Root List button….
thanks!!!

gareth says:

Hi Matt,

Thanks for this tutorial – I’ve just spent the last hour trying to get a nav controller on the right hand side using the template and have had no luck. However, it seems that it is xCode 4 stopping me from doing this. I’m not sure why but I cannot drag a nav controller into the split view controller, nor can I delete either of the existing view controllers and replace them. It just doesn’t let you…..

So I’ve had to use your sample project to get this. Any ideas how to get around this – have you noticed the same in xCode 4?

Thanks again.

Best wishes,

Gareth.

Matt Long says:

@gareth,

Unfortunately, I am still using Xcode 3 for many reasons. This will be one more I add to the list. Once they force us to upgrade, I’ll probably spend more time figuring it out, but for now I am still on 3.2.6 to maintain sanity. I love the potential of Xcode 4, but I don’t find it consistent enough to get real work done yet.

-Matt

[…] I have found another blog post which addresses this particular situation on Cocoa is my girlfriend, see also the comment section of that entry. The post is a little basic and does not cover the […]