waffle

Waffle is a weblog.
The author of Waffle, some guy in Sweden, also occasionally writes stmts.net.

writing

(Waffle passed out from exhaustion in March 2012 and is now coming back online.)

How to Mimic the Artwork Column in Cocoa

iTunes’ Artwork column is interesting.

The Artwork column in iTunes

It’s the leftmost column in the iTunes table view. Not only can it be unfurled at will, but it spans multiple table view rows. I’ve recently had the need to use something like the Artwork column. In fairness, my implementation is probably a bit more similar to the header in iPhone Spotlight, in that it spans across multiple rows, doesn’t necessarily take up a fixed minimal height and doesn’t invent extra rows for the duration of that minimal height. But that header also keeps the icon sticky at the top, which mine doesn’t.

It’s practically impossible to find any information about how to implement this. I’ve tapped my regular knowledgeable Cocoa contacts for hints, including the ones that are about to go kill some trees about it, and the consensus has been that it’s a doozy with no one clear approach.

A few different approaches emerged:

  • Creating a new table column and merging cells. The one reference implementation on cell merging seems to be a Dan Wood article from 2003.

    Let me be clear: age doesn’t play into whether the approach is viable since the fundamental structure of NSTableView hasn’t changed for a number for years. Lots of NeXTStep-era material is still illuminating. However, it is effectively left as an exercise to the reader how to implement integration with new features like the expansion rectangle for automatic “show the entire cell” tooltips. And the article only shows how to merge cells horizontally. I wanted vertically, which means that the code has to be reworked.

    I did this, and it did work, for some values of work. But it took a lot of effort to get there and it didn’t work very seamlessly.

  • Dumping a view into the table view. The most popular link I was shown in my research was Joar Wingfors’ seminal view-inside-a-cell trick, which was the first Cocoa trick I marveled at years back and which holds some merit. Really, this is what I want, isn’t it? Yes, mostly. The trouble is that you don’t really dump a view “inside” a cell — there’s no inside of a cell! A cell is like a magic stencil that you move around and fill out, and any illusion of actual position is mediated by a view pulling the strings. The trick hinges on placing the view inside NSTableView‘s own view hierarchy and updating it constantly as the cell is ordered to draw itself in new coordinates.

    What left me stranded with the merged cells approach was that of fighting the table view structure itself; a cell wants to be at one coordinate, and not spread thinly across many. You have to do a lot of fallible and fragile footwork to convince NSTableView that a cell at one coordinate is really the same as another, and that it has a larger extent, and that that cell should be drawn instead, despite it technically being offscreen and therefore good for culling.

    This approach didn’t live and die with cells being merged. I also quickly tried dumping a view directly inside the table view, but this was about as unsupported as you’d get. You’re highly likely to stand in the way of any assumptions under which the table view is working (the only controls that are by definition careful about messing with any subviews they didn’t stick there themselves are container views), and it didn’t work that well out of the gates. Even if I’d gotten it working, it would have been even more prone to eventual failure. I can’t rely on an implementation detail.

  • Keeping a separate view with some level of scroll view mechanism inside it, and synchronize the two. This is what ultimately turned out to work well enough.

What I did, very roughly, was this:

  1. Set up a table view.
  2. Establish a subclass of NSClipView and a custom view.
  3. Make both the clip view subclass and the custom view flipped. This may angry the blood with some people, but if you don’t, it’ll start out in the bottom left instead of the top left.
  4. Place your custom clip view next to the table view, and set its document view to be your custom view. Don’t forget the autoresizing mask if you’re doing this programmatically.
  5. Register for the table view’s scroll view’s clip view’s NSViewBoundsDidChangeNotification; inside it, set the scroll point of your custom clip view. This is genius and comes straight from CocoaDev. I love that site.
  6. Arrange, somehow, for your custom view to be resized to the appropriate height, updated with the prerequisite span information for the headers and (populated with views or custom drawing cells or custom drawing directly the headers that should be visible) (parenthesised for clarity and grouping), whenever the table view is updated. This is the mother of all reader exercises, but it will also vary heavily based on the design you settle on.

Some of you may have expected example code for this. I don’t have time to put such code together, and enough of the implementation most likely will differ that very little will be generally applicable. However, since I anguished over this for more or less a full week before I got it to work, and because Scott Stevenson cheered me on about helping Google since I had so much trouble finding it, I thought I’d document what little is firm about this.

This solution is still relatively new. I may still find that it falls apart in some situations. It’s also fairly imperfect with lots of manual synchronization, and the headers don’t integrate naturally under a column header in the table view if that’s what you’re interested in. But it’s an approach I found worked. If anyone has any ideas or code samples I am, along with the rest of the Cocoa-related readers, of which I understand there are a few, willing to listen in the comments.

Update: Jacob, the creator of Opacity, writes in with an alternate approach (includes source code): draw a slice of the image in a corresponding cell, one by one. Not a perfect solution for some more advanced scenarios, but good enough and a trifle to implement. (Also includes code for the “toggling” part of the functionality.)

Comments

  1. Not a helpful comment, but I presume iTunes is still not a Cocoa app?

    By JulesLt · 2009.10.04 19:54

  2. iTunes is Carbon. I don’t even think it uses the Carbon “data browser” control that’s the equivalent of NSTableView; there’s very little to pluck from its implementation.

    By Jesper · 2009.10.04 19:59

  3. I am not by far an expert on Carbon and Cocoa, but shouldn’t HICocoaView solve the problem with Cocoa views inside a Carbon application? I am just guessing that’s one way they could have done it.

    By Marcus · 2009.10.04 22:17

  4. iTunes uses some Cocoa on Mac OS X; I’m not sure entirely how much. It may just be the Mac OS X part of WebKit, which it now uses for the Store and iTunes LP. In any case, knowing that doesn’t make implementing the Artwork column in one’s Cocoa application any easier, so I’d like to table that part of the discussion.

    By Jesper · 2009.10.04 22:25

  5. It seemed to me that an easier way to do this might be to divide the content and draw that in a series of tubes slices. I made a basic example of what I’m talking about here: Another Way to Mimic the Artwork Column in Cocoa

    By Jacob · 2009.10.04 22:52

  6. An interesting proposal; something I actually did consider and write off as too contrived for some reason early on. I think I had another, better reason (any alternative solution is plenty contrived!) but I’m drawing a blank right now.

    By Jesper · 2009.10.04 23:07

  7. Thanks for the article.

    I needed exactly this a few years ago when I was writing a music store app for a client. Spent a few days trying to come up with a plan but eventually just told my client that it was more complicated than it looked and we settled on a different solution. This has still been bothering me ever since and I’ll probably try this solution just to see if I can get it to work.

    Thanks again. - Hjalti

    By Hjalti Jakobsson · 2009.10.05 01:46

  8. Very interesting read. Thanks for going into detail about approaches you tried that did not work! Very few articles give you such a complete picture.

    Could you have accomplished something similar to your final solution using a custom subclass of NSRulerView? That’d be where I would start if I needed to do something similar. There is an interesting article by Paul Kim on using NSRulerView for showing line numbers next to an NSTextView here.

    It may not exactly match you needs, but it would avoid some of the manual scroll synchronization and such.

    Thanks again for the article-thoroughly enjoyed it!

    By Jason Foreman · 2009.10.20 18:59

  9. I think it’s worth jumping through hoops to make sure the custom column is, legitimately, a custom column cohesive with the rest of the table.

    Consider the possibility that you allow column reordering. Users will be confounded by the fact that the one column can’t be rearranged like all the others (actually iTunes exhibits symptoms of this being the case for their solution).

    Also consider the accessibility implications. If you use a separate, custom view, you would need to go to extra trouble to get a screen reader to believe it was actually a column in the table, as opposed to a sibling view.

    By Daniel Jalkut · 2009.10.20 19:17

  10. Daniel: Very good points! It’s fine for my purpose, where it’s just going to be for decoration and the grouping context is clear in other ways. It’s not going to be a “table” table view. Then again, as you say, not even the iTunes implementation does reordering. (I consider it okay since it’s a very disruptive element, but I suppose you could also want it to go on the right edge.)

    By Jesper · 2009.10.20 20:02

  11. Jason: I actually neglected ruler views entirely since I was convinced they were only for horizontal headers/footers. (I certainly saw them; I’ve never dug around so much in the scroll/clip view swamp than I did when investigating this.)

    By Jesper · 2009.10.20 20:04

  12. After reading this post and Jacob’s followup, I went ahead and implemented a general version of this. It is based off of Jacob’s approach of using slices but generalized to work with any NSCell. It all works within the tableview framework so no odd syncing issues plus you can manipulate the columns just like any other. I’ve posted it here: Yet Another Way To Mimic the Artwork Column in Cocoa

    By Paul Kim · 2009.10.21 19:39

Sorry, the comment form is closed at this time.