Switching Views with a UISegmentedControl - Revisited

posted by crafterm, 27 June 2010

Recently I investigated switching between multiple different views using a UISegmentedControl, similar to iCal or the AppStore application.


The best approach I could find to work at the time was to use a managing view controller, that enclosed an array of sub view controllers. The UISegmentedControl would then switch between these sub views by adding/removing the selected segment’s view as a subview of the managing controller’s view.

While this worked and satisfied one of my main requirements of keeping the logic between view controllers separate, there were a few things that frustrated me about the approach:

  • The enclosing view controller that managed the sub views was in effect a ‘container’ view controller, and I distinctly remembered Evan Doll’s WWDC 2009 presentation where cautioned against building any style of container view controllers.

  • To push onto the navigation stack from within a sub view, each sub view needed to have a reference back to the managing container view controller, either via a property and/or custom constructor, as the sub views weren’t created within the navigation hierarchy and hence had a nil navigationController property.

  • Since the managing view controller enclosed a series of sub view controllers, some in the view hierarchy and some not, view life cycle messages needed to be forwarded to the sub view controllers to be good UIKit citizens.

While attending WWDC 2010 just a few weeks ago, I managed to talk to several UIKit engineers and together we managed to find a much better approach to solving this UI paradigm without requiring a container view controller.

New Shiny

The new approach is to utilize a UINavigationController rather than a managing view controller. However, rather than use the more common navigation controller methods pushViewController:animated: and popViewControllerAnimated:, we will manipulate the navigation view hierarchy directly by modifying the viewControllers property using the setViewControllers:animated: method.

The technique essentially works as follows. Any index change in the designated UISegmentedControl calls upon a method in a custom NSObject descendant controller object of ours. This controller accesses the selected view controller appropriate for the selected segment and installs it into the navigation controller stack directly using the setViewControllers:animated: method.

Finally, since the navigation view hierarchy has been modified directly, we then re-install the segmented control as the title view on the incoming view controller so that further segment changes can be made.

I’ve reimplemented the previous example application I built using the managing view controller with this new pattern, let’s step through it to demonstrate how it all works.

Since we’ll be using standard UINavigationController and UISegmentedControl objects in this application, I’ll skip straight to our custom controller object that accepts a message indicating a change in selected segment index, and does the navigation controller magic.


@interface SegmentsController : NSObject {
    NSArray                * viewControllers;
    UINavigationController * navigationController;

@property (nonatomic, retain, readonly) NSArray                * viewControllers;
@property (nonatomic, retain, readonly) UINavigationController * navigationController;

- (id)initWithNavigationController:(UINavigationController *)aNavigationController
                   viewControllers:(NSArray *)viewControllers;

- (void)indexDidChangeForSegmentedControl:(UISegmentedControl *)aSegmentedControl;


Here we define the SegmentsController interface to be an NSObject descendant, with storage for the view controllers appropriate for each segment, and a reference to our navigation controller.

A custom constructor accepts the view and navigation controller, and the indexDidChangeForSegmentedControl: is our method that can be invoked when a given segmented control index changes.


@interface SegmentsController ()
@property (nonatomic, retain, readwrite) NSArray                * viewControllers;
@property (nonatomic, retain, readwrite) UINavigationController * navigationController;

@implementation SegmentsController

@synthesize viewControllers, navigationController;

- (id)initWithNavigationController:(UINavigationController *)aNavigationController
                   viewControllers:(NSArray *)theViewControllers {
    if (self = [super init]) {
        self.navigationController = aNavigationController;
        self.viewControllers = theViewControllers;
    return self;

- (void)indexDidChangeForSegmentedControl:(UISegmentedControl *)aSegmentedControl {
    NSUInteger index = aSegmentedControl.selectedSegmentIndex;
    UIViewController * incomingViewController = [self.viewControllers objectAtIndex:index];

    NSArray * theViewControllers = [NSArray arrayWithObject:incomingViewController];
    [self.navigationController setViewControllers:theViewControllers animated:NO];

    incomingViewController.navigationItem.titleView = aSegmentedControl;

- (void)dealloc {
    self.viewControllers = nil;
    self.navigationController = nil;
    [super dealloc];


In the anonymous category we redefine our properties read/write so we can mutate them from within the implementation only, and define our constructor to store references to our given view and navigation controllers.

The meat of the work is done next. Our segmented control will be appropriately configured to call upon indexDidChangeForSegmentedControl: when it’s segment index changes. When this occurs, we retrieve the new index from the segmented control, and the relevant view controller to install (a more complex example could instantiate/cache view controllers to conserve memory), and assign it to the navigation controller via the setViewControllers:animated: message.

Once installed, we then apply the segmented control to the title view of the view controller we just installed into the navigation controller, and are done.

Finally, we implement appropriate memory management methods to de-allocate resources when being released.

Application Delegate Implementation

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    NSArray * viewControllers = [self segmentViewControllers];

    UINavigationController * navigationController = [[[UINavigationController alloc] init] autorelease];
    self.segmentsController = [[SegmentsController alloc] initWithNavigationController:navigationController viewControllers:viewControllers];

    self.segmentedControl = [[UISegmentedControl alloc] initWithItems:[viewControllers arrayByPerformingSelector:@selector(title)]];
    self.segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;

    [self.segmentedControl addTarget:self.segmentsController

    [self firstUserExperience];

    [window addSubview:navigationController.view];
    [window makeKeyAndVisible];

    return YES;

In this example, I’ve instantiated and configured the segmented control, navigation and segment controllers from within the application delegate, but this could equally be done at a lower level depending on your application.

In particular, the segmented control has its target/action pair set up to point to our indexDidChangeForSegmentedControl: method described above.

The segmentViewControllers methods returns the view controllers relating to each segment (the title property of each view controller is used as the segment’s title via the NSArray arrayByPerformingSelector: extension), and firstUserExperience kicks everything off by selecting and installing the first segment.

- (NSArray *)segmentViewControllers {
    UIViewController * italy     = [[ItalyViewController alloc] initWithNibName:@"ItalyViewController" bundle:nil];
    UIViewController * australia = [[AustraliaViewController alloc] initWithStyle:UITableViewStyleGrouped];

    NSArray * viewControllers = [NSArray arrayWithObjects:italy, australia, nil];
    [australia release]; [italy release];

    return viewControllers;

- (void)firstUserExperience {
    self.segmentedControl.selectedSegmentIndex = 0;
    [self.segmentsController indexDidChangeForSegmentedControl:self.segmentedControl];


Using a UINavigationController based approach has several distinct advantages that I quite like – it doesn’t require a container controller, and hence no custom code for handling memory, rotation, or view life cycle events.

Code-wise it’s much smaller than the previous implementation, and will be a lot easier to maintain. Segment view controllers can push directly onto the navigation controller stack since they’re set within the navigation hierarchy, and no special management of parent view controllers is required.

The full XCode project of the example above is available if you’d like to examine it further. Enjoy.

Multiple Views with a UISplitViewController

posted by crafterm, 14 June 2010

The iPad has ushered in a suite of new awesome user interface paradigms with it’s large screen and enhanced performance.

In this article, I’ll step through using one of the new user interface elements in iPhone SDK 3.2+, the UISplitViewController, in particular managing multiple views with their own navigation controller stack, handling all orientations.


Since I’m an avid cyclist and the Giro d'Italia just finished, in this article’s we’ll step through an example application that lists the name and distance of each stage.

When tapped, we’ll show a photo from Melbourne’s most popular cycling blog, Cycling Tips, and allow the user to tap through to the Cycling Tips article summarizing the stage (full credits to Cycling Tips, they’ve done an awesome job at covering the Giro this year).

I’ll use landscape orientation terminology to describe the visual aspects of the split view controller in this article, but please assume normal split view controller semantics with the left hand side of the split view appearing in a popup view when in portrait mode. We’ll discuss the code required to get this to work as well.


Since we want to focus on the UISplitViewController, I’ll browse over the data model for this particular application since it is quite small, all the code is available online, and it’s just a single Core Data Stage entity containing attributes relevant to each stage.

I definitely recommend utilizing Core Data for your models where possible, I find it fast and easy to use, and its great for prototyping new ideas.

For data persistence it’s awesome, you can switch between using in-memory storage and SQLlite during the development making it easy to always have fresh content while you’re making changes.

Table view list of Stages

On to the controller code, along the left hand side of the split view controller, we’ll show a table view, with each stage of the Giro being listed in a separate cell.

StageTableViewController Interface

@interface StageTableViewController : UITableViewController {
    NSFetchedResultsController * stageResultsController;

    NSMutableDictionary        * stageViewControllers;

    UIPopoverController        * popoverController;
    UIBarButtonItem            * popoverButtonItem;

@property (nonatomic, retain) NSFetchedResultsController * stageResultsController;

@property (nonatomic, retain) NSMutableDictionary        * stageViewControllers;

@property (nonatomic, retain) UIPopoverController        * popoverController;
@property (nonatomic, retain) UIBarButtonItem            * popoverButtonItem;

- (void)autoselectFirstStage;


@protocol PopupManagingViewController
- (void)showPopoverButtonItem:(UIBarButtonItem *)barButtonItem;
- (void)invalidatePopoverButtonItem:(UIBarButtonItem *)barButtonItem;

The stageResultsController property provides access to our Core Data backed model.

In addition to this we define storage for the known view controllers we present on the right hand side of the split view controller. This effectively caches view controllers we’ve already shown, so if the user taps back to them, they show instantly rather than re-fetch data.

We also define an autoselectFirstStage method that the application delegate can use to automatically select and show the first stage upon startup.

When orientated to portrait, the left hand side of the split view controller disappears and becomes available via a popup. The popoverController property is responsible for showing the view controller inside a popover view when a user taps the popoverButtonItem button.

We also define a protocol our detail view controller can implement to install and remove the button allowing access to the popover when we switch between detail views in portrait mode.

StageTableViewController Implementation

@implementation StageTableViewController

@synthesize stageResultsController, stageViewControllers, popoverButtonItem, popoverController;

- (void)viewDidLoad {
    [super viewDidLoad];

    self.title = @"Stages";
    self.stageResultsController = [[ContentController sharedInstance] stageResultsController];

    NSUInteger stageCount = [self tableView:self.tableView numberOfRowsInSection:0];
    self.contentSizeForViewInPopover = CGSizeMake(320.0, self.tableView.rowHeight * stageCount);
    self.clearsSelectionOnViewWillAppear = NO;

    self.stageViewControllers = [NSMutableDictionary dictionary];

- (void)autoselectFirstStage {
    NSIndexPath * startPath = [NSIndexPath indexPathForRow:0 inSection:0];
    [self.tableView selectRowAtIndexPath:startPath animated:NO scrollPosition:UITableViewScrollPositionNone];
    [self.tableView.delegate tableView:self.tableView didSelectRowAtIndexPath:startPath];

In viewDidLoad, we configure the view controller and create references to our model layer’s NSFetchedResultsController. Our autoselectFirstStage method ensures the table view and delegate are informed of our selection.

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [[self.stageResultsController sections] count];

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    id <NSFetchedResultsSectionInfo> sectionInfo = [[self.stageResultsController sections] objectAtIndex:section];
    return [sectionInfo numberOfObjects];

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *CellIdentifier = @"StageCell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];

    Stage * stage = [self.stageResultsController objectAtIndexPath:indexPath];
    cell.textLabel.text = stage.name;
    cell.detailTextLabel.text = stage.formattedDistance;

    return cell;

We then implement the required UITableViewDataSource protocol methods to return our content to fill the table view when requested.

For some extra shine, we would also want to implement a custom UITableViewCell as well to nicely show all the stage content. I’ll follow this up in a subsequent article.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    Stage * stage = [self.stageResultsController objectAtIndexPath:indexPath];

    // invalidate the current popover button if one is being used
    UINavigationController * currentStageNavigationController = self.splitViewController.rightSideViewController;
    UIViewController<PopupManagingViewController> * currentViewController = [currentStageNavigationController rootViewController];
    [currentViewController invalidatePopoverButtonItem:self.popoverButtonItem];

    // install the new viewcontrollers array (LHS: stage view controller, RHS: incoming detail navigation controller)
    UINavigationController * stageNavigationController = [self.stageViewControllers valueForKey:stage.name];

    if (!stageNavigationController) {
        StageDetailViewController * detailViewController = [[StageDetailViewController alloc] initWithStage:stage];
        stageNavigationController = [[UINavigationController alloc] initWithRootViewController:detailViewController];
        [detailViewController release];
        [self.stageViewControllers setValue:stageNavigationController forKey:stage.name];
        [stageNavigationController release];

    self.splitViewController.viewControllers = [NSArray arrayWithObjects:self.navigationController, stageNavigationController, nil];

    // dismiss the popover if present, and add the popover button to the incoming detail view controller
    [self.popoverController dismissPopoverAnimated:YES];

    if (self.popoverButtonItem) {
        UIViewController<PopupManagingViewController> * viewController = [stageNavigationController rootViewController];
        [viewController showPopoverButtonItem:self.popoverButtonItem];

The only UITableViewDelegate method we’ll implement is tableView:didSelectRowAtIndexPath: for when a user taps a particular stage in the table view.

In tableView:didSelectRowAtIndexPath: we perform a few tasks. Essentially, we want to create a StageDetailViewController instance within a navigation controller, and cache it in case the user selects this table row again.

Once we’ve created (or retrieved a cached version of) our StageDetailViewController we reassign the viewControllers property on the split controller, specifying the new left/right hand side view controllers to be shown.

In this case, we never want the left hand side to change from being the table view of all stages, so position 0 in the returned array is the navigationController reference of the StageTableViewController (be careful to remember this in your apps as you’ll get some funny behaviour if you return the view controller directly).

Position 1 of the array includes the newly created stage detail view controller, inset within a UINavigationController instance.

Once the ‘viewController’ property has been assigned, the split view updates instantly to show our selected view controller in the right hand side of the split view.

In addition to this, if the user selected an item in the table while in portrait mode from a popover, we dismiss the popover view, and install a new popover button in the detail view that the user can tap if they wish to select another item.

Finally as good memory management citizens we also implement viewDidUnload and didReceiveMemoryWarning to release allocated and cached resources respectively.

- (void)viewDidUnload {
    [super viewDidUnload];
    self.stageViewControllers   = nil;
    self.popoverButtonItem      = nil;
    self.popoverController      = nil;
    self.stageResultsController = nil;

- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
  [self.stageViewControllers removeAllObjects];

The full XCode project also includes the UISplitViewController delegate code to install/remove the popover button when the user changes orientation.

Now that we’ve implemented the left hand side of the split view, let’s move onto the right hand side detail view.

StageDetailViewController interface

The right hand side of the split view controller will initially show a picture from the selected Giro racing stage. We’ll also place this image inside a scroll view so the user can pan and zoom in/out, etc.

We will also install a button within the navigation bar so the user can drill down to the blog post summarizing that stage.

Here’s the interface:

@interface StageDetailViewController : UIViewController <ImageLoaderDelegate, UIScrollViewDelegate, PopupManagingViewController> {
    Stage                   * stage;

    UIScrollView            * scrollView;
    UIActivityIndicatorView * activityView;

    UIImageView             * stageImageView;
    ImageLoader             * imageLoader;

@property (nonatomic, retain) Stage                            * stage;

@property (nonatomic, retain) IBOutlet UIScrollView            * scrollView;
@property (nonatomic, retain) IBOutlet UIActivityIndicatorView * activityView;

@property (nonatomic, retain) IBOutlet UIImageView             * stageImageView;
@property (nonatomic, retain) ImageLoader                      * imageLoader;

- (id)initWithStage:(Stage *)stage;


The ImageLoader property is a helper class I’ve written for asynchronously downloading images over the network, utilizing the excellent ASIHttpRequest library.

StageDetailViewController implementation

@interface StageDetailViewController ()
- (void)didSelectReadArticle:(id)sender;

@implementation StageDetailViewController

@synthesize stage, stageImageView, imageLoader, activityView, scrollView;

- (id)initWithStage:(Stage *)aStage {
    if (self = [super initWithNibName:@"StageDetailViewController" bundle:nil]) {
        self.stage = aStage;
    return self;

- (void)viewDidLoad {
    [super viewDidLoad];

    self.title = [NSString stringWithFormat:@"Giro d'Italia: %@", self.stage.name];

    self.view.backgroundColor = [UIColor scrollViewTexturedBackgroundColor];

    self.scrollView.maximumZoomScale = 5.0f;
    self.scrollView.minimumZoomScale = 1.0f;
    self.scrollView.delegate = self;

    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Read Article" style:UIBarButtonItemStylePlain target:self action:@selector(didSelectReadArticle:)];
    self.navigationItem.backBarButtonItem  = [[UIBarButtonItem alloc] initWithTitle:[NSString stringWithFormat:@"Stage %@", self.stage.index] style:UIBarButtonItemStylePlain target:nil action:nil];

    self.imageLoader = [[ImageLoader alloc] initWithURL:self.stage.imageURL];
    self.imageLoader.delegate = self;
    [self.imageLoader start];

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
    return self.stageImageView;

In the anonymous category we define a didSelectReadArticle: method to be invoked when the taps the button to drill down to read the full blog post summarizing the race stage.

In viewDidLoad, we configure the view’s title and background colour (scrollViewTexturedBackgroundColor looks just awesome on the iPad), the scroll view’s scale and delegate properties, install a ‘Read Article’ button on the right hand side of the navigation bar, and redefine the back button to be the stage number rather than the full stage name.

Finally, we create an ImageLoader instance and start retrieving an image of the specified stage.

Should a user tap the ‘Read Article’ button the following method is called:

- (void)didSelectReadArticle:(id)sender {
    StageArticleViewController * articleViewController = [[StageArticleViewController alloc] initWithStage:self.stage];
    [self.navigationController pushViewController:articleViewController animated:YES];
    [articleViewController release];

This will push an instance of StageArticleViewController onto the navigation stack, which includes a web view configured to load the race stage summary.

Once again we ensure all allocated resources are released when the view is deconstructed.

- (void)viewDidUnload {
    [super viewDidUnload];
    self.stageImageView       = nil;
    self.imageLoader.delegate = nil;
    self.imageLoader          = nil;
    self.scrollView           = nil;

- (void)dealloc {
    self.stage = nil;
    [super dealloc];

Here we split deallocation of the view related items from the stage, since the stage was specified in the constructor whereas stageImageView, scrollView and the imageLoader objects were created in viewDidLoad.


As we can see, UISplitViewController is really useful for master-detail style interfaces and it’s configured similarly to a UITabBarController by assigning it’s viewControllers property.

I’ve skipped over a few parts of the example app to keep the post length under control, such as implementation of the StageArticleViewController, orientation permissions on all our view controllers, the image loader callbacks to add the image to the scroll view upon completion of download, etc. All of this is availablae in the full XCode project however, please feel free to peruse the code online.

Australasian Surf Business Magazine iPhone App

posted by crafterm, 30 May 2010

Its great to see an application you’ve been working on recently be accepted and made available on the Apple iTunes store.

Just last week, we released the Australasian Surf Business Magazine (asbmag) iPhone application!

asbmag’s application, targeted at those working within the surf industry, allows you to keep up to date with the latest industry news and job content on the iPhone, normally available on their website.

The design of the application has been to focus on the user’s experience while reading content on the phone, and integrate with the native capabilities of the device.

Content is locally in Core Data so that the application works well offline. When online, it efficiently retrieves the latest news and job articles from www.asbmag.com via an atom feed, and appropriately updates the device.

If you’re keen to keep up with what’s happening in the Surf Industry, download the app for free and enjoy!