iOS 的 UITextView 显示含有网络图片的 NSAttributedString 时,怎么样接管网络图片的下载过程?

2014-11-26 18:59:00 +08:00
 q84629462
NSString *html = @"<img src=\"http://cdn.v2ex.com/site/logo@2x.png\"/>";
NSDictionary *options = @{NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType};
NSAttributedString *string = [[NSAttributedString alloc] initWithData:[html dataUsingEncoding:NSUnicodeStringEncoding] options:options documentAttributes:nil error:&error];
//textView是UITextView
[text setAttributedText:string];
想在上面这一个过程中,实现:
把html中的img替换成小的缩略图显示,并使用TMCache缓存缩略图,在UITextView中点击缩略图后,显示原图。
搜索了两三天,依然找不到图片的下载是在NSAttributedString/UITextView这两个玩意的哪个方法里,倒是实现了点击后识别是否图片。
32122 次点击
所在节点    iDev
80 条回复
q84629462
2014-11-26 19:59:42 +08:00
@jox 大能求教
jox
2014-11-26 20:59:08 +08:00
。。。。。。。。

你使用了document attribute,我不是这样实现的,所以不清楚如果使用document attribute的话要怎么实现图片的点击事件。我不建议你这么做,这样真的很慢,不仅要把字符串转化成NSData,而且还有parse和render html的开销,我不清楚内部是怎么做的,我非常怀疑这样做的话实际上会调用web kit,我刚开始接触这个问题的时候也尝试过使用document attribute,慢的要死。

正确的做法是使用text kit或者core text,建议使用text kit,把图片作为富文本的附件和文本绑定,设定附件的尺寸,然后用text kit的layout manager算出附件的rect,然后再渲染图片,你如果使用textview渲染文本的话,iOS7之后textview有个delegate方法可以捕捉到图片和超链接的点击事件:

textView:shouldInteractWithTextAttachment:inRange:

你还可以创建个gesture recognizer和textview绑定,然后在action里使用layout manager得到图片的信息,如果你不使用textview渲染文本的话,可以在touchesBegan:withEvent:那四个方法里响应tap事件,上面提到的layout manager获取图片信息的方法是这个:
boundingRectForGlyphRange:inTextContainer:

如果你打算渲染gif的话,不要配置NSTextAttachment的image属性,只配置bounds属性,然后或者直接画或者使用UIImageView渲染,如果全是静态图片的话,直接配置image属性就行。

至于图片的缓存和缩略图方面你要自己实现,text kit就是用来对文本进行排版和渲染的,core text也可以,但是text kit用起来简单很多,text kit是在core text的基础上做的封装,记得如果一段文本里图片特别多的话你要注意图片的尺寸不要太高,不然会发生文字不显示的问题。。。
jox
2014-11-26 21:02:50 +08:00
忘了说了,如果使用text kit的话,你要自己parse html文本,将其转化成attributed string,你可能得写个简单的parser,这个挺简单的,借助别人写的一些工具就行,这个http://www.raywenderlich.com/14172/how-to-parse-html-on-ios 里面的那个hpple就不错,用的是libxml,速度非常快,因为会将html转化成dom树,用起来也很方便,递归地遍历dom树就行了
q84629462
2014-11-26 21:11:10 +08:00
@jox textkit就是NSAtrributedString和NSTextAttachment啊,跟我的用法有区别吗?
jox
2014-11-26 21:15:32 +08:00
当然有区别了,你是直接把html丢给text view然后就不管了,至于text view到底用的什么方法渲染html你是完全不清楚的,我说的是你自己使用text kit手动控制排版。
jox
2014-11-26 21:19:28 +08:00
text kit不是“NSAtrributedString和NSTextAttachment”,text kit是一整套的文字排版方案,NSAtrributedString是text kit能够处理的数据类型,NSTextAttachment是text kit接受的数据类型的其中一种属性。你能说出这种话说明你根本不理解text kit,再去看看文档和相关的资料吧,你只有理解了text kit的排版过程你才能解决你遇到的这个问题,不难的,看看文档就行了
PrideChung
2014-11-26 21:21:18 +08:00
我能想到的两种方案
1.用UIWebView+contentEditable属性代替UITextView
2.用NSURLProtocol拦截网络请求(不肯定能成功)
jox
2014-11-26 21:26:38 +08:00
@PrideChung uiwebview很占内存,uiwebview不是用来渲染文本的,而且使用uiwebview的话不能控制排版,只有全部加载完之后才能知道准确的尺寸,如果一段html里有多个img tag的话,这些图片的请求是没法控制的,而且还得使用javascript,蛋疼的要死。

直接parse html得到图片的url之后自己控制网络请求就很简单了,uiwebview是用来显示网页的,不是用来处理使用html作为数据结构传输的数据的。
gonghao
2014-11-26 21:30:37 +08:00
我倒觉得可以用 NSAttributedString 但是不要用 HTML 就行,图片不是太多不便于管理的画,自己创建一个 attachment 加到 NSAttributedString 里面去就行,图片管理可以自行控制,点击图片的问题,可以用 touchBegin 获取点击坐标,然后 NSLayoutManager 去找到坐标对应的 character 再判断是否是 NSTextAttachment 就成
PrideChung
2014-11-26 21:30:56 +08:00
@jox 那先试试NSURLProtocol吧,我不太清楚你的需求,UIWebView你可以当成最后的手段。
jox
2014-11-26 21:37:05 +08:00
@PrideChung 我不是来问问题的,我是被lz召唤过来的,webview的use case是用来渲染某个url的,webview的frame是固定的,相当于在应用里嵌入了个浏览器,类似html里的iframe,不需要考虑排版

但是lz要使用缩略图来渲染图片,这就涉及到了排版,只能使用text kit或者core text来实现
PrideChung
2014-11-26 21:56:43 +08:00
@jox 那么看来NSURLProtocol是最好的候选了吧,只要拦截到图片的请求然后替换成缩略图的URL就好了,就是不知道这样的图片请求能不能被拦截到。
jox
2014-11-26 22:25:22 +08:00
@PrideChung 我不知道,我也刚接触iOS开发没多久,我没用过你说的这个NSURLProtocol,需要进行network call的时候我使用的是iOS7出来的NSURLSession,用起来挺简单的,按照你说的这种拦截网络请求怎么感觉那么别扭呢?程序得到一个url,程序想要得到这个url指向的资源,直接向这个url发送网络请求不就好了吗,替换url干啥啊。。。。

即使能够拦截网络请求,你也不能保证你从网上得到的一段html文本里面的某个img tag里的src指向的图片资源一定有对应的缩略图版本,即使有你也不一定知道对应的缩略图版本的url是什么

我觉得正确的做法就是先parse html,然后使用得到的src属性里的url试图从网上下载这个url指向的资源,如果下载成功,那么就使用image I/O框架提供的API将图片裁剪成合适的缩略图尺寸得到图片的数据,然后再考虑是否要cache,然后再想办法渲染图片。如果下载失败,那就不渲染,这个逻辑不是更简单吗?
PrideChung
2014-11-26 22:45:35 +08:00
@jox 我一开始理解错了LZ的意思了,我以为他是想把完整图片的URL替换成缩略图的URL。不过NSURLProtocol还是可行,NSURLProtocol的强大在于不仅可以替换请求的NSURLRequest,甚至还能用自己组织的数据来响应拦截到的网络请求。

具体这么做:
通过NSURLProtocol拦截掉UITextView发出的图片加载请求,自己另外新建一个网络请求下载图片,完成缓存和裁剪缩略图的操作,然后再把缩略图当做请求成功的数据响应给之前被拦截的请求。

调用TextKit和Core Text排版是挺麻烦的事情,可免则免。
q84629462
2014-11-26 22:51:21 +08:00
其实我也是刚刚接触iOS,所以自定义排版对我来说比较难。。。
缩略图其实也是网络图片,是专门用来给移动设备图文混排里展示的,用户不一定会点击看大图,所以把html夹带的图片转为缩略图显示就比较重要了。
我其实想先用正则把HTML里的img src替换为缩略图src
但这个想法恐怕行不通,我刚刚测试了一下,
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithData:[html dataUsingEncoding:NSUnicodeStringEncoding] options:options documentAttributes:nil error:&error];
执行这句之后,html字符串中的图片就已经开始下载了。
我是这么测试出来的:
上门那句代码之后紧跟:
[string enumerateAttribute:NSAttachmentAttributeName inRange:NSMakeRange(0, string.length) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) {
if (value) {
NSTextAttachment *ment = value;
ment.image = [UIImage imageNamed:@"noavatar"];
}
}];
然后再[uitextview setAttributedText:string];
此时显示的noavatar这个图片已经不是noavatar原先的尺寸了,也就是说iOS已经在后台读取到了图片,获得了图片真实尺寸,而且还是同步读取图片。
恐怕还真要自己解析html了。。。
q84629462
2014-11-26 22:52:36 +08:00
@PrideChung 其实你的理解是对的,我就是想把原图转为缩略图src
jox
2014-11-26 22:53:11 +08:00
@PrideChung 。。。。。。。。。。我觉得操作网络请求更麻烦,core text是不太好用(其实也还行),但是text kit很简单啊,如果使用textview的话只需要parse一下html就可以了,不使用textview直接用text kit渲染也挺简单的啊,一共也没几行代码,怎么会麻烦呢?
q84629462
2014-11-26 22:56:27 +08:00
@PrideChung 不过接管图片下载过程是指找出是哪个类哪个方法实现了图片下载,想集成这个类这个方法自己图片实现下载并缓存到disk而已
我其实有个更大胆的想法,想用UITextView实现类似网页中的图片延迟读取,屏幕显示到哪个图片再去读取,但似乎太异想天开。。。
jox
2014-11-26 23:00:35 +08:00
@q84629462 文字的排版的确是很艰深的一个问题领域,不过你要处理的这个问题非常非常的简单,就用text kit就能解决,另外,请不要使用正则表达式来parse html,html是context free的语言,你不能使用正则表达式来处理html。

网上已经有写好的代码可以很简单地parse html,我说的那个hpple就很好用,请千万不要使用正则表达式来处理HTML,不信你可以试试,我保证你会后悔的
jox
2014-11-26 23:07:11 +08:00
@q84629462 这不是异想天开,这是lazy loading,你如果要渲染大量的html文本的话,lazy loading不是“异想天开”,而是你必须要实现的,要么借助uitableview,要么你自己实现,否则内存占用会成为很严重的一个问题,iOS下几乎所有的view都使用CALayer来渲染内容,Core Animation会cache生成的render数据,这些数据非常占内存,光渲染一大段文字就需要将近20M的内存,如果你不能确定将要渲染多少数据的话,图片必须经过裁剪再使用,而且也必须使用lazy loading的策略,否则内存占用轻轻松松就会破百。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/149498

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX