Archive for February, 2008

Bye Bye, IB2

Wednesday, February 27th, 2008
IBIcons.jpg

I’m still using IB2 at work, but we’ll be making the move to Leopard over the next few months. I feel a slight pang of pre-nostalgia when I think about trading in Interface Builder 2 and its historical code base for its shiny new successor, Interface Builder 3. I know that IB3 is going to be a million times more usable and that it was past time for a real change in the world of IB, but those who know me know that I tend to pine for the old before embracing the new. I’ll pine now and get it over with.

I remember the exact moment when I realized how amazingly cool Interface Builder is. I was making a custom palette for some GUI classes, implementing the drawing and mouse event handling methods for when they would be running in IB. While I was typing, it struck me that what I was doing was a little strange. I was giving these classes a second, meta life only to be invoked by Interface Builder. All of the views and controls in Cocoa have this second IB life. When we drop them into a window, they draw preview versions of themselves and sometimes even respond to our mouse and key events (if we poke around in just the right spots). Interface Builder takes over from there. It guides us as we drag the views around, adjust their sizes and much more. Wow, what a clever and useful application. Who thought of this? They’re a genius.

Jean-Marie Hullot and his Interface Builder

965797A3-7BB9-4D8E-8207-2FD87BF7906E.jpg

Interface Builder was developed by Jean-Marie Hullot, pictured above during his days at NeXT. There is little written about him or his invention on the internet except that Hullot wrote the original version in some Lispy language and that it was originally called ‘SOS Interface’. Steve Jobs loved it, kidnapped Hullot and Tim Berners-Lee used it to make the first web browser, WorldWideWeb. Now we all use Interface Builder.

While I was scouring the net for information about Hullot, I came across a PDF excerpt from the book “How the Web was Born: The Story of the World Wide Web” by James Gillies and Robert Cailliau. There is a short section about Hullot and Interface Builder, titled “Monsieur Hullot’s holiday’. This is my favorite bit (emphasis is mine):

Hullot was using a Macintosh, and like many developers, he rapidly found that although the Mac’s GUI might have been brilliant, it was a pig of a machine to write applications on. ‘When the Macintosh was invented,’ he explains, ’suddenly, compared to what you had to do before, you had to add at least 60 or 70 percent of your time to make the user interface.’ Before the Macintosh, the interface was pretty basic-you would just type a line of text and the computer would respond with successive lines scrolling up the screen-but that meant that programmers didn’t have to worry about it. All the windows and menus that came with the Mac were great for the people who used the end applications, but for the people who had to write them they were a nightmare. Each time they wanted a menu or a window in their application, they had to program it in from scratch. Hullot’s big idea was to turn the problem into a simple question of drawing lines on the screen. He made a palette of objects for things like menu items and windows. Then to build the outline of an application you would simply take objects from the palette and draw lines between them. So, for example, you could pick up a menu object, and then by drawing lines from the other objects like ‘Open’ or ‘Print’, you would put those functions in the menu.

Hullot called his invention Interface Builder. It was a powerful tool that, like Smalltalk, put the power of objects at the developer’s disposal.

Kind of amazing, huh? These days, it’s easy to take things like windows and menus for granted. They come free with our development tools. On top of that, the responder chain and target/action mechanisms make it simple to dynamically link buttons and menu items to actions. If your app has a simple interface, there’s very little code that needs to be written for UI development. Even if your interface is complex, much of the burden is lifted by Hullot’s technology. Thank you, Jean-Marie.

The book goes on to describe Hullot and Jobs’ first meeting and the rest is history. In case you haven’t seen it, here’s a video of Steve Jobs presenting some applications on NeXTSTEP. He shows Interface Builder at 23:40. Funny how little has changed until now.

So, I’ll say goodbye to Inteface Builder 2 and end my pining now. Bye bye, IB2. Your interface was really strange and awkward but you were really cool and useful. I’ll miss palettes (not really, actually).

Styling an NSTableView (DTTAH)

Friday, February 22nd, 2008

NSTableView is a great class for displaying long lists of data, but its default implementation is kind of boring looking. This post will show you how give your table views extra sex appeal. I’m categorizing it as a “don’t try this at home” tutorial because it sometimes involves overriding private NSTableView methods. Be warned that Apple can and will change the private framework code without notice and that some of the techniques used in this tutorial may no longer work. The tutorial and the example project are meant to be educational and exploratory. They’re not necessarily “right”, so use them wisely.

If anyone out there knows how to get comparable results without using NSTableView private methods, please please leave a comment.

The Example Project

drag.jpg

The CustomTableView Xcode project can be dowloaded here.

If you’re new to Cocoa or subclassing NSTableView, the tutorial will be most useful if you download the project and follow along in the source code. The comments will help explain implementation details. The project has 3 classes:

  • MyController (the application’s main controller and the table’s delegate and data source)
  • MyTableView (the NSTableView subclass)
  • MyCell (the NSCell subclass)

It also adds categories to NSColor and NSBezierPath for custom alternating background colors and rounded rectangles.

In the sample project, customizations are made to the following visual properties of the table:

  • Alternating background colors
  • Selection highlight
  • Drag image
  • Drop highlight on rows
  • Drop highlight between rows

The table also uses an NSCell subclass to draw an icon and two rows of text. Let’s start with NSCell.

NSCell

Table views use the NSCell class to draw content in their rows. The first step to customizing the look of your table is to create an NSCell subclass and override the method

-(void)drawInteriorWithFrame:(NSRect)theFrame inView:(NSView *)theView

In the example project, the MyCell class divides the NSRect passed in the frame argument into three smaller rectangles that it uses to position the image, title and subtitle. The image and text are then drawn into those rectangles using NSImage and NSString’s respective -(void)drawInRect methods.

After you make your subclass, you need to let the table view know about it. You can set the cell for the columns of the table using NSTableColumn’s method

-(void)setDataCell:(NSCell *)theCell

In the example project, the MyController class sets the data cell for the table’s column with the following lines of code in awakeFromNib:

MyCell * aCustomCell = [[[MyCell alloc] init] autorelease];
[[oMyTableView tableColumnWithIdentifier:@"theTableColumn"] setDataCell:aCustomCell];

NSTableView

After you have an NSCell subclass, you may also want to customize some of NSTableView’s visual properties. You can set the background color, row height, whether or not the table should draw horizontal or vertical lines and the color of the lines using the NSTableView inspector palette in Interface Builder. Other adjustments take a little bit of code.

Alternating Background Colors

If you choose to display alternating background colors, the default colors are light blue and white. A simple way to customize the alternating background color scheme in your application is to add a category to NSColor that overrides the method

+(NSArray *)controlAlternatingRowBackgroundColors 

In your implementation of this method, return an NSArray containing the two colors you want your tables to use.

Selection Highlight

In the example project, MyTableView draws a rounded rectangle in the selected rows. If you want to draw your own selection highlight, override the following method in your NSTableView subclass:

-(void)highlightSelectionInClipRect:(NSRect)theClipRect

This method is asking you to draw the highlights for the rows that are currently selected within the “clip rect”, or the area of the table that is visible. One way to do this is to:

  1. Use NSTableView’s method
    -(NSRange)rowsInRect:(NSRect)theRect;

    to get the range of row indexes that are currently visible in the clip rect.

  2. Use NSTableView’s method
    -(NSIndexSet)selectedRowIndexes;

    to get a list of all of the selected rows in the table.

  3. Go through the list of visible row indexes. If an index is included in the list of selected indexes, get the NSRect for that row with the NSTableView method:
    -(NSRect)rectOfRow:(int)theRowIndex;
  4. Draw your custom highlight style inside this rectangle.

If your application is running on Tiger or any previous OS and you only override this method, you’ll notice that the table still draws its default highlight on top of your drawing. The second thing you must do to draw a custom selection highlight in this case is override the private method:

-(id)_highlightColorForCell:(NSCell *)cell
{
    return nil;
}

Returning nil in this method will “stop” the table view from drawing the default highlight rectangle (weird, huh?).

If you only want to change the highlight selection color instead of drawing a custom highlight, you can simply override the method

-(id)_highlightColorForCell:(NSCell *)cell

to return the color of your choice.

Drag Image

If your NSTableView subclass supports dragging, you can create a custom drag image by overriding the method

- (NSImage *)dragImageForRowsWithIndexes:(NSIndexSet *)theRowIndexes
			    tableColumns:(NSArray *)theTableColumns
				   event:(NSEvent *)theEvent
				  offset:(NSPointPointer)theOffset

In this method make an NSImage, draw what you want into the image and return it. You can get your NSTableView subclass to give you an NSImage with the content of the cell drawn inside by calling super’s implementation of the method.

In the example project, MyTableView composites the NSImage returned by super onto another NSImage with a rounded rectangle drawn inside. The new NSImage is returned as the drag image.

Drop Highlights

Drop highlights in NSTableView are notoriously fugly and they are not “legally” customizable. In the example project, MyTableView overrides some private methods to draw a black rounded rectangle to highlight drags over rows and a line with a small accent circle (like NSOutlineView) to highlight drags between rows.

For Tiger and earlier, there are two private methods that your NSTableView subclass can override to draw custom drop highlights on and between rows:

-(void)_drawDropHighlightOnRow:(int)theRowIndex;
-(void)_drawDropHighlightBetweenUpperRow:(int)theUpperRowIndex
			     andLowerRow:(int)theLowerRowIndex
				atOffset:(float)theOffset; 

In both cases, use the NSTableView method

-(NSRect)rectOfRow:(int)theRowIndex

to retrieve the rectangle of the row you want to highlight.

In Leopard, things get a little more complicated. There are several private methods that NSTableView might use to draw the drop highlight between rows. Through trial and error, I found that overriding the following method does the trick:

- (void)_drawDropHighlightBetweenUpperRow:(int)theUpperRowIndex
			      andLowerRow:(int)theLowerRowIndex
				    onRow:(int)theRow
				 atOffset:(float)theOffset

A new NSTableView feature in Leopard is the drop highlight color. Like the rest of the drop highlight visual properties, it is only customizable by overriding a private method. If you don’t override this method, the default implementation will draw a light blue background over the row that is the current drop target. If light blue isn’t your thing, you can override

+(id)_dropHighlightBackgroundColor;

to return the color of your choice. If you want your table to have the same highlights running in Leopard as it does in Tiger, return [NSColor clearColor].

Do Not Try This At Home!!!

I hope that this post will help you build a beuatiful table view that your users will love to look at, but once again, please please be careful when using private NSTableView methods for selection and drop highlights. While it’s fun and educational to explore the AppKit framework, you absolutely should not override private methods in your shipping app. I’ve filed an enhancement radar in the hopes that Apple will make these methods public:

rdar://problem/5758731

If anyone knows of other techniques that are more legal, please share them in the comments.

Resources

The source code in the CustomTableView example project has detailed comments that should help answer any questions you may have about implementing the techniques described in this post.

Other useful projects with alternate approaches to customizing the visual elements of a table:

If you are new to Cocoa drawing, I’d suggest reading the Quartz 2D Programming Guide as well the Learn Quartz tutorials at CocoaDevCentral.

For more information on NSTableView, start with the Introduction to Table Views Programming Guide. There is an NSTableView tutorial on the CocoaDev wiki as well as one at CocoaDevCentral. These will help with the less superficial parts of programming an NSTableView (like how to really implement drag and drop and data sources).

For more information on NSCell, read the Control and Cell Programming Topics for Cocoa.

If NSTableView isn’t exactly what you’re looking for and you’re targeting OS 10.5+, you might want to look into the new Cocoa class, NSCollectionView.

Also, If you’re targeting OS 10.5+, take a look at the new source list selection style for NSTableView.

Finally, class-dump lets you look at stuff you shouldn’t be looking at.

Computer Administrative Debris

Friday, February 15th, 2008

Edward Tufte recently posted a video analysis of the iPhone interface on his website. If you haven’t seen it yet, I highly recommend taking a look.

My favorite part is when he offhandedly states, “The white frames are too thick and should be in gray and only a single pixel thick,” as he’s scrolling the thumbnail browser. I imagine Edward Tufte working as a design supervisor at Apple, watching over people’s shoulders, scolding young designers for not displaying enough information. I’m sure he would run a tight ship where every pixel would have a meaningful purpose.

Actually, I lied, my real favorite part is a term Tufte uses frequently in the video, “computer administrative debris”, usually as he’s noting the lack of it on the iPhone’s UI. That’s a great way to describe all the crap on our interfaces, the important stuff that enables users to perform complex tasks in a tiny two-dimensional workspace. Things like windows, menus, tabs, check boxes, toolbars, scroll bars, etc.

One of the challenges of interface design is to reduce computer administrative debris while maintaining a high standard of usability. Of course, the difficulty of this challenge is related to the complexity of the application. Pro apps are going to have more computer administrative debris than consumer apps simply because they have more features. The iPhone and Apple TV-type applications have it easy in this sense, because their users are mostly browsing content. The developers don’t often need to provide them with a context to adjust or create the content. Most desktop apps, on the other hand, are all about providing that context. Still, there are some things that desktop application developers can do to trim the amount of debris on their interfaces.

Acorn.jpg

PShop.jpg

Top: Flying Meat’s consumer image editor, Acorn

Bottom: Adobe’s pro image editor, Photoshop

Features

As Jeff Atwood points out in his post, Every User Lies, users don’t actually use most of the features of the applications they buy. They want to, and might even tell you that they do, but they usually don’t. Jeff’s advice is good. Observe. Figure out what is critical for your users to get their work done and go from there. Also, don’t be afraid to say no to feature requests if you’re in the position to do so. Keeping the feature set streamlined is a simple way to reduce the amount of computer administrative debris your users will have to wade through to get a simple task done.

A good example of this is Flying Meat’s consumer image editor, Acorn. Pictured above are both Acorn and its comparable pro application, Adobe Photoshop. They’re both shown at their initial states, just after I opened an image. Acorn is clearly more approachable because there is less debris on the screen. Its feature set is not nearly as rich a Photoshop’s, but Acorn’s developer, Gus Mueller, knew that his users were not going to use all of those features anyway. He made a succinct feature list and because of that he is able to minimize computer administrative debris on his interface.

Organization

Pro and prosumer apps, like Apple’s Aperture, are in a constant state of struggle with computer administrative debris. They are the applications with the most features, but with some organization, even they manage improve their computer administrative debris to content ratio over time.

Aperture2.jpg

Aperture1.jpg

Top: Aperture 2
Bottom: Aperture 1.5.6

Pictured above are Apple’s Aperture 2, which came out this week, and its predecessor, Aperture 1.5.6. The Aperture 2 team did some re-organizing for this release. The most notable organizational improvement is the shifting of the Adjustments and Metadata widgets over to the source list area. The arrangement that we see in 1.5.6 is no longer possible. Users will never see the source list, adjustments, and metadata debris at the same time. The Aperture team understood that the users will never need to access more than one of these features at a time, so they made the decision to consolidate. Using tabs as an organizational tool, they reduced debris.

Directness

Tufte makes an important observation about the iPhone when he talks about computer administrative debris. For many features, the iPhone uses the content as the interface and users interact with it directly rather than through interface widgets. Again, the fact that users of the iPhone are mostly browsing makes it easier to do this, but there’s something to be learned from their model.

A simple example of letting the users interact directly with content to trim debris is drag and drop, something we’re all familiar with by now. By letting users either pick up items from one container and drag them into another or reorder items in a single container, we reduce the need for buttons or action menus. Less debris. Apple’s iMovie 08 boasts, “Drag-and-drop moviemaking”. In fact, Apple’s entire iLife and iWork repertoire is full of examples of interacting directly with content instead of through menus, buttons or text fields, greatly reducing the amount of computer administrative debris on their interfaces.

Keynote.jpg

Apple’s Keynote

Keynote, pictured above, has a very clean interface with little debris because users interact directly with the interface representations of the content. Can your interaction model be more direct? When could users interact directly with content instead of through text fields or action menus? This is a tough one and often requires some creative coding, but its worth it.

Free of Debris (not really)

It’s not possible to completely eliminate widgets from a desktop application, but we have the tools we need to reduce the amount of unsightly and distracting computer administrative debris on our interface designs. With the introduction of gesture events and CALayers in Cocoa, the Mac developer’s toolbox is becoming more sophisticated and we have lots of room to be creative and innovative.

Users are also becoming more sophisticated and their expectations are evolving. It won’t be long until the mere sight of a pop up menu will be enough to make them quit an application and never open it again. Trust that they can handle an unorthodox interaction model. They might be liars, as Jeff Atwood points out, but they learn quick if they’re presented with a well thought out and executed design. The iPhone interface proves that and as Tufte notes over and over in his video, there is often no computer administrative debris in sight.

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.