TextKit no Xamarin.iOS

O TextKit é uma nova API que oferece recursos poderosos de layout e renderização de texto. Ele é construído sobre a estrutura de baixo nível Core Text, mas é muito mais fácil de usar do que o Core Text.

Para tornar os recursos do TextKit disponíveis para controles padrão, vários controles de texto do iOS foram reimplementados para usar o TextKit, incluindo:

  • UITextView
  • UITextField
  • UILabel

Arquitetura

O TextKit fornece uma arquitetura em camadas que separa o armazenamento de texto do layout e da exibição, incluindo as seguintes classes:

  • NSTextContainer – Fornece o sistema de coordenadas e geometria que é usado para layout de texto.
  • NSLayoutManager – Estabelece texto transformando texto em glifos.
  • NSTextStorage – Mantém os dados de texto, bem como manipula atualizações de propriedades de texto em lote. Todas as atualizações em lote são entregues ao gerenciador de layout para o processamento real das alterações, como recalcular o layout e redesenhar o texto.

Essas três classes são aplicadas a um modo de exibição que renderiza texto. Os modos de exibição internos de manipulação de texto, como UITextView, UITextFielde UILabel já os têm definidos, mas você também pode criá-los e aplicá-los a qualquer UIView instância.

A figura a seguir ilustra essa arquitetura:

Esta figura ilustra a arquitetura do TextKit

Armazenamento de texto e atributos

A NSTextStorage classe contém o texto que é exibido por um modo de exibição. Ele também comunica quaisquer alterações no texto - como alterações em caracteres ou seus atributos - ao gerenciador de layout para exibição. NSTextStorage herda da cadeia de caracteres, permitindo que as alterações nos atributos de MSMutableAttributed texto sejam especificadas em lotes entre BeginEditing e EndEditing chamadas.

Por exemplo, o trecho de código a seguir especifica uma alteração nas cores de primeiro plano e plano de fundo, respectivamente, e destina-se a intervalos específicos:

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 ();

Depois EndEditing de chamado, as alterações são enviadas para o gerenciador de layout, que por sua vez executa todos os cálculos de layout e renderização necessários para que o texto seja exibido na exibição.

Layout com caminho de exclusão

O TextKit também oferece suporte a layout e permite cenários complexos, como texto de várias colunas e texto fluindo em caminhos especificados chamados caminhos de exclusão. Os caminhos de exclusão são aplicados ao contêiner de texto, que modifica a geometria do layout de texto, fazendo com que o texto flua em torno dos caminhos especificados.

A adição de um caminho de exclusão requer a configuração da ExclusionPaths propriedade no gerenciador de layout. A definição dessa propriedade faz com que o gerenciador de layout invalide o layout de texto e flua o texto ao redor do caminho de exclusão.

Exclusão com base em um CGPath

Considere a seguinte UITextView implementação de subclasse:

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);
            }
        }
    }
}

Esse código adiciona suporte para desenho no modo de exibição de texto usando elementos gráficos principais. Como a classe agora foi criada para usar o UITextView TextKit para sua renderização e layout de texto, ela pode usar todos os recursos do TextKit, como definir caminhos de exclusão.

Importante

Este exemplo subclasses UITextView para adicionar suporte a desenho por toque. A subclassificação UITextView não é necessária para obter os recursos do TextKit.

Depois que o usuário desenha no modo de exibição de texto, o desenho CGPath é aplicado a uma UIBezierPath instância definindo a UIBezierPath.CGPath propriedade:

bezierPath.CGPath = exclusionPath;

A atualização da seguinte linha de código faz com que o layout de texto seja atualizado ao redor do caminho:

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

A captura de tela a seguir ilustra como o layout do texto muda para fluir ao redor do caminho desenhado:

Esta captura de tela ilustra como o layout do texto muda para fluir ao redor do caminho desenhado

Observe que a propriedade do gerenciador de AllowsNonContiguousLayout layout é definida como false nesse caso. Isso faz com que o layout seja recalculado para todos os casos em que o texto é alterado. Definir isso como true pode beneficiar o desempenho, evitando uma atualização de layout completo, especialmente no caso de documentos grandes. No entanto, definir AllowsNonContiguousLayout como true impediria que o caminho de exclusão atualizasse o layout em algumas circunstâncias - por exemplo, se o texto for inserido em tempo de execução sem um retorno de carro à direita antes do caminho ser definido.