我的应用程序从API中提取HTML,将其转换为NSAttributedString
(为了允许可点击的链接)并将其写入AutoLayout表中的一行.麻烦的是,每当我调用这种类型的单元格时,高度都会被错误计算并且内容会被切断.我尝试过不同的行高计算实现,但都没有正常工作.
如何准确,动态地计算其中一行的高度,同时仍保持点击HTML链接的能力?
不良行为的示例
我的代码如下.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { switch(indexPath.section) { ... case kContent: { FlexibleTextViewTableViewCell* cell = (FlexibleTextViewTableViewCell*)[TableFactory getCellForIdentifier:@"content" cellClass:FlexibleTextViewTableViewCell.class forTable:tableView withStyle:UITableViewCellStyleDefault]; [self configureContentCellForIndexPath:cell atIndexPath:indexPath]; [cell.contentView setNeedsLayout]; [cell.contentView layoutIfNeeded]; cell.selectiOnStyle= UITableViewCellSelectionStyleNone; cell.desc.fOnt= [UIFont fontWithName:[StringFactory defaultFontType] size:14.0f]; return cell; } ... default: return nil; } }
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { UIFont *cOntentFont= [UIFont fontWithName:[StringFactory defaultFontType] size:14.0f]; switch(indexPath.section) { ... case kContent: return [self textViewHeightForAttributedText:[self convertHTMLtoAttributedString:myHTMLString] andFont:contentFont andWidth:self.tappableCell.width]; break; ... default: return 0.0f; } }
-(NSAttributedString*) convertHTMLtoAttributedString: (NSString *) html { return [[NSAttributedString alloc] initWithData:[html dataUsingEncoding:NSUTF8StringEncoding] options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)} documentAttributes:nil error:nil]; }
- (CGFloat)textViewHeightForAttributedText:(NSAttributedString*)text andFont:(UIFont *)font andWidth:(CGFloat)width { NSMutableAttributedString *mutableText = [[NSMutableAttributedString alloc] initWithAttributedString:text]; [mutableText addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, text.length)]; UITextView *calculatiOnView= [[UITextView alloc] init]; [calculationView setAttributedText:mutableText]; CGSize size = [self text:mutableText.string sizeWithFont:font constrainedToSize:CGSizeMake(width,FLT_MAX)]; CGSize sizeThatFits = [calculationView sizeThatFits:CGSizeMake(width, FLT_MAX)]; return sizeThatFits.height; }
wcd.. 7
在我正在开发的应用程序中,应用程序从其他人编写的糟糕API中提取可怕的HTML字符串,并将HTML字符串转换为NSAttributedString
对象.我别无选择,只能使用这个糟糕的API.很伤心.任何必须解析可怕的HTML字符串的人都知道我的痛苦.我用Text Kit
.方法如下:
解析html字符串以获取DOM对象.我使用带有光包装的libxml,hpple.这种组合非常快速且易于使用.强力推荐.
递归遍历DOM对象以构造NSAttributedString
对象,使用自定义属性标记链接,用于NSTextAttachment
标记图像.我称之为富文本.
创建或重用主Text Kit
对象.即NSLayoutManager
,NSTextStorage
,NSTextContainer
.分配后将它们连接起来.
布局过程
将步骤2中构造的富文本传递给步骤3中的NSTextStorage
对象[NSTextStorage setAttributedString:]
使用方法[NSLayoutManager ensureLayoutForTextContainer:]
强制布局发生
计算用方法绘制富文本所需的帧[NSLayoutManager usedRectForTextContainer:]
.如果需要,添加填充或边距.
渲染过程
返回步骤5中计算的高度 [tableView: heightForRowAtIndexPath:]
用第2步绘制富文本[NSLayoutManager drawGlyphsForGlyphRange:atPoint:]
.我在这里使用离屏绘图技术,因此结果是一个UIImage
对象.
使用a UIImageView
来渲染最终结果图像.或者,结果图像对象传递给contents
财产layer
的财产contentView
的财产UITableViewCell
在对象[tableView:cellForRowAtIndexPath:]
.
事件处理
捕捉触摸事件.我使用附有表视图的轻敲手势识别器.
获取触摸事件的位置.在这个位置可检查,如果用户点击一个链接或图片[NSLayoutManager glyphIndexForPoint:inTextContainer:fractionOfDistanceThroughGlyph]
和[NSAttributedString attribute:atIndex:effectiveRange:]
.
事件处理代码段:
CGPoint location = [tap locationInView:self.tableView]; // tap is a tap gesture recognizer NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location]; if (!indexPath) { return; } CustomDataModel *post = [self getPostWithIndexPath:indexPath]; // CustomDataModel is a subclass of NSObject class. UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; location = [tap locationInView:cell.contentView]; // the rich text is drawn into a bitmap context and rendered with // cell.contentView.layer.contents // The `Text Kit` objects can be accessed with the model object. NSUInteger index = [post.layoutManager glyphIndexForPoint:location inTextContainer:post.textContainer fractionOfDistanceThroughGlyph:NULL]; CustomLinkAttribute *link = [post.content.richText attribute:CustomLinkAttributeName atIndex:index effectiveRange:NULL]; // CustomLinkAttributeName is a string constant defined in other file // CustomLinkAttribute is a subclass of NSObject class. The instance of // this class contains information of a link if (link) { // handle tap on link } // same technique can be used to handle tap on image
与[NSAttributedString initWithData:options:documentAttributes:error:]
渲染相同的html字符串相比,此方法更快,更可定制.即使没有剖析我也可以说Text Kit
方法更快.即使我必须自己解析html并构造属性字符串,它也非常快速且令人满意.这种NSDocumentTypeDocumentAttribute
方法太慢,因此是不可接受的.有了Text Kit
,我还可以创建复杂的布局,如带有可变缩进,边框,任意深度嵌套文本块等的文本块.但它确实需要编写更多代码来构造NSAttributedString
和控制布局过程.我不知道如何计算用它创建的属性字符串的边界矩形NSDocumentTypeDocumentAttribute
.我相信创建的属性字符串NSDocumentTypeDocumentAttribute
是由Web Kit
而不是Text Kit
.因此,不适用于可变高度表视图单元.
编辑:
如果你必须使用NSDocumentTypeDocumentAttribute
,我认为你必须弄清楚布局过程是如何发生的.也许您可以设置一些断点来查看哪个对象负责布局过程.然后,也许您可以查询该对象或使用另一种方法来模拟布局过程以获取布局信息.有些人使用ad-hoc单元格或UITextView
对象来计算高度,我认为这不是一个好的解决方案.因为这样,应用程序必须至少两次布局相同的文本块.无论你是否知道,在你的应用程序的某个地方,某些对象必须布置文本,这样你才能获得布局信息,如边界矩形.既然您提到了NSAttributedString
类,那么最好的解决方案是Text Kit
在iOS 7之后.或者Core Text
如果您的应用程序是针对早期的iOS版本.
我强烈建议,Text Kit
因为这样,对于从API中提取的每个html字符串,布局过程只发生一次,并且布局信息(如边界矩形和每个字形的位置)都被NSLayoutManager
对象缓存.只要Text Kit
保留了对象,就可以随时重用它们.当使用表视图呈现任意长度的文本时,这非常有效,因为文本只布置一次并在每次需要显示单元格时绘制.我也推荐使用Text Kit
不UITextView
作为官方苹果文档建议.因为UITextView
如果他想重用Text Kit
附加的对象,就必须缓存每一个UITextView
.Text Kit
像我一样将对象附加到模型对象,只更新NSTextStorage
和强制NSLayoutManager
从API中提取新的html字符串时的布局.如果表视图的行数是固定的,则还可以使用固定的占位符模型对象列表来避免重复分配和配置.而且因为drawRect:
会导致Core Animation
产生无用的支持位图必须避免,不要使用UIView
和drawRect:
.使用CALayer
绘图技术或将文本绘制到位图上下文中.我使用后一种方法,因为这可以在后台线程中完成GCD
,因此主线程可以自由响应用户的操作.我的应用程序中的结果非常令人满意,速度很快,排版很好,表格视图的滚动非常流畅(60 fps),因为所有绘图过程都是在后台线程中完成的GCD
.每个应用程序都需要使用表视图绘制一些文本Text Kit
.
在我正在开发的应用程序中,应用程序从其他人编写的糟糕API中提取可怕的HTML字符串,并将HTML字符串转换为NSAttributedString
对象.我别无选择,只能使用这个糟糕的API.很伤心.任何必须解析可怕的HTML字符串的人都知道我的痛苦.我用Text Kit
.方法如下:
解析html字符串以获取DOM对象.我使用带有光包装的libxml,hpple.这种组合非常快速且易于使用.强力推荐.
递归遍历DOM对象以构造NSAttributedString
对象,使用自定义属性标记链接,用于NSTextAttachment
标记图像.我称之为富文本.
创建或重用主Text Kit
对象.即NSLayoutManager
,NSTextStorage
,NSTextContainer
.分配后将它们连接起来.
布局过程
将步骤2中构造的富文本传递给步骤3中的NSTextStorage
对象[NSTextStorage setAttributedString:]
使用方法[NSLayoutManager ensureLayoutForTextContainer:]
强制布局发生
计算用方法绘制富文本所需的帧[NSLayoutManager usedRectForTextContainer:]
.如果需要,添加填充或边距.
渲染过程
返回步骤5中计算的高度 [tableView: heightForRowAtIndexPath:]
用第2步绘制富文本[NSLayoutManager drawGlyphsForGlyphRange:atPoint:]
.我在这里使用离屏绘图技术,因此结果是一个UIImage
对象.
使用a UIImageView
来渲染最终结果图像.或者,结果图像对象传递给contents
财产layer
的财产contentView
的财产UITableViewCell
在对象[tableView:cellForRowAtIndexPath:]
.
事件处理
捕捉触摸事件.我使用附有表视图的轻敲手势识别器.
获取触摸事件的位置.在这个位置可检查,如果用户点击一个链接或图片[NSLayoutManager glyphIndexForPoint:inTextContainer:fractionOfDistanceThroughGlyph]
和[NSAttributedString attribute:atIndex:effectiveRange:]
.
事件处理代码段:
CGPoint location = [tap locationInView:self.tableView]; // tap is a tap gesture recognizer NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location]; if (!indexPath) { return; } CustomDataModel *post = [self getPostWithIndexPath:indexPath]; // CustomDataModel is a subclass of NSObject class. UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; location = [tap locationInView:cell.contentView]; // the rich text is drawn into a bitmap context and rendered with // cell.contentView.layer.contents // The `Text Kit` objects can be accessed with the model object. NSUInteger index = [post.layoutManager glyphIndexForPoint:location inTextContainer:post.textContainer fractionOfDistanceThroughGlyph:NULL]; CustomLinkAttribute *link = [post.content.richText attribute:CustomLinkAttributeName atIndex:index effectiveRange:NULL]; // CustomLinkAttributeName is a string constant defined in other file // CustomLinkAttribute is a subclass of NSObject class. The instance of // this class contains information of a link if (link) { // handle tap on link } // same technique can be used to handle tap on image
与[NSAttributedString initWithData:options:documentAttributes:error:]
渲染相同的html字符串相比,此方法更快,更可定制.即使没有剖析我也可以说Text Kit
方法更快.即使我必须自己解析html并构造属性字符串,它也非常快速且令人满意.这种NSDocumentTypeDocumentAttribute
方法太慢,因此是不可接受的.有了Text Kit
,我还可以创建复杂的布局,如带有可变缩进,边框,任意深度嵌套文本块等的文本块.但它确实需要编写更多代码来构造NSAttributedString
和控制布局过程.我不知道如何计算用它创建的属性字符串的边界矩形NSDocumentTypeDocumentAttribute
.我相信创建的属性字符串NSDocumentTypeDocumentAttribute
是由Web Kit
而不是Text Kit
.因此,不适用于可变高度表视图单元.
编辑:
如果你必须使用NSDocumentTypeDocumentAttribute
,我认为你必须弄清楚布局过程是如何发生的.也许您可以设置一些断点来查看哪个对象负责布局过程.然后,也许您可以查询该对象或使用另一种方法来模拟布局过程以获取布局信息.有些人使用ad-hoc单元格或UITextView
对象来计算高度,我认为这不是一个好的解决方案.因为这样,应用程序必须至少两次布局相同的文本块.无论你是否知道,在你的应用程序的某个地方,某些对象必须布置文本,这样你才能获得布局信息,如边界矩形.既然您提到了NSAttributedString
类,那么最好的解决方案是Text Kit
在iOS 7之后.或者Core Text
如果您的应用程序是针对早期的iOS版本.
我强烈建议,Text Kit
因为这样,对于从API中提取的每个html字符串,布局过程只发生一次,并且布局信息(如边界矩形和每个字形的位置)都被NSLayoutManager
对象缓存.只要Text Kit
保留了对象,就可以随时重用它们.当使用表视图呈现任意长度的文本时,这非常有效,因为文本只布置一次并在每次需要显示单元格时绘制.我也推荐使用Text Kit
不UITextView
作为官方苹果文档建议.因为UITextView
如果他想重用Text Kit
附加的对象,就必须缓存每一个UITextView
.Text Kit
像我一样将对象附加到模型对象,只更新NSTextStorage
和强制NSLayoutManager
从API中提取新的html字符串时的布局.如果表视图的行数是固定的,则还可以使用固定的占位符模型对象列表来避免重复分配和配置.而且因为drawRect:
会导致Core Animation
产生无用的支持位图必须避免,不要使用UIView
和drawRect:
.使用CALayer
绘图技术或将文本绘制到位图上下文中.我使用后一种方法,因为这可以在后台线程中完成GCD
,因此主线程可以自由响应用户的操作.我的应用程序中的结果非常令人满意,速度很快,排版很好,表格视图的滚动非常流畅(60 fps),因为所有绘图过程都是在后台线程中完成的GCD
.每个应用程序都需要使用表视图绘制一些文本Text Kit
.