对于NSAttributedString,AutoLayout行高度计算错误

Mat*_*att 13 objective-c ios autolayout ios8

我的应用程序从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;
    }
}
Run Code Online (Sandbox Code Playgroud)

- (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;
    }
}
Run Code Online (Sandbox Code Playgroud)

-(NSAttributedString*) convertHTMLtoAttributedString: (NSString *) html {
    return [[NSAttributedString alloc] initWithData:[html dataUsingEncoding:NSUTF8StringEncoding]
                                            options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
                                                      NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)}
                                 documentAttributes:nil
                                              error:nil];
}
Run Code Online (Sandbox Code Playgroud)

- (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;
}
Run Code Online (Sandbox Code Playgroud)

wcd*_*wcd 7

在我正在开发的应用程序中,应用程序从其他人编写的糟糕API中提取可怕的HTML字符串,并将HTML字符串转换为NSAttributedString对象.我别无选择,只能使用这个糟糕的API.很伤心.任何必须解析可怕的HTML字符串的人都知道我的痛苦.我用Text Kit.方法如下:

  1. 解析html字符串以获取DOM对象.我使用带有光包装的libxml,hpple.这种组合非常快速且易于使用.强力推荐.
  2. 递归遍历DOM对象以构造NSAttributedString对象,使用自定义属性标记链接,用于NSTextAttachment标记图像.我称之为富文本.
  3. 创建或重用主Text Kit对象.即NSLayoutManager,NSTextStorage,NSTextContainer.分配后将它们连接起来.
  4. 布局过程
    1. 将步骤2中构造的富文本传递给步骤3中的NSTextStorage对象[NSTextStorage setAttributedString:]
    2. 使用方法[NSLayoutManager ensureLayoutForTextContainer:]强制布局发生
  5. 计算用方法绘制富文本所需的帧[NSLayoutManager usedRectForTextContainer:].如果需要,添加填充或边距.
  6. 渲染过程
    1. 返回步骤5中计算的高度 [tableView: heightForRowAtIndexPath:]
    2. 用第2步绘制富文本[NSLayoutManager drawGlyphsForGlyphRange:atPoint:].我在这里使用离屏绘图技术,因此结果是一个UIImage对象.
    3. 使用a UIImageView来渲染最终结果图像.或者,结果图像对象传递给contents财产layer的财产contentView的财产UITableViewCell在对象[tableView:cellForRowAtIndexPath:].
  7. 事件处理
    1. 捕捉触摸事件.我使用附有表视图的轻敲手势识别器.
    2. 获取触摸事件的位置.在这个位置可检查,如果用户点击一个链接或图片[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
Run Code Online (Sandbox Code Playgroud)

[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 KitUITextView作为官方苹果文档建议.因为UITextView如果他想重用Text Kit附加的对象,就必须缓存每一个UITextView.Text Kit像我一样将对象附加到模型对象,只更新NSTextStorage和强制NSLayoutManager从API中提取新的html字符串时的布局.如果表视图的行数是固定的,则还可以使用固定的占位符模型对象列表来避免重复分配和配置.而且因为drawRect:会导致Core Animation产生无用的支持位图必须避免,不要使用UIViewdrawRect:.使用CALayer绘图技术或将文本绘制到位图上下文中.我使用后一种方法,因为这可以在后台线程中完成GCD,因此主线程可以自由响应用户的操作.我的应用程序中的结果非常令人满意,速度很快,排版很好,表格视图的滚动非常流畅(60 fps),因为所有绘图过程都是在后台线程中完成的GCD.每个应用程序都需要使用表视图绘制一些文本Text Kit.


Mad*_*man 5

您需要更新内在内容大小。

我假设您在此代码中将属性文本设置为标签 [self configureContentCellForIndexPath:cell atIndexPath:indexPath];

所以,它应该是这样的

cell.youLabel.attributedText = NSAttributedString(...) 
cell.youLabel.invalidateIntrinsicContentSize()
cell.youLabel.layoutIfNeeded()
Run Code Online (Sandbox Code Playgroud)

您的高度计算代码(CGFloat)textViewHeightForAttributedText:(NSAttributedString*)text andFont:(UIFont *)font andWidth:(CGFloat)width 应替换为使用原型单元格的单元格高度计算。