Fast UITableViewCell with a UIWebView
If you use UITableView’s you know that scrolling through your cells has to be fast. If you’ve done much research, you’ve probably found the Tweetie approach using one custom view and doing the drawing yourself. This works great for static text and image cells. It gets more difficult when you need to add hyperlinking to the text in the cell. The problem becomes how you add hyperlinking to a UITableViewCell but keep the scroll speed as fast as possible.
I have an app that requires this type of functionality. Its an enterprise social computing app – you can think of it like Facebook or Yammer. Users can post microblog status messages that other users can “like” and comment on.
The first attempt I made was to subclass ABTableViewCell and in my drawing I would set aside certain regions that were “linkable”. In this screenshot, the light blue in the cells denotes text that is linkable – usernames in this case.
My tableview cell then waits for touchesBegan and looks to see if a touch is in one of these areas. If it is, it sets a flag denoting what area is touched and redraws the cell with a highlight behind the text. When it gets touchesEnded it tells its delegate what function needs to happen. So for the screenshot above, touching the username would navigate to a view showing that users details.
Its a pretty easy approach but it has its limits such as a status that has multiple links. I was left with either hand parsing the html and measuring where all the text should go that’s linkable, draw that text in a different color, and remember those regions. Or I could just strip out any HTML and draw flat text for the status… I took the easy route. Worked great for a 1.0 release but of course status linking was the number one feature request from users.
The idea came to me that I could add my own UIWebView to the cell and become its delegate so that I could react to any touches on links. As you probably know though, adding subviews – especially non-opaque subviews – can cause scrolling to be slow and to skip frames in the animations. To work around this I decided two things…
1. Only use the UIWebView when the text actually contains a link, otherwise just draw flat text
2. Only show the UIWebView when the tableview isn’t scrolling so that we don’t get in the way
This approach seems to work really well – but there are a few issues that you need to work around. Most of them I’ve figured out but there are two that I’m still stumped on which I will detail in a bit.
Issues and Workarounds
Issue one – loading an HTML string into a UIWebView takes a second
It takes a little bit for the webview to display your HTML. My workaround is to draw the HTML stripped version of the status in flat text and keep the webview hidden until you receive webViewDidFinishLoad. At that point you can remove the flat text or draw it in clear color and set hidden = NO on the webview.
Issue two – UIWebViews flash when first loading content
I honestly can’t tell you why this happens, but it does. And its a bit jarring to the user experience. The workaround for this is to simply move the code to redraw the cell and display the webview into its own method then use performSelector:withObject:afterDelay to call it. 0.35 second duration seems to work nice.
Issue three – UIWebViews will crash your app if you don’t nil its delegate
This was a weird bug. Because your popping webviews on and off cells at a pretty quick pace, you’ll run into situations where the webview hasn’t completed loading your HTML before you want to pop the webview off and release it during prepareForReuse (and you always want to pop the webview off and release it when preparing for reuse!) or dealloc. The problem is that the webview continues to live until its done loading whatever it is it was loading. When its done it will tell its delegate (your cell) that its done. If your cell was dealloced… CRASH. So make sure you set its delegate to nil.
Other Issues I Haven’t Solved
Issue one – UIWebView eats touches
If your app responds to tableView:didSelectRowAtIndexPathto show a details view, you’ll notice that if you touch within the bounds of the webview, this method will never fire. So the user is forced to touch a part of the cell thats outside the webview. If you designed your cell well, they’ll have no idea where this boundry is. Its a very frustrating user experience – one that I would love to get solved before shipping.
Issue two – status bar touches no longer scroll to top
If any of your displayed cells have a webview on them – hidden or not – the scroll to top by touching the status feature of the tableview quits working. My guess is that the scrollView child of the UIWebView is eating these as well and not passing them up the responder chain. Its another issue I haven’t resolved yet.
OK – enough talk. Here’s a sample project that shows how I do this… FastTableViewCellWithWebView (or you can look at the full Mercurial repository here). Download it, run it, let me know what you think. And more important… let me know if you can resolved my two issues!