NSViewController, the New C in MVC - Pt. 3 of 3

The last two installments in this series focused on the challenge of integrating NSViewController into the controller layer of the current Cocoa MVC application architecture. This is an important step to take, especially when developing a Cocoa application with a single window interface design. In a single window design, users will access most of the application’s features through one window rather than opening and closing several smaller windows as they are needed. Since this one window will never close during a session, the multiple view controllers in the window will fill the role previously held by multiple window controllers in creating a dynamic Cocoa application.

The application’s view controllers will perform tasks like loading/unloading the content of nib files and their controllers as needed and validating/invalidating menu items as features are added or removed from the window. Plugging NSViewController into the existing controller architecture is a simple way to provide it with the support it needs to fulfill these standard duties for your application.

However important these window controller-type tasks are, keep in mind that they are not the principle duty of a view controller. Unlike NSWindowController, NSViewController has a close relationship to the view layer of the Cocoa MVC design. Each view controller manages a view in a view hierarchy. A system of view controllers, like the system Jonathan Dann and I described in the previous installment, manages the entire view hierarchy of a window. One of the most mission-critical jobs of a system of view controllers is to build and maintain the structure and layout of a complex view hierarchy in a dynamic environment.

lightroom.jpg

Above: Adobe Photoshop Lightroom is an example of a feature rich application with a single window interface design.

Quick Quiz: what is a view hierarchy?

Quick answer from the docs (emphasis is mine):

In addition to being responsible for drawing and handling user events, a view instance can act as a container, enclosing other view instances. Those views are linked together creating a view hierarchy. Unlike a class hierarchy, which defines the lineage of a class, the view hierarchy defines the layout of views relative to other views.

V + C, the dynamic layout duo

There is a key difference between developing a Cocoa application that spreads its interface over several windows and one that uses a single window interface. In a single window design, the view hierarchy of the application’s main window is much more complex and, most importantly, dynamic than Cocoa windows normally are. In fact, the view hierarchy is a prominent character in this type of development. Managing its structure and layout throughout the application’s runtime is a significant design problem that should be addressed in a systematic way by the software’s MVC architecture – in both the controller and view layers.

NSViewController addresses this issue by providing support for building and managing the structure of a view hierarchy through its “view” instance variable. Once you organize your view controllers into a coherent system, you have a convenient mechanism for accessing and adjusting the hierarchy from its significant branches. But the problem is only half solved since changes in the view hierarchy are reflected by changes in the layout, and unfortunately, the issue of layout in a dynamic view environment remains to be addressed by Cocoa.

Building and maintaining a complex layout, like the one pictured above, requires cooperation from both the views and the controllers of a window. The controllers are the dictators of the layout. They tell the views contained in their domain of the hierarchy where to go and how to behave. Unless the circumstances are very special, it’s up to the view hierarchy itself to do the calculations necessary to maintain the integrity of that layout as the forces within it change over time. If either member of the layout duo fails to perform their duty, the interface will simply break.

The problem with V

In Cocoa, NSView fails to perform its duty in this kind of system because it isn’t equipped with the right tools. NSView has something called an “autoresizing mask”, which is meant to define the layout behavior of a view when its superview’s layout changes. However, Autoresizing was never designed to function in a view hierarchy with a dynamic structure. It is simply not the right tool for this job. This is evidenced by one simple thing: Autoresizing requires that a view is already laid out within its superview before you are supposed to set the desired resizing behavior.

This is well and good when you’re creating your entire layout in Interface Builder, where all the views are present and accounted for, but it’s simply not a practical limitation to make in a system of views and view controllers where elements of the layout will be added and removed during the application’s runtime.

It means that a view controller must have knowledge of it’s view’s superview and its frame before it can technically set a layout, but view controllers don’t necessarily have that information. For instance, when a view controller is initialized, it will create a view that will eventually be added to the view hierarchy. At that moment, however, it doesn’t know anything about the view/controller hierarchy that it and its view are about to become a part of. Technically, there’s no way for the controller to guarantee that the layout it dictates to its view (or more specifically, the view’s subviews) won’t be damaged by the layout of the view above it in the hierarchy. Since NSViewController can’t always meet the requirements set forth by Autoresizing, the layout is exposed to the glitches and bugs that I’ve described in previous posts by default, and there’s nothing to be done about it except to take extra special care to explicitly size and position new views when adding them into the hierarchy and to set limits on how much the user is allowed to resize elements like split views and the window itself. This is just extra grunt-work code that isn’t *really* necessary. It’s a byproduct of the bugs in Autoresizing.

The bigger problem, one that does sometimes affect the design of the controllers, is that when a view controller makes a change to the existing view hierarchy, by either adding or removing views or manually adjusting the size or position of a view, it needs to take the view’s siblings into consideration. The quote I pasted earlier, defining a view hierarchy, states that:

the view hierarchy defines the layout of views relative to other views.

What the docs really mean is that the layout of views is defined relative to their superviews. The sibling views aren’t considered by the view hierarchy at all during autoresizing. So, any sibling that needs to move or resize as a result of the change needs to have its layout recalculated and its autoresizing mask re-set by the view controller that propagated the change – even if the affected views aren’t in the controller’s domain of the view hierarchy. In this situation, the dynamic layout behavior of the view hierarchy becomes the responsibility of the view controller to maintain. This slippage of responsibility results in case-by-case workaround code that’s not only a pain in the ass to maintain, but that sometimes requires dependencies to views contained by other view controllers to be hard coded into the view controller. All this does is make your view controllers less reusable by no fault of their own. Again, none of this is necessary, it’s a byproduct of the limitations of Autoresizing.

Absolute vs. Relative

At the heart of the problem with Autoresizing, specifically in a dynamic system of views and view controllers, is the fact that there’s just nothing dynamic about it. The mechanism is based on absolute positions and sizes, which makes it an inappropriate layout tool for this kind of interface development. There are very few absolutes in a single window interface design. Absolutes might include the height of a toolbar or the width of a control, but the state of the view hierarchy as a whole, at any given time during the app’s runtime, is variable.

A dynamic view hierarchy and its controller counterparts would be better served by a layout tool that can accommodate relative sizes and positions. In a relative system, a view controller could just tell a newly created view to, for example, *fill* the width and height of its superview or *float* upwards in its superview – without having to specify any specific size or position (which might not be known anyway) – and the view hierarchy would work out the details once the view is in there.

Remember, view controllers have A LOT of responsibility in a Cocoa applicaiton. They handle action events, load nib files, validate menu items, and sync up views to their data. Why add managing the autoresizing behavior of the view hierarchy to their already long list of chores?

Still more to come…

So, yeah, I warned about the rant ;) Now I’ll try to offer a solution.

I’ve prepared an example project that illustrates how to use Jonathan’s and my controller subclasses, XSWindowController and XSViewController to build and manage a dynamic view hierarchy. The project introduces an NSView subclass, KTView, into the mix. Naturally, KTView uses an alternative to the Autoresizing mechanism that’s specifically designed to work within a changing view hierarchy. It’s not a perfect solution (only Apple can do that), but it does get rid of the extra code from the view controllers that’s only there as a byproduct of Autoresizing.

The goal of the project is to bring together the concepts and opinions that have been covered in these three posts through examples of NSViewController in action.

I’ve decided that I don’t want to release KTView without an Interface Builder plugin, so I’ll publish the project with a tutorial as soon as that’s finished.

Until then, I found the original Smalltalk MVC Design paper, How to use Model-View-Controller. It’s interesting to read how Cocoa’s MVC design deviated from the original idea, especially when it comes to views and controllers. Enjoy!

Tags: , , , ,

16 Responses to “NSViewController, the New C in MVC - Pt. 3 of 3”

  1. Ralph Says:

    Hi Cathy,
    your writer’s block is gone, I see. Congratulation, excellent article. I can’t wait to see your solution, KTView…

  2. Mickaël Menu Says:

    Thanks you for this VERY great set of articles!
    Hope the KTView project will come soon :)

  3. unwesen Says:

    Very useful articles!

    I’m not a GUI programmer at heart (my background is more in networking/distributed apps), so anything in the GUI world that isn’t incredibly simple seems unreasonably complicated to me. It’s not necessarily a fair attitude that I have there, but I’m working on it ;)

    Either way, whenever I work on any GUI-related code, and run into problems, I go to the Google oracle to help me along. One thing I’ve noticed is that when it comes to anything Cocoa, my problems either fall into the simple and easily anwered category, or in the category of things that no-one seems to ever have written about on the web (I’ve not posted to Cocoa-dev, but the archives don’t seem to yield answers to my questions either).

    So it comes as a *huge* relief to find such a well-written blog with in-depth discussions of Cocoa/GUI-related issues. It’s also interesting beyond the immediate need for solving problems, this article being a case in point.

    My RSS reader and me are looking forward to more posts!

    But compliments aside, I’ve got a selfish reason for commenting as well. Since you seem to have worked with Cocoa a lot, are you by any chance aware of a drop-in set of views for building a more iTunes 7-ish Cocoa GUI (either programmatically or with Interface Builder)? I’d love to give the little app I’m working on a more modern look, but it seems that involves a huge amount of work that I’m not prepared to invest.

    Either way, keep the blog posts rolling!

  4. Orangutron Says:

    Whenever I am looking for solutions to a problem, I like to peruse existing solutions. In this case, I see that WebKit also deals a lot with dynamic, arbitrarily-placed content at runtime. Perhaps it would be worthwhile for you to explore WebKit’s implementation and compare that with your own.

  5. Cathy Says:

    Hi Orangutron,

    This is a really good observation to make about Web Kit. I’m definately headed in that direction ;)

  6. unwesen Says:

    Orangutron: that’s a fantastic suggestion! I’ve been meaning to look at WebKit anyway, but this gives me another, very good reason to do so - thanks, I hadn’t thought of that!

  7. jerry Says:

    I’m a new Cocoa programmer with a very long history coding elsewhere (i remember learning about ObjC in early 80s along w/smalltalk). So far I am very pleased with it — and glad to be coming to party after it has evolved to this state. I was directed to your blog after posting to cocoa-dev and this article, and example code seem to be a good answer.

    WRT the auto/absolute/relative resizing, are you familiar with Layouts in Java? In that architecture a window/view has a Layout which defines how its subviews are laid out. There are absolute Layouts and relative ones that will automagicially wrap/clip subviews. It can be quite powerful … and frustrating.

  8. jerry Says:

    Ok, I walked through the example code and the magic works (i.e. i dont grok the tech yet). Then I created a MyView.xib and MyViewController.h/m with a button and a text field. The controller connects them so textField shows a count of # times button is pushed. I got the splitView to load these on the left side instead of the Outline, and it worked fine.

    However.

    My applications are not Document based. They are conventional Cocoa apps, which start life from the XCode 3.0 New Project Assistant->Application>Cocoa Application (and sometimes Cocoa-Python application). They start off with a Main Menu.nib which has a Window.

    How do I make the XS view/window stuff work in this framework?
    I tried moving the MyView files over, creating an AppController, removing the Window from MainMenu.xib, creating a MyWindow.xib + Controller, which loads MyView. The best I was able to get was MyView showing up, but pushing the button drops me into the debugger under [NSApplication sendAction:to:from:].

    Apparently the NSApp isnt connected to the window?

  9. jerry Says:

    I figured out how to make it work with my non-document app, but not with the XS stuff. I’m not quite clear on why that is needed as my solution seems to work ok. Does the XS/Resonder stuff address the layout issue?

    I posted my solution back on the cocoa-dev list (june 14 2008 postings)
    It required a bit of code in my AppController specifically

    @interface AppController : NSObject {
    IBOutlet NSWindowController *windowController;
    NSViewController *viewController;
    }

    @implementation AppController
    - (id)init
    {
    if ( self = [super init] )
    {
    // substitute yourView’s nib + controller names in next line
    viewController = [[TheViewController alloc] initWithNibName:@”TheView” bundle:nil];
    }
    return self;
    }

    - (void)awakeFromNib
    {
    [viewController.view setFrame:[windowController.window.contentView frame]];
    [windowController.window.contentView addSubview:viewController.view];
    }
    @end

    plus adding an NSWindowController, putting both into the MainMenu.nib and connecting them there.

    Then in TheView.xib I set the FileOwner to be TheViewController and connect its view in IB… and set up all the other widgets/connections.

    That seems to work and can be copied/shared with other apps.

  10. Cathy Says:

    Hi Jerry,

    Been away from my computer for the past week (WWDC). Glad to see that you seem to have solved your problem. I’ll be sure to address the issue of a non-document based application with the next update to the view controllers.

    Thanks for sharing your solution and if you have more questions, don’t hesitate to send me an email. :)

  11. jerry Says:

    love to drop an email, but I cant find your addr. google did turn up some interesting leads — were you looking for a powerglove serial adapter back in 2004?

  12. Duncan Robertson Says:

    I attended wwdc for the first time last week. Although I got lots from the conference, the thing I find that advances my knowledge quickest, is when people trigger things that have been whirling in your head to make you go aaahh .. I see … These 3 articles from Jonathan and yourself have triggered such a moment. Thankyou to you both.

  13. professional Says:

    Hello. I think you are eactly thinking like Sukrat. I really loved the post.

  14. name Says:

    Good day!,

  15. name Says:

    Hello!,

  16. name Says:

    Hi!,

Leave a Reply