Разделить атрибутивную строку текстового представления на (референтные) разделы - PullRequest
9 голосов
/ 20 марта 2019

Я пытаюсь реализовать очень простой текстовый редактор, который должен работать как с NSTextView на macOS, так и с UITextView на iOS.В этом текстовом редакторе есть кнопка на панели инструментов " Разрыв раздела ", которая вставляет новый раздел в текущую позицию курсора при каждом нажатии.Раздел должен быть:

  1. визуально идентифицируемым как раздел (путем добавления визуальных разделителей между двумя последующими разделами и, возможно, добавлением некоторого вертикального пробела),

  2. Referable .Пользователь должен иметь возможность видеть список всех разделов в редакторе, и, щелкнув элемент в этом списке, текстовое представление должно сразу перейти к началу этого раздела.

В еще один вопрос Я спросил, как решить первую проблему, и, к сожалению, я пока не нашел ответа на этот вопрос (хотя является решением, которое работает на macOSтолько ).

Однако этот вопрос касается аспекта second : Как сохранить список всех разделов в моем текстовом представлении, где каждый раздел содержит точную ссылку на связанный текст?

Сложность этой задачи заключается в том, что пользователь может копировать и вставлятьили просто отредактируйте любую часть текста в любое время.Таким образом, я не могу сохранить простой массив номеров абзацев или что-то в этом роде.

Screenshot of several sections in a text view


Что я пробовал и почему это кажетсябыть такой сложной задачей:

  1. Одна идея, которая у меня возникла, заключалась в создании подкласса NSTextStorage и использовании массива изменяемых атрибутных строк в качестве внутреннего хранилища.Затем я бы использовал специальный подкласс NSTextAttachment и использовал бы его как индикатор разрыва раздела внутри моего хранилища текста.Проблема в том, что текстовое представление вызывает только следующие методы, когда пользователь редактирует текст :

    func replaceCharacters(in range: NSRange, 
                           with str: String)
    

    и

    func setAttributes(_ attrs: [NSAttributedString.Key : Any]?, 
                         range: NSRange)
    

    Первый метод - толькоПередав простую строку без каких-либо атрибутов, второй метод получает только атрибуты.Это означает, что в первом методе я не могу сказать, должен ли символ замены фактически быть разрывом раздела, или нет, поэтому я не могу решить, где создать новый элемент в моем внутреннем массиве хранения текста.

    Во втором методе я не знаю, действительно ли пользователь добавил новый текст (в этом случае мне нужно было бы добавить новый элемент в моем массиве хранения текста для каждого атрибута разрыва раздела) или пользователь просто изменил некоторые атрибутысуществующего текста (в этом случае новые элементы массива уже были созданы ранее).

  2. Я также рассмотрел идею использования нескольких текстовых представлений внутри табличного представления или стекового представления.Однако это не сработает, потому что это не даст пользователю выбирать (и удалять) текст в нескольких последующих разделах.

  3. Наконец, я попытался создать подкласс NSLayoutManager, нодокументация на это действительно тонкая, и мне кажется, что это не то место.(В конце концов, ответственность за компоновку текста лежит на менеджере верстки, а не на отслеживании его структуры.)

Есть идеи?

Ответы [ 3 ]

2 голосов
/ 29 марта 2019

Как мне сохранить список всех разделов в моем текстовом представлении, где каждый раздел содержит точную ссылку на связанный текст?

Начните с решения, что такое секция . Интуитивно это кажется блоком смежного текста, некоторые из которых связаны вместе, образуя законченный документ. Но попробуйте более подробно: все ли тексты в разделе имеют одинаковый стиль? Те же наценки? Вам действительно нужно отслеживать разделы вообще, или это будет работать так же просто, чтобы отслеживать разрывы разделов?

Далее, поскольку вы уже решили купить TextKit , используя NSTextView и UITextView, и поскольку у вас есть потребности, которые выходят за рамки наиболее распространенного "представления с некоторым редактируемым текстом в Это «сценарий использования для этих классов, вам нужно больше узнать о том, как базовые классы сочетаются друг с другом. Основными классами являются NSTextStorage, NSTextContainer, NSLayoutManager и, конечно, NSTextView (или UITextView).

Понимание того, как работают классы TextKit и какие возможности они вам предоставляют, поможет вам понять, как реализовать концепцию «раздела». Вот несколько примеров того, как вы можете пойти:

  • Создание собственного текстового представления. Большая часть работы, выполняемой текстовым представлением, действительно выполняется другими классами: контейнером текста, менеджером макета и хранилищем текста. Вы можете создать свое собственное представление, которое содержит серию текстовых контейнеров, так что каждый раздел представлен отдельным контейнером, каждый со своим собственным менеджером макета и объектами хранения. Это, очевидно, самая большая работа, но она дает большой контроль над тем, как отображаются разделы. Кроме того, если вы построите свой вид для работы на обеих платформах, вам не придется беспокоиться о различиях между UITextView и NSTextView.

  • Пусть текстовое представление выполняет всю работу. Облегченная реализация разделов может включать настройку делегата для текстового представления или объекта хранения текста. Оба из этих методов делегата вызова при редактировании происходят, поэтому, если ваш делегат поддерживает список индексов или диапазонов, которые представляют разделы, он может использовать эти методы для обновления данных границ раздела. Вам все равно придется выяснить, как визуально показать границы сечений, но это может быть так просто, как вставка изображения на каждой границе сечения.

2 голосов
/ 29 марта 2019

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

Ваше первое решение подкласса NSTextStorage хорошо, но оноподвергать хранилище большому количеству нежелательной логики, я предлагаю просто иметь свой (NS|UI)TextViewDelegate для реализации func textDidChange(_ notification: Notification), и при изменении текста вы можете использовать хранилище текста для перечисления ваших вложений и сохранения списка диапазонов для последующего использования.:

func textDidChange(_ notification: Notification)
{
    self.textView.textStorage?.enumerateAttribute(NSAttributedString.Key.attachment, in: NSMakeRange(0, self.textView.textStorage!.length), options: [], using: {
        (value:Any?, range:NSRange, stop:UnsafeMutablePointer<ObjCBool>) in
        // If the value is one of your attachment save the range in a list
    })
}

Таким образом, не имеет значения, что делает пользователь (запись, копирование / вставка и т. Д.), У вас всегда будет список допустимых диапазонов, содержащих ваш разделитель:)

Это всего лишь пример, ваша логика для перестройки списка может быть более точной, но я надеюсь, что это поможет вам начать.

0 голосов
/ 24 марта 2019

Я бы играл с NSRange.Это может не относиться к вашему подходу, но может использоваться как предложение.

Ссылаясь на ваши основные моменты.

Визуально идентифицируется как раздел (путем добавления визуальных разделителей между двумя последующимиразделы и, возможно, добавление некоторых вертикальных пробелов) Referable. Пользователь должен иметь возможность видеть список всех разделов в редакторе, и, щелкнув элемент в этом списке, текстовое представление должно сразу перейти к началуэтот раздел.

Кнопки UIB могут быть добавлены в виде разделов внутри textView.Ширина кнопки должна быть шириной вашей строки внутри textView.

Плюсы:

  • Виды или кнопки внутри textView отслеживаются, и его ось y может быть обновлена, если текст вставлен в любой раздел.
  • При выборе кнопки можно отследить смещение содержимого textView и прокрутить раздел до целевого местоположения.
  • Диапазон между двумя кнопками даст вам диапазон соответствующего текста внутри раздела.Аналогично массив текстовых разделов.
...