Xamarin.iOS 中的 TextKit

TextKit 是新的 API,可提供強大的文字配置和轉譯功能。 它是建置在低階核心文字架構之上,但比核心文字更容易使用。

若要讓 TextKit 的功能可供標準控件使用,已重新實作數個 iOS 文字控制項以使用 TextKit,包括:

  • UITextView
  • UITextField
  • UILabel

架構

TextKit 提供分層架構,將文字記憶體與配置和顯示區隔開,包括下列類別:

  • NSTextContainer – 提供用來配置文字的座標系統和幾何。
  • NSLayoutManager – 將文字轉換成字元來配置文字。
  • NSTextStorage – 保存文字數據,以及處理批次文字屬性更新。 任何批次更新會交給配置管理員以實際處理變更,例如重新計算版面配置和重新繪製文字。

這三個類別會套用至轉譯文字的檢視。 內建的文字處理檢視,例如 UITextViewUITextFieldUILabel 已經設定這些檢視,但您也可以建立並套用至任何 UIView 實例。

下圖說明此架構:

此圖說明 TextKit 架構

文字 儲存體 和屬性

類別 NSTextStorage 會保存檢視所顯示的文字。 它也會將任何變更傳達給要顯示的版面配置管理員,例如字元或其屬性的變更。 NSTextStorage繼承自MSMutableAttributed字串,允許在和 EndEditing 呼叫之間BeginEditing批次指定文字屬性的變更。

例如,下列代碼段會分別指定前景和背景色彩的變更,以及以特定範圍為目標:

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.CGPath 屬性來套用至 UIBezierPath 實例:

bezierPath.CGPath = exclusionPath;

更新下列程式代碼列會讓文字版面設定在路徑周圍更新:

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

下列螢幕快照說明文字版面配置如何變更,以繞著繪製路徑流動:

此螢幕快照說明文字版面配置如何變更,以在繪製路徑周圍流動

請注意,在此案例中,配置管理員的 AllowsNonContiguousLayout 屬性會設定為 false。 這會導致針對文字變更的所有情況重新計算版面配置。 將此設定為 true 可能會避免完整版面配置重新整理來提升效能,特別是在大型檔時。 不過,將 設定 AllowsNonContiguousLayout 為 true 會防止排除路徑在某些情況下更新版面配置,例如,如果在運行時間輸入文字,但在設定路徑之前沒有尾端歸位字元。