原文地址:
http://disanji.net/2010/11/26/fast-uitableviewcell-with-a-uiwebview/
如果你用过UITableView,你会知道必须能够快速地滚动浏览单元格。如果你做过足够的搜索,你可能已经发现Tweetie approach方法使用一个自定义视图而且自己完成绘图。这种方法对于静态文本和图片是不错的。但是当你需要向单元格中文本加入超链接时这种方法就变得困难。问题变成如何向UITableViewCell加入超链接并且保持滚动浏览的速度尽可能快。
第一个方法:
我有一个程序需要这个功能。它是一个企业的社会计算程序,你可以认为它是一个类似Facebook或者微博客的东西。用户可以发布微博客状态信息其他人可以进行评论。
第一个方案是子类化ABTableViewCell,在我的绘图过程中我会把可能做成链接的区域先放在一边。在这个截图中,单元格中浅蓝色部分表示可以链接的文本(这个例子中是用户名)。
我的tableview之后等待touchesBegan而且要判断是否在这些区域有触击事件发生。如果是的话,就设置标志位表示这个区域被触击了然后在文本后面用高亮来重绘这个单元格。当它获得touchesEnded信号,会调用相应的函数。对于上面截图的例子,触击用户名将会运行一个视图来展示用户的详细资料。
这是相当简单的一个方法但是也有它的局限比如对于有多链接的某个状态就无能为力。此时,我既可以手动解析html,判断有链接的文本的链接地址,以不同的颜色绘制文本而且记住这些区域。或者我可以展开任何html,为每个状态绘制单调的文本。我选择了简单的方式。对于1.0发布版来说工作的不错但是当然状态链接是用户最需要的特性。
第二个方法:
我突然想到我可以添加自己的UIWebView到单元格中而且成为它的委托以便于我能够对链接上的触击事件做出反应。你可能已经了解到,添加子视图(特别是透明的子视图),会引起滚动浏览变慢而且遗漏动画里的某些帧。为了处理它我决定了两件事情:
1 仅仅当文本确实包含一个链接时使用UIWebView,否则就只是绘制普通的文本。
2 仅仅当tableview没有滚动的时候显示UIWebView以便于我们不会妨碍滚动速度。
这个方法似乎工作正常,但是有几个问题需要注意。大部分我都已经解决但是仍然有两个我还在研究后面我会详细解释一下。
问题和变通方案
第一个问题——加载一个html字符串到UIWebView需要一秒钟。
它花费一些时间来使你的webview显示html。我的解决方案是以普通文本方式绘制html的字符串,同时保持webview是隐藏的直到你获得了webViewDidFinishLoad信号。在此时你可以移除普通文本或者以透明色绘制它然后设置webview的hidden位为NO。
第二个问题——当第一次加载内容时UIWebViews会闪烁
我无法找到这个问题之所在。而且它对于用户体验是不好的。这个问题的解决方案是把绘制单元格和显示webview的代码移到它自己的函数中,然后使用performSelector:withObjuct:afterDelay来调用它。0.35秒的持续时间似乎使得显示正常。
第三个问题——如果你不清零它的委托的话,UIWebViews会使你的程序崩溃
这是一个诡异的bug。因为你以相当快的速度压入和弹出webviews到单元格中,你可能会进入这样这个情形:webview还没有完成加载html而此时你打算将webview弹出,之后在prepareForReuse或回收内存时将它释放。此时webview会继续生存直到它的加载完所需加载的东西。当这些完成之后它会通知它的委托(cell)加载已经完成。如果你的单元格已经解除分配,程序就会崩溃。因此要确保它的委托置零了。
其他我还没有解决的问题
第一个问题——UIWebView吞噬触击事件
如果你的程序响应tableView:didSelectRowAtIndexPath来显示一个详细视图,你会注意到如果你在webview的边界内触击,这个函数不会触发。因此用户不得不去触击单元格内其他部分(webview之外)。如果你很好地设计了单元格,用户就不会知道这个边界在哪里。这个一个非常令人沮丧的用户体验——一个我想要在旅行之前解决的问题。
第二个问题——触击状态栏不会滚动回顶部
如果每一个显示的单元格都有一个webview在上面(无论隐藏与否),通过触击状态栏来滚动回顶部不能正常实现。我的猜测是UIWebView的子类scrollView截获了这些信号,没有将它们传递给响应链(response chain)。这个另一个我没有解决的问题。
原文作者:Nick Harris
原文链接:
https://nickharris.wordpress.com/2010/06/17/fast-uitableviewcell-with-a-uiwebview/
Fast UITableViewCell with a UIWebView
The Problem
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.
First Solution
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.
Second Solution
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!
Tags: CocoaMacNick HarrisObjective-cUITableViewUITableViewCellUIWebView