Hob-Knobbing with Interface Builder

It’s pretty tempting to take a good Cocoa tool—say, Cocoa Bindings—and proceed to use it everywhere, regardless of whether it’s really the best way to solve your problem.

My recent example of this involved Interface Builder.

I wanted to add custom guides to a resizable view, like in this not-for-real example:

Blue square with round green "knobs" on all four corners and at the mid-point of all four borders

Interface Builder is a good Cocoa tool, right? So I used it to set up the little green “knobs”.

Then, I wanted to fiddle with their size.

Hm.

I could, in the above example, manually go in and change the size and location of 8 views in my nib. A disheartening prospect, but Interface Builder is a good tool, right?

But you have to remember why it’s a good tool.

There isn’t really any difference in difficulty between changing a window’s size in Interface Builder and changing it in code. In both cases, you make a change, save, compile, and run.

But Interface Builder is better because you get a mini-preview of the change right there. You can fiddle with it a lot, get it just right, without any compile-run cycle at all. Same with changing a color, or rearranging subviews.

That’s why it’s a good tool. It makes it easy to keep fiddling with the details until you get them just right. If it doesn’t help you with that, then don’t use it.

Once I got that message through my head, I went back to my source code files and wrote an implementation that created and placed the knob views programmatically, based on a single value: knob diameter. (Make your own joke here.) Change that value, compile and run, and see the results instantly. While it cost me an hour or two to make it work like that, after that, it only took me a few minutes of fiddling to come up with a size I liked. And, more importantly, I can go back later and change it again with the same ease. Say, after I’ve run it past a real designer.

Sorry, Interface Builder.

More Than Token Problems

One of my favorite typos from when I worked at Apple was “NSTokenRage”. Rage…over tokens? I don’t know what that was about, but I’ve certainly had my share of puzzlement, frustration, and yes, rage, over NSTokenField, and I have the bugs to prove it.

So let’s dish.

I’ve found three big problems with it.

Tabbing Out

First, tabbing out doesn’t invoke the same token validation behavior as typing in a tokenizing character within the field, such as a comma or a return.

In the latter case, the delegate’s tokenField:shouldAddObjects:atIndex: method is called, and so the delegate can choose whether to tokenize the user’s input or not. In the former case, however, it is not called, nor is any other standard delegate method. Tokenization of the user’s input just…happens.

Here are screenshots from the sample app I submitted with the bug. In the token field, duplicate tokens are not allowed, and if you type in duplicate text, it should disappear instead of being tokenized. Here’s an example, where I’m about to enter the duplicate text “b”:

If I then type return, focus stays in the token field, and the duplicate token is rejected and disappears:

But if I type tab, which changes focus to the text field below the token field, the duplicate token is erroneously created:

The sample app also includes a workaround.

Each token does a round trip from the string the user initially types, to a “represented object” for that string once that string becomes a token, to a display string for the represented object used when drawing the token.

The string → represented object data is provided by the delegate method tokenField:representedObjectForEditingString:, and the represented object → display string data is provided by the delegate method tokenField:displayStringForRepresentedObject:. The workaround is for the first method to return a represented object indicating that the string is invalid (I used NSNull), and for the second method to return nil for the display string for such an invalid object. While this doesn’t keep the invalid represented object from being added to the list, it does prevent it from being displayed.

The downside of this approach is that we get entries like these in Console for the application:

2011-03-13 17:20:47.668 Token Workaround[2819:903] : Failed to query display string for a represented object . Ignoring…

The bug for this issue is rdar://9000060.

Unheralded Delete

A related problem is that there is no delegate method that is called when the user deletes a token, no tokenField:willDeleteObjects:.

Without this, it becomes harder to enforce a no-duplicates rule. The workaround I used was to implement the NSControl delegate method controlTextDidChange:. It’s a lower-level access point, but at least it’s guaranteed to catch all editing changes.

The bug for this issue is rdar://9000191.

Keystroke Validation

The last major issue I saw was an inability to do keystroke validation by the standard Cocoa technique of subclassing NSFormatter.

Apple’s docs list three methods you need to override in a subclass:

  • stringForObjectValue:
  • getObjectValue:forString:errorDescription:
  • attributedStringForObjectValue:withDefaultAttributes:

Only the last actually has to do with keystroke validation. The first two do the same sort of round-tripping from display string ↔ represented object that we saw in NSTokenField itself.

The trouble is, NSTokenField has its own internal attributed string format that is impossible (or seems like a bad idea to me, anyway) to try to replicate. It would be nice if there were a way to just implement the third method, and tell the formatter that, hey, the existing formatting is just fine, thanks. But as far as I know, there isn’t.

I got around this myself by implementing a custom field editor and overriding a private method that is called to actually set the text. I’d much rather have a better way.

The bug for this issue is rdar://9092238.

There’s also the more minor issue that it seems to be impossible to set the right arrow key as a tokenizing character (rdar://8849747).

And that’s all I have to say about NSTokenRageField.

The Lovers, the Dreamers, and Me

Lukas Mathis has a great post on performing complex tasks—as opposed to “multitasking”—on iOS.

I’d like to tie this back to John Gruber’s ongoing iPad creation vs. consumption argument. He’s consistently portrayed it as ludicrous that anyone would doubt the ability to make creative works on the iPad. For example, “Insert your own smirking mockery of those who insist the iPad is only for consumption and not creation here.” from “The Chair”.

But it’s not ludicrous, and Mathis shows why. Simple creative tasks on the iPad? Sure. Complex creative tasks? Hell no, and by design. If it’s the future of computing…the future’s still missing some pretty big stuff. It’s missing me, as a creative person who works primarily via complex tasks.

I think the best way to blindside Apple in the next couple of years is to find an amazing solution to this problem, because, for all the great things they’re doing, Apple most likely won’t. (Lukas mentions webOS.)