People have asked which part of our Twitterrific application for the iPhone was hardest to develop. There were many challenges, but the one I found most onerous was scrolling in the UITableView.
The code we shipped in 1.0 was obviously flawed. Scrolling was jerky. We weren’t happy with it and neither were users.
There was no shortage of “expert” opinions on what was causing it. Some thought it was the loading of the avatar images, others thought it was the fancy backgrounds. One reviewer on iTunes even suggested that I could spend a couple of hours reading documentation on Apple’s site and fix it up.
The root of the problem was having links inline with the text: there are currently no attributed strings in the iPhone SDK. Before displaying a table row, I had to scan the text of the tweet for URLs and screen names, measure the length of text, and then create a bunch of views that approximated what people were used to seeing as HTML. This overwhelmed the CPU on the device: the GPU was having no problem compositing the layers. I had heeded the advice of experts and was careful to flatten the hierarchy as much as possible and to use opacity sparingly: only NSString’s sizeWithFont: was killing me.
So what changed in the 1.1 release? A slight interaction change and a new approach to rendering the UIButtons used for the links in the text.
The interaction change was that no text was styled when a table row is unselected. This allowed us to use a plain UILabel for the text of the tweet. Since only one tweet is selected at any time, this simplified the text layout requirements when reusing a table row.
Since UILabel was being used for rendering the text, our approach for measuring text changed as well. Instead of controlling all the text layout, I had to adapt my code to use metrics and line breaking that matched those used in UILabel’s implementation. They call it reverse engineering, kids.
This approach is far from perfect. There are cases where the buttons are placed incorrectly: I suspect that the cause of these problems is how round-off error is being handled in the method used by UILabel’s underlying WebView and the method used by sizeWithFont:. A half-pixel being rounded up versus down can cause a line break or the button text to not align with the underlying label. These errors seem to occur more frequently with Asian fonts.
There is also an issue with a URL that wraps a line: the button is only created for the first part of the URL (leading some users to think that the button won’t work—it will.)
In essence, it’s a hack. And useful one until Apple deals with Radar ID# 5834539 (titled “There is no way to style a run of text in a UILabel or UITextView”.) It wouldn’t hurt to dupe it if you’d like to see this aspect of the iPhone SDK improve.
Now that the FABULOUS NDA is a thing of the past, there’s no reason why you should have to figure this stuff out like I did. Here’s my implementation. Knock yourselves out customizing this code for your own needs. All I ask is that if you make any improvements in createButtons:, please let me know (my contact info is on my résumé page.)
But wait, there’s more!
And an extra added bonus, this project also shows you how to use regular expressions using RegexKitLite and libicucore. Extra pattern matching goodness, for all your iPhone needs.
Damn it feels good to leave out that NDA disclaimer at the bottom of this essay.
Updated December 4th, 2008: In the original essay, I forgot to link to the RegexKitLite page at SourceForge. This code by John Engelhart is so damn useful, I tend to think about it as a standard part of OS X. Take a moment and check out this project’s exemplary documentation.