Создание текстового редактора для iOS 7
Проблема
Мне нужно понять, как работает TextKit и как я могу использовать его для создания текстового редактора. Мне нужно выяснить, как рисовать ТОЛЬКО видимый текст, с которым взаимодействует конечный пользователь, или определить, как я буду лениво применять атрибуты к видимому тексту только без применения атрибутов ко всему измененному диапазону текста в методе processEditing.
Фон
iOS 7 вышел с TextKit. У меня есть токенизатор и код, который полностью реализует TextKit (см. Проект Apple TextKitDemo - ссылка приведена ниже)... и он работает. Однако он ДЕЙСТВИТЕЛЬНО медленный. Когда текст анализируется, NSTextStorage требует, чтобы вы раскрашивали диапазон ENTRE отредактированного текста в методе processEditing в том же потоке. Разгрузка работы в поток не помогает. Это просто слишком медленно. Я дошел до того, что я могу повторно атрибутировать только измененные диапазоны, но если диапазон слишком велик, процесс будет медленным. В некоторых условиях весь документ может быть признан недействительным после внесения изменений.
Вот некоторые идеи, которые у меня есть. Пожалуйста, дайте мне знать, будет ли кто-нибудь из них работать или, возможно, подтолкнет меня в правильном направлении.
1) Несколько NSTextContainers
Чтение документов показывает, что я могу добавить несколько NSTextContainers в NSLayoutManager. Я предполагаю, что, делая это, я должен уметь определять не только количество строк, которые могут быть нарисованы в NSTextContainer, но также я должен знать, какой NSTextContainer виден конечным пользователям. Я знаю, что если я пойду по этому маршруту, мне нужно будет потратить много времени, чтобы убедиться, насколько это возможно. Первоначальное тестирование предполагает, что вам нужен только один NSTextContainer. Поэтому мне пришлось бы подклассифицировать NSLayout или создать оболочку, в которой диспетчер компоновки определяет, какой текст переходит в текстовый контейнер. Тьфу. Кроме того, я не знаю, как TextKit позволяет мне знать, что пришло время рисовать конкретный NSTextContainer... возможно, это не так, как это работает!
2) Недействительные диапазоны с NSLayoutManager
Недействительность layoutManager с использованием invalidateLayoutForCharacterRange: actualCharacterRange:. Но что это на самом деле делает и как он выгрузит фазу атрибуции текста? Когда это позволяет мне знать, что конкретный текст нужно выделить? Кроме того, я вижу, что NSLayoutManager будет лениво рисовать глифы... как? когда? Как это мне помогает? Как я могу использовать этот вызов, чтобы привязать строку поддержки до того, как она на самом деле выведет текст?
3) Перегрузка NSLayoutManager drawGlyphsForGlyphRange: atPoint: метод.
Я действительно не хочу этого делать. При этом в Mac OS X NSAttributedStrings имеет эту концепцию временных атрибутов, где информация стиля используется только для представления. Это ускоряет процесс выделения БОЛЬШОГО! Проблема в том, что она отсутствует в инфраструктуре iOS 7 TextKit (или она есть, и я просто не знаю об этом). Я верю, что перевернув этот метод, он даст мне тот же тип скоростей, который вы получите с помощью временных атрибутов... поскольку я мог бы ответить на все вопросы макета, цвета и форматирования в этом методе, не затрагивая при этом атрибут NSTextStorage строка. Единственная проблема заключается в том, что я не знаю, как этот метод работает в отношении других методов, предоставляемых в классе NSLayoutManager. Сохраняет ли оно ширину и высоту? Изменяет ли размер NSTextContainer, когда он слишком мал? Кроме того, он только рисует глифы для символов, которые были добавлены в текстовый буфер. Он не перерисовывает весь экран. только крошечная его часть... и это прекрасно. У меня есть некоторые идеи о том, как я могу работать с этим... но у меня действительно нет желания компоновать глифы. Это слишком много работы, и я не нашел хорошего примера, который делает это.
Я был бы очень признателен за любую помощь, которую вы можете предложить.
В качестве благодарности я перечисляю все фреймворки и ссылки, которые я использовал в течение последних нескольких лет, которые помогли мне добраться туда, где я сейчас нахожусь в надежде, что они вам полезны.
Рамки выделения синтаксиса:
Ресурсы
Большинство из этих структур одинаковы. Они либо не учитывают переключение контекста (или вам нужно написать оболочку для предоставления контекста) для диапазонов, либо они не восстанавливают диапазоны контекста, поскольку пользователь изменяет текст (например, строки, многострочные комментарии и т.д.). Последнее требование ОЧЕНЬ важно. Потому что, если токенизатор не может определить, на какие диапазоны влияет изменение, вам придется снова проанализировать и приписать всю строку. Единственным исключением из этого является Crimson Editor. Проблема с этим токенизатором заключается в том, что он не сохраняет состояние во время токенизации. Во время рисования алгоритм использует токены для определения состояния рисования. Он начинается с верхней части документа, пока он не попадет в видимый диапазон текста. Излишне говорить, что я оптимизировал это, кэшируя состояние документа в определенных частях документа.
Другая проблема заключается в том, что фреймворки не соответствуют тому же шаблону MVC, который Apple делает - чего и следовало ожидать. В инфраструктурах, в которых есть полный рабочий редактор, используются крючки, предоставляемые API-интерфейсом, на котором они построены (т.е. GTK, Windows и т.д.), Который предоставляет им информацию о том, где и когда рисовать, в какую часть экрана. В моем случае TextKit, по-видимому, требует от вас атрибута всего измененного диапазона в processEditing.
Возможно, мои наблюдения ошибочны. (Надеюсь, они!) Может быть, например, ParseKit будет работать для того, что мне нужно, и я просто не понимаю, как его использовать. Если это так, пожалуйста, дайте мне знать! И еще раз спасибо!
Ответы
Ответ 1
Я понял это. Я не использовал ни одного из приведенных выше предложений. Это, как говорится, производительность, которую я получаю сейчас, просто невероятна. Имейте в виду, что YMMV. То, как вы метафицируете токенизацию и кеширование своей строки, может отличаться от меня. Тем не менее, я смог набрать 1400 строк PHP файла, и для любого его изменения потребовалось всего 0,015 секунды. Просто невероятно.
Вот такой подход:
Мой UIViewController является делегатом UITextViewDelegate и UIScrollViewDelegate.
Когда вызывается UITextViewDelegate.textViewDidChange: Я определяю, какой диапазон текста в настоящее время отображается конечному пользователю. Я сделал это, используя мой существующий подклассифицированный UITextView и добавив к нему этот метод:
- (NSRange)visibleRangeOfText
{
CGRect bounds = self.bounds;
UITextPosition *start = [self characterRangeAtPoint:bounds.origin].start;
UITextPosition *end = [self characterRangeAtPoint:CGPointMake(CGRectGetMaxX(bounds), CGRectGetMaxY(bounds))].end;
return NSMakeRange([self offsetFromPosition:self.beginningOfDocument toPosition:start],
[self offsetFromPosition:start toPosition:end]);
}
После этого я передаю диапазон подклассам объекта NSTextStorage, где он затем выполнит волшебство, чтобы определить, какие строки нужно выделить.
То же самое касается вызовов метода UIScollViewDelegate. В зависимости от того, какая часть просматриваемого представления просматривается, я передаю видимый диапазон моему подклассовому вызову NSTextStorage и определяет, были ли строки уже отнесены и т.д.
Я понимаю, что я много читаю. Я в конечном итоге использовал то, что у меня было в настоящее время, и немного изменил его для работы с вышеупомянутой реализацией.
Я хотел поделиться некоторыми из моих открытий, которые я нашел интересными при этом:
1) Если вы попытаетесь выделить любой текст ВЫШЕ текущей строки, где находится курсор, вы можете увидеть, что курсор "перескакивает" вверх по виду, а затем возвращается к исходной позиции, где он был первоначально. Я почти уверен, что это вызвано вызовом метода NSTextStorage.processEditing. Я смог получить его там, где система выделяет только строку, которая была изменена... так что эта проблема ушла сейчас.
2) Изначально я сделал это, чтобы предотвратить перемещение курсора:
NSRange selectedRange = [textView selectedTextRange];
[textView setScrollEnabled:NO];
NSRange visibleRange = [textView visibleRangeOfText];
[textStorage applyAttributesToRange:visibleRange];
[textView setScrollEnabled:YES];
Это сработало... но вызов [textView setScrollEnabled: NO] сделал МАССИВНЫЙ удар по производительности. Для этой команды потребовалось почти 3/4 секунды, чтобы закончить файл 1400 строк. Я не уверен, что заставляет его быть медленным, но я думал, что стоит упомянуть.