Fri, 29 Aug 2008 08:37:41 GMT

Organising view controllers

Understanding how individual view controllers work is one thing. Organising them is another. Applications typically deal with multiple view controllers. Views can change dynamically. Hence view controllers need dynamic capabilities.

This article presents an idea for organising view controllers within an application. Design goals include: flexibility, simplicity, convention over configuration.

View paths

The design idea centres around the concept of a view path. Paths are a ubiquitous paradigm. You find them in file systems, graphs, in Cocoa’s key-value coding, in X-Path querying, the list goes on. Graph is the underlying structure (directed acyclic, to be technical). Paths express a route between two points in the graph, from the root to some leaf node for example.

View hierarchies form such a nested model, so applying paths naturally expresses the intrinsic nested construct. Simple notion then: View hierarchy equals graph, use path to navigate it. Stay with me. You’ll see where I’m going.

Meta-control

Meta is a funny word. “It’s from the Greek,” in a Greek accent! Here it refers to things beyond or above, a higher order. View controllers control their views, yes. But what controllers the controller? Answer: some form a meta-control, controlling the control. It makes the head spin. Yet necessary. Every application, large and small, requires some form of overarching control, or, in object terms, some framework on which to overlay the flesh of behaviour.

I’m being a little cryptic. Are you following me?

So. Let’s assume: one view, one view controller. “View” here refers to a view sub-hierarchy, one that you load from a nib; so it might comprise many sub-views but has one container view. Our application wants to manage a hierarchy of such controller-view-nib triplets.

Tempting to directly overlay a tree structure by sub-classing a new view controller class, adding an array of view sub-controllers. A simple approach, but limiting. It forces the view controllers into a strict hierarchy. This does not fit the requirement for flexibility. It inserts the meta-controlling layer into the low-order control layer. A flexible solution really needs a second-order of controlling existing above the first rather than intermingled.

Show me the money

So far, we’ve been living in the abstract. Reality doesn’t live there. Let’s get practical. Take an example.

Start an experiment. Take Apple’s ViewController sample code (screen shot below) as the reference point. Let’s try to improve it. Can we reduce the number of lines? Make it easier to understand, maintain, expand? Seems like a tall order. Too tall?

ViewController sample code

As you see, there’s a large custom view in the upper portion of the window. Clicking the pop-up button presents you with four choices. The choice switches the custom view. Four different sets of view controller, nib and sub-view switch in and out accordingly.

Lies, lies and statistics

Improvement is hard to measure. Better is a subjective term. My better and your better might not be the same better. So for the experiment, we will add some objectivity. Start by measuring the existing ViewController project.

Basic software metric: lines of source code (SLOC).

The cloc tool reports:

-------------------------------------------------------------------------------
Language          files     blank   comment      code    scale   3rd gen. equiv
-------------------------------------------------------------------------------
Objective C          11       165       502       229 x   2.96 =         677.84
-------------------------------------------------------------------------------
SUM:                 11       165       502       229 x   2.96 =         677.84
-------------------------------------------------------------------------------

229 lines of code in total. I’m running cloc from the ViewController project directory and passing arguments ”--force-lang="Objective C",h .” meaning that source files with h classify as Objective C; dot refers to the current working directory, of course.

Adding the --by-file option produces some interesting statistics too.

-------------------------------------------------------------------------------
File                        blank   comment      code    scale   3rd gen. equiv
-------------------------------------------------------------------------------
./MyWindowController.m         30        64        92 x   2.96 =         272.32
./CustomImageViewController.m  17        58        37 x   2.96 =         109.52
./AppDelegate.m                17        50        24 x   2.96 =          71.04
./CustomVideoViewController.m  15        46        18 x   2.96 =          53.28
./CustomTableViewController.m  13        44        17 x   2.96 =          50.32
./MyWindowController.h         13        40         9 x   2.96 =          26.64
./CustomImageViewController.h  13        40         8 x   2.96 =          23.68
./AppDelegate.h                12        40         7 x   2.96 =          20.72
./CustomVideoViewController.h  12        40         6 x   2.96 =          17.76
./CustomTableViewController.h  12        40         6 x   2.96 =          17.76
./main.m                       11        40         5 x   2.96 =          14.80
-------------------------------------------------------------------------------
SUM:                          165       502       229 x   2.96 =         677.84
-------------------------------------------------------------------------------

Here we see that most of the complexity (in lines-of-code terms anyway) resides in the window controller implementation: 92 lines of code, top of the list. That’s likely where one might expect greater complexity. But on the other hand, shouldn’t the view controller paradigm mitigate window controller complexity to some degree. We shall see.

Cash register

Let’s also use David A. Wheeler’s SLOCCount tool. It’s easy to install using MacPorts. Just enter sudo port install sloccount in Terminal. After installing, entering ”sloccount .” in the ViewController project directory gives:

Total Physical Source Lines of Code (SLOC)                = 246
Development Effort Estimate, Person-Years (Person-Months) = 0.05 (0.55)
 (Basic COCOMO model, Person-Months = 2.4 * (KSLOC**1.05))
Schedule Estimate, Years (Months)                         = 0.17 (1.99)
 (Basic COCOMO model, Months = 2.5 * (person-months**0.38))
Estimated Average Number of Developers (Effort/Schedule)  = 0.28
Total Estimated Cost to Develop                           = $ 6,196
 (average salary = $56,286/year, overhead = 2.40).

Phew! 246 lines costing 6,000 dollars. Interesting that the number of lines do not agree. SLOCCount counts some of the comment lines. Big question is: can our experiment save bucks?

Let the games begin

Start with a new project. Use the Cocoa Application template: not document based, without Core Data. I’ll call it View Controller Mark II! That sounds Spitfire-esque. Excuse the appeal to glamour and (apparent) adventure. Just hope it doesn’t crash!

The new empty project provides just 5 lines of code in main.m. We have a blank MainMenu.xib too; just the main menu and main window. No application delegate, no window controller. Yet.

Rather than re-create the nibs, the experiment will just copy them from Apple’s sample code verbatim. That way, the comparison will be like-for-like. Only Objective-C code will change. Nothing else. So, delete MainMenu.xib; MainMenu.nib will replace it. Add to Resources: four Custom View nibs, MainMenu and TestWindow nibs. Add with Copy enabled. The custom video view uses QTMovieView so the project needs QTKit framework adding too. Add Quartz while we’re at it; the camera view uses Quartz’s QCView. The Lake Don Pedro JPEG image and QuickTime movie needs adding, finally.

Build already? No. It cannot run yet. Missing AppDelegate for one thing. After launch, the application delegate needs to create a new window controller (a custom one) and initialise it using the TestWindow nib.

So, we sub-class NSWindowController as MyWindowController. Add the necessary instance variables, myTargetView and viewController; add the (IBAction)viewChoicePopupAction:(id)sender instance method. That makes the project build and run, though do nothing remarkable of course.

By the time building and running reaches an error-free level of success, application delegate and window controller sources appear as follows.

Barebones application delegate

#import <Cocoa/Cocoa.h>

@class MyWindowController;

@interface AppDelegate : NSObject
{
    MyWindowController *myWindowController;
}
@end

It’s implementation:

#import "AppDelegate.h"
#import "MyWindowController.h"

@implementation AppDelegate

- (IBAction)newDocument:(id)sender
{
    // The MainMenu nib's main menu sends this action when you select File New,
    // or Command+N. The menu item target is the first responder. This falls
    // through the Responder Chain until it finally hits AppDelegate. Answer by
    // constructing the window controller, if not already existing, and show it.
    if (myWindowController == nil)
        myWindowController = [[MyWindowController alloc] initWithWindowNibName:@"TestWindow"];
    [myWindowController showWindow:self];
}

- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
    // By virtue of AppDelegate being the application's delegate, it
    // automatically receives application notifications. How kind. Respond by
    // sending the -newDocument: action, just as if the user pressed Command+N.
    [self newDocument:self];
}

@end

Barebones window controller

#import <Cocoa/Cocoa.h>

@interface MyWindowController : NSWindowController
{
    IBOutlet NSView *myTargetView;
    NSViewController *viewController;
        // TestWindow.nib contains bindings to this instance variable's "title"
        // and "representedObject" property. Rather than naming it
        // myCustomViewController, as does the Apple sample code, name the
        // instance variable by its KVC key. Doing so saves an accessor.
}

- (IBAction)viewChoicePopupAction:(id)sender;

@end

And its implementation:

#import "MyWindowController.h"

@implementation MyWindowController

- (IBAction)viewChoicePopupAction:(id)sender
{
    // TestWindow nib connects action -viewChoicePopupAction: to the window
    // controller. MyWindowController is the nib's owner. The
    // Image-Table-Video-Camera pop-up button sends this action to the nib
    // owner. The window controller responds by switching view and view
    // controller.
}

@end

At this point, then, the project has 40 lines of source code. Where to go from here?

Switching view controllers

That’s the main point. When the user selects one of the four choices available in the pop-up button (Image, Table, Video or iSight Camera) the custom view responds by switching to the corresponding view alternative. As regards complexity, meaning breadth and depth of view controller nesting, the requirement is very trivial. Breadth of four, depth of one. It’s hardly worth having any meta-control. But you have to start somewhere. Here’s as good a place as any.

To be continued.


Trackbacks

Use the following link to trackback from your own site:
http://blog.pioneeringsoftware.co.uk/trackbacks?article_id=11

Comments

Leave a comment

(never displayed)

Markdown enabled