iOS UITableView 动态渲染文本和图片(NSAttributedString + 图片)

4
我的问题是:我在一个iOS应用程序中有动态内容(例如推特 - 尽管这不是一个推特应用程序),包括文本和图像(主要是图标/表情符号和缩略图)。我想在一个表格行中同时呈现文本和图像。主要困难在于每一行都会有不同的大小(使缓存更加困难),而且我需要动态计算图像大小,以适应每个图像出现时周围的文本(我可以有像20个表情符号挨着另一个+文本+更多表情符号等)。
我已经寻找了一些方法并尝试了几种方法。我的最初想法是使用UIWebView。我能够创建一个每行一个UIWebView的示例应用程序,即使对于预渲染单元格的一些“智能”NSCache,性能也不是很好,而且我必须处理UIWebView javascript回调来确定何时正确加载内容。
我的第二次尝试是为新的UITableViewCell覆盖drawRect。在drawRect方法中,我使用NSAttributedString和NSSelectorFromString来设置每种内容的范围(常规文本、粗体、斜体、不同颜色、图像)。为了适应图像,我使用CTRunDelegateCallbacks回调(getAscent、getDescent、getWidth)。就像这样:
            CTRunDelegateCallbacks callbacks;
            callbacks.version = kCTRunDelegateVersion1;
            callbacks.getAscent = ascentCallback;
            callbacks.getDescent = descentCallback;
            callbacks.getWidth = widthCallback;

            CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, (__bridge void *)imgAttr); // img Attr is a Dictionary with all image info

有关此方法的更多详细信息,请查看这篇优秀的文章:http://www.raywenderlich.com/4147/how-to-create-a-simple-magazine-app-with-core-text#

最后,有两个问题:

  • 这是最好的方法吗?我能否在性能良好的情况下完成此操作?我想知道每次滚动时是否需要为每个单元格调用drawRect会不方便 - 是否有任何事情我可以提前做?我已经玩了一段时间,它仍然很卡顿 - 但我还没有尝试在单独的线程中缓存每一行并在cellForRowAtIndexPath中使用它(尽管这可能超过内存使用);

  • 我无法找到获取每个tableview单元格的行高的最佳方法。在NSAttributedString正确处理所有内容后,我如何知道我的drawRect函数使用的高度?

--- 添加更多信息

在我的drawRect创建所有内容(字符串和图像)之后,我正在尝试使用boundingRectWithSize获取正确的高度:

CGRect frame = [self.attString boundingRectWithSize:CGSizeMake(320, 10000) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading | NSStringDrawingUsesDeviceMetrics context:nil];

如果只有文本,这个功能可以很好地工作,但是当我将图像字符添加到内容中时,它无法正确计算高度...

2个回答

3
我今天遇到了同样的问题并解决了它。我正在使用CTRunDelegateCallbacks在文本之间绘制自定义对象,但boundingRectWithSize:options:context:没有给我正确的高度(它忽略了我的自定义对象)。
相反,我在Core Text中找到了一个更低级别的API,满足所有要求并且完全正确。我为这个问题创建了一个类别。使用它时玩得开心!:-) 头文件:
#import <Foundation/Foundation.h>

@interface NSAttributedString (boundsForWidth)

- (CGRect)boundsForWidth:(float)width;

@end

实现文件:

#import "NSAttributedString+boundsForWidth.h"
#import <CoreText/CoreText.h>

@implementation NSAttributedString (boundsForWidth)

- (CGRect)boundsForWidth:(float)width
{
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)self);

    CGSize maxSize = CGSizeMake(width, 0);

    CGSize size = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0), NULL, maxSize, nil);

    CFRelease(framesetter);

    return CGRectMake(0, 0, ceilf(size.width), ceilf(size.height));
}

@end

2

我一旦弄清楚如何计算高度,就会使用时间分析器检查性能。那篇文章很棒,但它没有说明如何计算高度... 我一直在尝试使用:CGRect frame = [self.attString boundingRectWithSize:CGSizeMake(320, 10000) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading | NSStringDrawingUsesDeviceMetrics context:nil]; 但它没有得到正确的值... 它没有考虑我的内联图像。 - cusquinho
我的属性字符串经验有限,但要求它计算其矩形似乎是正确的方法。但如果它给出了错误的答案,除非你能想到一个解决方法,否则这可能是一条死路。你可以把字符串放在UILabel中并调用sizeToFit吗?这是我过去所做的,尽管只是使用普通字符串。 - Timothy Moose
sizeToFit可以工作,但我会失去对不同大小的内联图像的支持...我可能已经找到了一种方法,通过使用CTFrameGetLineOrigins并使用数组中最后一项的y原点。如果它有效,我会在这里发布最终结果..谢谢! - cusquinho

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接