TextKit в Xamarin.iOS

TextKit — это новый API, который предлагает мощные функции макета текста и отрисовки. Он построен на основе низкоуровневой базовой текстовой платформы, но гораздо проще использовать, чем основной текст.

Чтобы сделать функции TextKit доступными для стандартных элементов управления, несколько элементов управления текстом iOS были повторно реализованы для использования TextKit, в том числе:

  • UITextView
  • UITextField
  • UILabel

Архитектура

TextKit предоставляет многоуровневую архитектуру, которая отделяет текстовое хранилище от макета и отображения, включая следующие классы:

  • NSTextContainer — предоставляет систему координат и геометрию, которая используется для макета текста.
  • NSLayoutManager — выкладывает текст, превратив текст в глифы.
  • NSTextStorage — содержит текстовые данные, а также обрабатывает обновления свойств пакетного текста. Все пакетные обновления передаются диспетчеру макетов для фактической обработки изменений, таких как пересчет макета и перерасчет текста.

Эти три класса применяются к представлению, которое отрисовывает текст. Встроенные представления обработки текста, такие как UITextViewUITextField, и UILabel уже имеют их набор, но их можно создать и применить к любому UIView экземпляру.

На следующем рисунке показана архитектура:

This figure illustrates the TextKit architecture

Текстовые служба хранилища и атрибуты

Класс NSTextStorage содержит текст, отображаемый представлением. Он также сообщает о любых изменениях текста, таких как изменения символов или их атрибутов, в диспетчер макетов для отображения. NSTextStorageнаследует от MSMutableAttributed строки, что позволяет изменять текстовые атрибуты в пакетах между BeginEditing вызовами.EndEditing

Например, следующий фрагмент кода указывает изменение на передний план и цвета фона соответственно и целевые диапазоны.

textView.TextStorage.BeginEditing ();
textView.TextStorage.AddAttribute(UIStringAttributeKey.ForegroundColor, UIColor.Green, new NSRange(200, 400));
textView.TextStorage.AddAttribute(UIStringAttributeKey.BackgroundColor, UIColor.Black, new NSRange(210, 300));
textView.TextStorage.EndEditing ();

После EndEditing вызова изменения отправляются в диспетчер макетов, который, в свою очередь, выполняет все необходимые вычисления макета и отрисовки для отображения текста в представлении.

Макет с путем исключения

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

Для добавления пути исключения требуется задать ExclusionPaths свойство в диспетчере макетов. Задание этого свойства приводит к тому, что диспетчер макетов отменяет макет текста и перемещает текст вокруг пути исключения.

Исключение на основе CGPath

Рассмотрим следующую UITextView реализацию подкласса:

public class ExclusionPathView : UITextView
{
    CGPath exclusionPath;
    CGPoint initialPoint;
    CGPoint latestPoint;
    UIBezierPath bezierPath;

    public ExclusionPathView (string text)
    {
        Text = text;
        ContentInset = new UIEdgeInsets (20, 0, 0, 0);
        BackgroundColor = UIColor.White;
        exclusionPath = new CGPath ();
        bezierPath = UIBezierPath.Create ();

        LayoutManager.AllowsNonContiguousLayout = false;
    }

    public override void TouchesBegan (NSSet touches, UIEvent evt)
    {
        base.TouchesBegan (touches, evt);

        var touch = touches.AnyObject as UITouch;

        if (touch != null) {
            initialPoint = touch.LocationInView (this);
        }
    }

    public override void TouchesMoved (NSSet touches, UIEvent evt)
    {
        base.TouchesMoved (touches, evt);

        UITouch touch = touches.AnyObject as UITouch;

        if (touch != null) {
            latestPoint = touch.LocationInView (this);
            SetNeedsDisplay ();
        }
    }

    public override void TouchesEnded (NSSet touches, UIEvent evt)
    {
        base.TouchesEnded (touches, evt);

        bezierPath.CGPath = exclusionPath;
        TextContainer.ExclusionPaths = new UIBezierPath[] { bezierPath };
    }

    public override void Draw (CGRect rect)
    {
        base.Draw (rect);

        if (!initialPoint.IsEmpty) {

            using (var g = UIGraphics.GetCurrentContext ()) {

                g.SetLineWidth (4);
                UIColor.Blue.SetStroke ();

                if (exclusionPath.IsEmpty) {
                    exclusionPath.AddLines (new CGPoint[] { initialPoint, latestPoint });
                } else {
                    exclusionPath.AddLineToPoint (latestPoint);
                }

                g.AddPath (exclusionPath);
                g.DrawPath (CGPathDrawingMode.Stroke);
            }
        }
    }
}

Этот код добавляет поддержку рисования в текстовом представлении с помощью основной графики. UITextView Так как класс теперь создан для использования TextKit для отрисовки текста и макета, он может использовать все функции TextKit, такие как настройка путей исключения.

Внимание

В этом примере подклассы для добавления поддержки UITextView сенсорного рисования. Подклассы UITextView не необходимы для получения функций TextKit.

После того как пользователь рисует текстовое представление, он CGPath применяется к UIBezierPath экземпляру, задав UIBezierPath.CGPath свойство:

bezierPath.CGPath = exclusionPath;

Обновление следующей строки кода приводит к обновлению текстового макета по пути:

TextContainer.ExclusionPaths = new UIBezierPath[] { bezierPath };

На следующем снимке экрана показано, как изменяется макет текста для обхода нарисованного пути:

This screenshot illustrates how the text layout changes to flow around the drawn path

Обратите внимание, что свойство диспетчера макетов AllowsNonContiguousLayout имеет значение false в этом случае. Это приводит к перерасчету макета во всех случаях, когда текст изменяется. Если задать значение true, это может повысить производительность, избегая полного обновления макета, особенно в случае больших документов. Однако если задано AllowsNonContiguousLayout значение true, то путь исключения не будет обновлять макет в некоторых случаях, например если текст вводится во время выполнения без конечного возврата каретки до заданного пути.