Ответ 1
В приложении, над которым я работаю, приложение вытягивает ужасные строки HTML из паршивого API, написанного другими людьми, и преобразует строки HTML в объекты NSAttributedString
. У меня нет выбора, кроме как использовать этот паршивый API. Очень грустный. Любой, кто должен разбирать ужасную строку HTML, знает мою боль. Я использую Text Kit
. Вот как:
- parse html string, чтобы получить объект DOM. Я использую libxml со световой оболочкой, hpple. Эта комбинация очень проста и проста в использовании. Настоятельно рекомендуется.
- рекурсивно перемещайте объект DOM для создания объекта
NSAttributedString
, используйте специальный атрибут для маркировки ссылок, используйтеNSTextAttachment
для пометки изображений. Я называю это богатым текстом. - создавать или повторно использовать первичные объекты
Text Kit
. т.е.NSLayoutManager
,NSTextStorage
,NSTextContainer
. Подключите их после выделения. Процесс компоновки -
- Передайте богатый текст, построенный на шаге 2, на объект
NSTextStorage
на шаге 3. с помощью[NSTextStorage setAttributedString:]
- использовать метод
[NSLayoutManager ensureLayoutForTextContainer:]
, чтобы заставить макет произойти.
- Передайте богатый текст, построенный на шаге 2, на объект
- вычислить кадр, необходимый для рисования богатого текста с помощью метода
[NSLayoutManager usedRectForTextContainer:]
. При необходимости добавьте отступ или маржу. - процесс рендеринга
- вернуть высоту, вычисленную на шаге 5, в
[tableView: heightForRowAtIndexPath:]
- нарисуйте богатый текст на шаге 2 с помощью
[NSLayoutManager drawGlyphsForGlyphRange:atPoint:]
. Я использую внеэкранную технику рисования здесь, поэтому результатом является объектUIImage
. - используйте
UIImageView
для рендеринга конечного изображения. Или передать объект изображения результата в свойствоcontents
свойстваlayer
свойстваcontentView
объектаUITableViewCell
в[tableView:cellForRowAtIndexPath:]
.
- вернуть высоту, вычисленную на шаге 5, в
- обработка событий
- захватить событие касания. Я использую признак распознавания жесткого диска, прикрепленный к представлению таблицы.
- получить местоположение события касания. Используйте это местоположение, чтобы проверить, что пользователь нажал ссылку или изображение с помощью
[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
. Таким образом, это не предназначено для ячеек просмотра переменной высоты таблицы.
EDIT:
Если вы должны использовать NSDocumentTypeDocumentAttribute
, я думаю, вам нужно выяснить, как происходит процесс компоновки. Возможно, вы можете установить некоторые точки останова, чтобы увидеть, какой объект отвечает за процесс компоновки. Тогда, возможно, вы можете запросить этот объект или использовать другой подход для имитации процесса компоновки, чтобы получить информацию о макете. Некоторые люди используют ad-hoc-ячейку или объект UITextView
для вычисления высоты, которая, по-моему, не является хорошим решением. Потому что таким образом, приложение должно составлять один и тот же фрагмент текста, по крайней мере, дважды. Знаете ли вы или нет, где-то в вашем приложении, какой-то объект должен разложить текст так, чтобы вы могли получить информацию о макете как ограничивающий прямоугольник. Поскольку вы упомянули класс NSAttributedString
, лучшим решением является Text Kit
после iOS 7. Или Core Text
, если ваше приложение нацелено на более раннюю версию iOS.
Я настоятельно рекомендую Text Kit
, потому что таким образом, для каждой строки html, выведенной из API, процесс компоновки происходит только один раз, а информация о макете, такая как ограничивающий прямоугольник и позиции каждого глифа, кэшируется объектом NSLayoutManager
. Пока объекты Text Kit
сохраняются, вы всегда можете их повторно использовать. Это чрезвычайно эффективно при использовании представления таблицы для визуализации текста произвольной длины, поскольку текст выкладывается только один раз и рисуется каждый раз, когда ячейка необходима для отображения. Я также рекомендую использовать Text Kit
без UITextView
, как предполагалось в официальных документах Apple. Потому что нужно кэшировать каждый UITextView
, если он хочет повторно использовать объекты Text Kit
, прикрепленные к этому UITextView
. Прикрепите объекты Text Kit
для моделирования таких объектов, как я, и обновляем NSTextStorage
и принудительно NSLayoutManager
для компоновки, когда из HTML вытаскивается новая строка html. Если число строк табличного представления фиксировано, можно также использовать фиксированный список объектов модели-заполнителя, чтобы избежать повторного распределения и конфигурации. И поскольку drawRect:
заставляет Core Animation
создавать бесполезные растровые изображения, которые следует избегать, не используйте UIView
и drawRect:
. Либо используйте технику рисования CALayer
, либо нарисуйте текст в растровом контексте. Я использую последний подход, потому что это можно сделать в фоновом потоке с GCD
, поэтому основной поток может реагировать на работу пользователя. Результат в моем приложении действительно удовлетворительный, он быстрый, набор верстки хорош, прокрутка табличного представления очень плавная (60 кадров в секунду), поскольку весь процесс рисования выполняется в фоновом потоке с помощью GCD
. Каждое приложение должно нарисовать текст с табличным представлением, используя Text Kit
.