TextKit en Xamarin.iOS

TextKit es una nueva API que ofrece características eficaces de representación y diseño de texto. Se basa en el marco de texto básico de bajo nivel, pero es mucho más fácil de usar que Core Text.

Para que las características de TextKit estén disponibles para los controles estándar, se han vuelto a implementar varios controles de texto de iOS para usar TextKit, entre los que se incluyen:

  • UITextView
  • UITextField
  • UILabel

Architecture

TextKit proporciona una arquitectura superpuesta que separa el almacenamiento de texto del diseño y la presentación, incluidas las siguientes clases:

  • NSTextContainer: proporciona el sistema de coordenadas y la geometría que se usa para diseñar texto.
  • NSLayoutManager: escribe texto convirtiendo texto en glifos.
  • NSTextStorage: contiene los datos de texto, así como controla las actualizaciones de propiedades de texto por lotes. Las actualizaciones por lotes se entregan al administrador de diseño para el procesamiento real de los cambios, como recalcular el diseño y volver a dibujar el texto.

Estas tres clases se aplican a una vista que representa el texto. Las vistas integradas de control de texto, como UITextView, UITextField y UILabel ya las tienen establecidas, pero también puede crearlas y aplicarlas a cualquier instancia de UIView.

En la ilustración siguiente se muestra esta arquitectura:

This figure illustrates the TextKit architecture

Text Storage y atributos

La clase NSTextStorage contiene el texto que muestra una vista. También comunica los cambios realizados en el texto (como los cambios en caracteres o sus atributos) en el administrador de diseño para su visualización. NSTextStorage hereda de la cadena MSMutableAttributed, lo que permite especificar cambios en los atributos de texto en lotes entre llamadas BeginEditing y EndEditing.

Por ejemplo, el siguiente fragmento de código especifica un cambio en los colores de primer plano y de fondo, respectivamente, y tiene como destino intervalos concretos:

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

Después de llamar a EndEditing, los cambios se envían al administrador de diseño, que a su vez realiza los cálculos de representación y diseño necesarios para que el texto se muestre en la vista.

Diseño con ruta de acceso de exclusión

TextKit también admite el diseño y permite escenarios complejos, como texto de varias columnas y texto que fluye alrededor de las rutas de acceso especificadas denominadas rutas de exclusión. Las rutas de exclusión se aplican al contenedor de texto, que modifica la geometría del diseño de texto, lo que hace que el texto fluya alrededor de las rutas especificadas.

Agregar una ruta de exclusión requiere establecer la propiedad ExclusionPaths en el administrador de diseño. Establecer esta propiedad hace que el administrador de diseño invalide el diseño de texto y fluya el texto alrededor de la ruta de exclusión.

Exclusión basada en una ruta de CGPath

Tenga en cuenta la siguiente implementación de subclase 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);
            }
        }
    }
}

Este código agrega compatibilidad para dibujar en la vista de texto mediante Core Graphics. Dado que la clase UITextView ahora se ha creado para usar TextKit para su representación y diseño de texto, puede usar todas las características de TextKit, como establecer rutas de exclusión.

Importante

En este ejemplo, las subclases UITextView para agregar compatibilidad con dibujos táctiles. La subclasificación de UITextView no es necesaria para obtener las características de TextKit.

Después de dibujar el usuario en la vista de texto, el CGPath dibujado se aplica a una instancia de UIBezierPath estableciendo la propiedad UIBezierPath.CGPath:

bezierPath.CGPath = exclusionPath;

Al actualizar la siguiente línea de código, el diseño de texto se actualiza alrededor de la ruta:

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

En la captura de pantalla siguiente se muestra cómo cambia el diseño de texto para fluir alrededor de la ruta dibujada:

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

Tenga en cuenta que la propiedad AllowsNonContiguousLayout del administrador de diseño está establecida en false en este caso. Esto hace que el diseño se vuelva a calcular para todos los casos en los que cambia el texto. Establecer esto en true puede beneficiar el rendimiento evitando una actualización de diseño completo, especialmente en el caso de documentos grandes. Sin embargo, establecer AllowsNonContiguousLayout en true impediría que la ruta de exclusión actualice el diseño en algunas circunstancias; por ejemplo, si el texto se escribe en tiempo de ejecución sin un retorno de carro final antes de establecer la ruta.