WWDC Quick Look 💓 By SwiftGGTeam
What's new in TextKit and text views

What's new in TextKit and text views

元の動画を見る

ハイライト

TextKit 2 は、iOS 16 および macOS Ventura の UIKit および AppKit テキスト コントロールのデフォルトのレイアウト エンジンになり、UITextView と NSTextView は、自動移行パス、TextKit 1 互換モード、およびグリフ/NSRange コードから NSTextLayoutManager/NSTextRange に移行する戦略を取得します。


主要内容

多くのアプリケーションはテキスト エンジン コードを直接記述せず、常に依存します。UITextViewNSTextViewUILabelこのようなシステム制御。WWDC 2022 での変更点: これらのコントロールの背後にあるデフォルトのテキスト レイアウト エンジンは TextKit 2 に切り替わります (01:56)。

通常のアプリケーションではこれは非常に心配ありません。テキスト ビューに特別な変更を加えないほとんどのアプリケーションは、コードなしで TextKit 2 を取得できます。Apple、iOS 16でUIKitの移行を完了、UITextViewTextKit 2 がデフォルトで使用されます。 macOS Ventura では、AppKit のテキスト コントロールもデフォルトで TextKit 2 を使用します (03:08)。

テキストを詳細にカスタマイズするアプリケーションでは問題が発生します。古いコードが頻繁にアクセスされるlayoutManager次に、グリフ API を使用して行、四角形、または選択範囲をカウントします。TextKit 2 はグリフ API を提供しなくなりました。これは、一部のテキスト システムでは文字からグリフへの継続的なマッピングが機能しないためです (15:33)。このタイプのコードは、「グリフの操作」から「レイアウトの断片、行の断片、およびテキスト範囲の操作」に変更する必要があります。

Apple は 2 つの移行パスを提示しました。短期的には、UITextViewそしてNSTextViewTextKit 1 互換モードに入ることで、既存のアプリケーションが引き続き動作することを確認できます (09:03)。長期的には、アプリケーションは偶発的なロールバックを避ける必要があります。これは、テキスト ビューが TextKit 2 から TextKit 1 に切り替わると、システムは自動的に切り替わらないためです (11:54)。

詳細

テキスト コントロールはデフォルトで TextKit 2 を使用します

(03:08) TextKit 2 が iOS 15 で最初に UIKit に参入UITextField。iOS 16 以降、UIKit のすべてのテキスト コントロールはデフォルトで TextKit 2 を使用します。UITextView。また、AppKit により、macOS Ventura ではテキスト コントロールがデフォルトで TextKit 2 を使用するようになります。

エンジンを明示的に選択する必要がある場合は、初期化中に指定できます。usingTextLayoutManager。これは 2 つのシナリオに適しています。新しいコードでは TextKit 2 の使用が強制されます。古い実装と互換性がある必要があるテキスト ビューは、作成時に TextKit 1 を直接選択します。

let textView = UITextView(usingTextLayoutManager: true)
let compatibilityTextView = UITextView(usingTextLayoutManager: false)

キーポイント:

  • UITextView(usingTextLayoutManager: true): TextKit 2 を使用してテキスト ビューを作成します。
  • UITextView(usingTextLayoutManager: false): TextKit 1 を使用してテキスト ビューを作成します。
  • 初期化中にエンジンを選択する方が、作成後にレイアウト マネージャーを置き換えるより安全です。講演では、ランタイムの置き換えによりフォーカスの喪失や入力の中断が発生する可能性があると述べられていました (11:03)。

まず NSTextLayoutManager を確認してから、layoutManager にアクセスします

(13:20) 誤って互換モードに入ってしまう最も一般的な理由は、テキスト ビューにアクセスすることです。layoutManager財産。システム間バージョン コードで TextKit 1 ブランチを直接削除できない場合、Apple は最初に確認することを推奨しますtextLayoutManager

if let textLayoutManager = textView.textLayoutManager {
    // TextKit 2 code goes here
}
else {
    let layoutManager = textView.layoutManager    
    // TextKit 1 code goes here
}

キーポイント:

  • textView.textLayoutManager: TextKit 2 が利用可能な場合に返されます。NSTextLayoutManager
  • if let textLayoutManager: 最初に古いレイアウト マネージャーにアクセスしないように、TextKit 2 コードをこのブランチに配置します。
  • else: TextKit 2 が利用できない場合にのみ、古いパスに入ります。
  • textView.layoutManager: このアクセスにより TextKit 1 互換モードがトリガーされるため、TextKit 1 ブランチに配置する必要があります。

グリフトラバーサルの代わりにレイアウトフラグメントを使用する

(16:24) TextKit 1 のグリフ API は非常に低レベルです。講義ではテキストビューで改行後の行数をカウントする例を説明しました。古い考え方では、グリフを走査してからチェックしていました。lineFragmentRect(forGlyphAt:)。TextKit 2 の背後にある考え方は列挙ですNSTextLayoutFragmentを数えてから、NSTextLineFragment

// Example: Updating glyph-based code 

var numberOfLines = 0
let textLayoutManager = textView.textLayoutManager

textLayoutManager.enumerateTextLayoutFragments(from:
                                               textLayoutManager.documentRange.location,
                                               options: [.ensuresLayout]) { layoutFragment in
        numberOfLines += layoutFragment.textLineFragments.count
}

キーポイント:

  • numberOfLines:統計結果を保存します。
  • textView.textLayoutManager: TextKit 2 レイアウト マネージャーを入手します。
  • enumerateTextLayoutFragments(from:options:): ドキュメントの先頭から始めてレイアウトの断片を列挙します。
  • textLayoutManager.documentRange.location: 列挙の開始位置。
  • .ensuresLayout: 列挙する前に、必要なレイアウトが生成されていることを確認してください。
  • layoutFragment.textLineFragments.count: 各レイアウト フラグメント内の行フラグメントの数。累算後、行折り返し後の総行数が得られます。

NSRange と NSTextRange 間の変換

18:09)TextKit 1 常用NSRangeインデックス文字列。TextKit 2の導入NSTextLocationそしてNSTextRange、より構造化されたテキスト コンテンツを表すために使用されます。テキストビューにはまだありますselectedRangescrollRangeToVisibleこのタイプNSRangeAPI なので、コードを移行するには変換が必要になることがよくあります。

からNSRangeに行くNSTextRange

let textContentManager = textLayoutManager.textContentManager

let startLocation = textContentManager.location(textContentManager.documentRange.location, 
                                                offsetBy: nsRange.location)!

let endLocation = textContentManager.location(startLocation, 
                                              offsetBy: nsRange.length)

let nsTextRange = NSTextRange(location: startLocation, end: endLocation)

キーポイント:

  • textLayoutManager.textContentManager: 位置の変換は、コンテンツ マネージャーを通じて行う必要があります。
  • documentRange.location: オブジェクトを文書の先頭に配置します。
  • offsetBy: nsRange.location: 文書の先頭から次へ移動します。NSRange出発点。
  • offsetBy: nsRange.length: 開始点から次の地点に移動します。NSRange終点。
  • NSTextRange(location:end:): 開始点と終了点を含む TextKit 2 範囲を構築します。

からNSTextRange転送して戻すNSRange

let textContentManager = textLayoutManager.textContentManager

let location = textContentManager.offset(from: textContentManager.documentRange.location,
                                         to: nsTextRange!.location)

let length = textContentManager.offset(from: nsTextRange!.location,
                                       to: nsTextRange!.endLocation)

let nsRange = NSRange(location: location, length: length)

キーポイント:

  • offset(from:to:): 2つ数えますNSTextLocation間の線形オフセット。
  • location: 文書の先頭からNSTextRange開始点からのオフセット。
  • lengthNSTextRange始点から終点までのオフセット。
  • NSRange(location:length:): 2 つの整数をテキスト ビュー API で使用できる範囲に再構築します。

UITextRange はオフセットを介して転送されます

22:01UITextViewそしてUITextField従うUITextInput、使用しますUITextPositionそしてUITextRange。ほとんどの場合、直接変換する必要はありません。NSTextRange。本当に必要な場合、Apple は中間層として整数オフセットを使用することを推奨します。

let offset = textView.offset(from: textview.beginningOfDocument, to: uiTextRange.start)

let startLocation = textContentManager.location(textContentManager.documentRange.location, 
                                                offsetBy: offset)!

let nsTextRange = NSTextRange(location: startLocation)

キーポイント:

  • textView.offset(from:to:):バンドルUITextRange.startドキュメントの先頭を基準とした整数オフセットに変換します。
  • textContentManager.documentRange.location: TextKit 2 ドキュメントの開始点。
  • location(_:offsetBy:):整数オフセットを次のように変換しますNSTextLocation
  • NSTextRange(location:): この位置から始まる TextKit 2 範囲を作成します。
  • スピーチリマインダー、UITextPositionそれを作成したビューに対してのみ有効であり、ビュー間で再利用しないでください (22:51)。

重要ポイント

  • TextKit 2 移行チェック ページを作成します: すべてのUITextViewそしてNSTextView作成方法は、まずTextKit 2を直接利用できるインスタンスを次のように変更します。usingTextLayoutManager: true。これにより、予期しない TextKit 1 フォールバックを早期に検出できます。エントリーポイントはtextView.textLayoutManagerおよび互換モードのログ。

  • リッチ テキスト エディターに行数を追加: グリフベースの行数を列挙に変更します。NSTextLayoutFragment。これにより、文字からグリフへのマッピングの問題を回避でき、TextKit 2 のレイアウト モデルと一致します。エントリーはenumerateTextLayoutFragments(from:options:)

  • 範囲変換ツールを作成します:NSRangeNSTextRangeUITextRange変換は小さなモジュールに集中されます。エディターでの選択、スクロール、および注釈の強調表示にはすべて範囲変換が必要です。入り口はNSTextContentManager.location(_:offsetBy:)そしてoffset(from:to:)

  • グラフィックとテキストが混在したテキスト カードを作成: TextKit 2 は、単純でないテキスト コンテナーをサポートしています。NSTextContainer.exclusionPathsテキストを画像領域から遠ざけてください。ニュース、メモ、長いテキスト リーダーの埋め込み画像レイアウトに適しています。

  • インライン添付ファイルをインタラクティブに作成: TextKit 2 のテキスト添付ビュー プロバイダー API を使用すると、UIViewまたはNSViewテキスト添付ファイルとして送信し、イベントを直接処理します。インライン投票、タスクチェックボックス、コードブロックアクションボタンなどの小さな機能から始めます。

関連セッション

  • TextKit 2 について - TextKit 2 の基礎となるアーキテクチャについて説明します。移行戦略を読む前に基本概念を理解するのに適しています。
  • UIKit の新機能 — iOS 16 をカバーする UIKit アップデート。UITextViewデフォルトの移行を一緒に見てみましょう。
  • AppKit の新機能 — macOS Ventura をカバーする AppKit アップデートNSTextViewデフォルトの移行を一緒に見てみましょう。
  • PDFKit の新機能 - PDFKit はドキュメント テキスト、フォーム、注釈を処理し、TextKit のテキスト レイアウト更新と組み合わせて読むのに適しています。
  • デスクトップクラスの編集インタラクションを採用する — テキスト ビューの選択、検索、および編集エクスペリエンスに直接関連する編集メニューと検索インタラクションについて説明します。

コメント

GitHub Issues · utterances