IOS UITableView с динамическим текстом и изображениями, отображаемыми вместе (NSAttributedString + images)

Моя проблема заключается в следующем: у меня динамический контент в приложении iOS (например, twits - хотя это не приложение для twitter), которое включает в себя как текст, так и изображения (в основном значки/смайлики и миниатюры). Я хочу сделать как текст, так и изображения вместе в строке таблицы. Основная трудность заключается в том, что каждая строка будет иметь разный размер (сложнее сделать кеширование), и мне нужно рассчитать размер изображения динамически, чтобы подгонять текст вокруг каждого изображения (у меня может быть 20 смайликов один рядом с другим + текст + больше смайлики и т.д.).

Я смотрю вокруг, и я пробовал несколько подходов. Моя первоначальная идея заключалась в использовании UIWebView. Я смог создать пример приложения с одним интерфейсом UIWebView для каждой строки, и даже с некоторыми "умными" NSCache для производительности предварительно обработанных ячеек было не очень хорошо, плюс мне пришлось иметь дело с обратными вызовами JavaScript для UIWebView, чтобы выяснить, когда контент был правильно загружен.

Моя вторая попытка - перезаписать drawRect для нового UITableViewCell. Внутри метода 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 (хотя это может превышать использование памяти);

  • Я не могу найти лучший способ получить высоту строки для каждой ячейки таблицы. Как узнать высоту, используемую моей функцией drawRect после того, как NSAttributedString правильно обработала весь мой контент?

--- Добавление дополнительной информации

После того, как мой drawRect создает все содержимое (строки и изображения), я пытаюсь использовать boundingRectWithSize для получения правильной высоты:

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

Это работает отлично, если только текст, но когда я добавляю глифы изображения в контент, он не правильно вычисляет высоту...

Ответ 2

Я столкнулся с той же проблемой и решил ее сегодня. Я рисую пользовательские объекты между текстом с помощью CTRunDelegateCallbacks, но boundingRectWithSize:options:context: не дал мне правильную высоту. (Он игнорирует мои пользовательские объекты).

Вместо этого я нашел API нижнего уровня в Core Text, который удовлетворяет всем требованиям и является абсолютно правильным. Я создал категорию для этой проблемы. Получайте удовольствие от использования!: -)

Заголовочный файл:

#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