Posts Tagged ‘Cocoa’

Welcome, Designers

Monday, March 10th, 2008

My head has been reeling all weekend after watching the iPhone SDK presentation. The excitement I felt the first time I saw an iPhone has returned full-force after it was crushed by Steve Jobs during last year’s WWDC keynote address. I still think he owes all of us an apology for trying to pass off Ajax techniques as an SDK like we’re stupid. Anyway, I’m super happy and optimistic about all the new technology we get to play with and like many others, I can’t help but think about the future. There’s one thing in particular that I can’t get off my mind.

There are going to be lots of new Cocoa developers and many of them will come from the field of design, not software development. Some will have never programmed before in their life. This happened with HTML and CSS when the web became popular. It’s going to happen in the world of iPhone development and Mac desktop application development will naturally follow.

This influx of new developers means that the usability of the Cocoa’s APIs are going to be put to the test like never before. Bugs like NSView’s autoresizing not working as advertised or the fact that the methods to customize the drag and drop highlights of an NSTableView are private and not legally accessible to developers are not going to sit well with this new generation of Mac devs, who will demand control over every aspect of their visual design.

It should be possible for the Cocoa engineers to give developers complete control over the visual properties and interactive behaviors of their classes. I don’t mean in that convoluted creating a custom cell way, it should be easy — HTML and CSS easy (in fact, why don’t we use style sheets?). If there are points where this is technically impossible, it’s a design flaw of the framework and should be fixed. If it means writing a new NSTableView, so be it.

Sorry, you’re screwed

I keep thinking of an article I read about the history of CSS and this part in particular:

Meanwhile, writers of Web pages were complaining that they didn’t have enough influence over how their pages looked. One of the first questions from an author new to the Web was how to change fonts and colors of elements. HTML at that time did not provide this functionality - and rightfully so. This excerpt from a message sent to the www-talk mailing list early in 1994, gives a sense of the tensions between authors and implementors:

In fact, it has been a constant source of delight for me over the past year to get to continually tell hordes (literally) of people who want to — strap yourselves in, here it comes — control what their documents look like in ways that would be trivial in TeX, Microsoft Word, and every other common text processing environment: “Sorry, you’re screwed.”

The author of the message was Marc Andreessen, one of the programmers behind NCSA Mosaic. He later became a co-founder of Netscape and by then his views - if they ever were his views - on formatting had changed.

I doubt that any Cocoa engineer has such a flippant attitude towards designers. After all, this is OS X — the best looking operating system on the market. I just hope that they will take these types of issues into more serious consideration when mapping out their priorities. Sure, advertising “New Table Views!” in Cocoa isn’t as sexy a feature as Core Animation, but it’ll make everyone’s life easier in the long run. Imagine this question popping up on the Cocoa-dev list: “How do I customize the background color of a column in an NSTableView”. Now think of the answer…

Why I Say ‘NO’ to Autoresizing

Tuesday, February 12th, 2008

Cocoa does so much so well, but like any framework, it has its quirks. It can be difficult to decide when it’s time to put your foot down and say “No. Thanks. I’ll take over from here.” For me, one of those moments came when I had finally had it with NSView’s autoresizing behavior. Like a self-doubting girlfriend, convinced that I must be doing something wrong, I let it go for a long time. Eventually I had to face the truth. My views were not where I expected them to be and it wasn’t because my expectations were unreasonable. Autoresizing just wasn’t working.

Autoresizing

NSView has an “autoresizing” mechanism. It’s a layout tool that lets the developer configure how a subview will resize and reposition itself when its superview’s frame rectangle changes. For example, let’s say that we have a window with a button in the bottom right corner and we want it to stay there when the user resizes the window.

Picture 2.png

The button is a subview of the window’s “content view”, so we’ll configure the button’s autoresizing behavior. The button is fine being positioned at the bottom of the window, but we want it to move along the x-axis as the window’s width changes. To do this, we’ll use the autoresizing mask constant NSViewMinXMargin. According to the docs,

If set, the view’s left edge is repositioned proportionally to the change in the superview’s width. Otherwise, the view’s left edge remains in the same position relative to the superview’s left edge.

If we want to configure the view programmatically, we’ll first let the superview know that it needs to trigger the autoresizing, then we’ll configure the subview’s autoresizing mask.

[oWindowContentView setAutoresizesSubviews:YES];
[oButton setAutoresizingMask: NSViewMinXMargin];

Or we can use the nifty visual editor in Interface Builder to set “springs and struts”:

Picture 41.png

The Problem

The example above works great, but it doesn’t take much to get autoreszing to misbehave. I’m going to make a view and give it one subview. I want the outer view to adjust its width and height with the window and the inner view to stay its original size, but to remain centered in its superview. To achieve this layout, I’ll use the following code:

// turn on autoresizing
[oWindowContentView setAutoresizesSubviews:YES];
[oOuterView setAutoresizesSubviews:YES];

// set autoresizing mask
[oOuterView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
[oInnerView setAutoresizingMask:(NSViewMinXMargin | NSViewMinYMargin |
		 		 NSViewMaxXMargin | NSViewMaxYMargin)];

This is what happens:

Everything is fine for a little while, the inner view stays centered. But at one point, after the outer view’s height becomes smaller than the inner view’s height and I expand the window back out, the inner view’s Y margin is lost and its original setting is broken. The inner view is stuck to the top of the outer view’s frame.

This is a very simple example. In real life, view hierarchies are much more complex. We have views inside split views inside other split views next to other views containing buttons, text fields and sliders and they all have to know what to do when the user resizes the window or drags a divider. Behavioral quirks like the one in the video become harder and harder to live with as our interfaces grow.

Taking Control

I sometimes think that designing a dynamic layout is a bit like choreographing a dance routine. What should I do when the dancers just can’t get it right, no matter how many times we’ve been over it? I could instruct them to just stay still when they forget what to do and to jump back in when they’re ready. I could hire someone to stand on the sidelines during the performance, reminding them of their next move when they look lost. Another option is to just replace them with dancers who listen and remember.

I spoke with an Apple engineer at the last WWDC about the problems I was having with autoresizing. I can still see the look on his face when he realized what I was talking about. It was as if he was thinking, “Oh yeahhhhh THAT…” Yeah. That. I asked if the NSView people had fixed That in Leopard. He confirmed that no, they had not and suggested that I set a minimum size for the superviews to prevent the situation. He also asked me what I thought they could do to anticipate resizing past certain sizes.

The idea of setting a minimum size for the superview is the common fix for this. You see it most often with split views, where superviews are very likely to be sized smaller than their subviews. It seems to be an accepted practice to restrict split view divider dragging past some arbitrary point when there is no real usability or layout-related reason to do so. The behavior I see time and again is that just before the split view divider reaches the subview’s amnesia-inducing point, it’ll automatically jump closed and when it’s dragged back out, it’ll jump back. This is bad interaction design. It’s a work-around that has nothing to do with what is good or bad in terms of “user experience”. In fact, it creates a bad user experience. Things shouldn’t be jumping around under the mouse, especially in the middle of a split view divider drag when the user is expecting some measure of control over the layout.

So what could NSView do to prevent this behavior? It could cache margins or at least the proportions we’re trying to preserve. That way, it will always have the correct value to use for its calculations. Seems kind of easy to me.

For the time being, we’re stuck with this behavior and we have to either set minimum sizes for superviews, pollute our application code with controllers to monitor and adjust the layout, or subclass NSView and all of its subclasses and define our own resizing behavior. None of these situations is ideal. The first results in less dynamic layouts and less enjoyable interactions, but it’s simple. The second is just dirty and the last is a pretty big undertaking considering how many subclasses NSView has. I’m sure there are other creative solutions we could consider. You can probably guess what I do from the title of this post. I subclass and bypass the whole autoresizing mechanism. The only time I turn on autoresizing is for the view that’s at the very top of the hierarchy so that it resizes with the window. My solution has its own issues, but they’re pretty easy to deal with and I get an amazing dance troupe that can handle even my most avant-garde choreographic efforts.

A zip file containing the Xcode project I use in the video can be downloaded here.

UPDATE (02-16-08)

I was just thinking about what the bug really is. I don’t think that it’s an issue of simply cacheing a value anymore. There’s something else going on.

If we look at NSViewMinYMargin. Again from the docs:

If set and the superview is not flipped, the view’s top edge is repositioned proportionally to the change in the superview’s height.

I think it has to do with the change calculation. You’ll notice in the video, it didn’t happen each time i shrank the superview. I think that when you’re resizing fast enough, it’s possible that the notification of the frame change is a little behind and when it goes to make the calculation, the change is 0. That doesn’t explain why it stays “stuck”, but it is possible to “unstick” it if you keep resizing over and over. When it unsticks, the proportions are off, so that it’s no longer centered, so there’s something weird there, too. Very very weird.