ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
TextKitおよびテキストビューの最新情報
UIフレームワークのTextKitやテキストビューの最新情報をご覧ください。レイアウトの改良やAPIの強化について解説し、複数のOSバージョン間の互換性を維持する方法や、TextKit 2でAppを最新化する方法を紹介します。 このセッションを最大限に活用するには、WWDC21の「TextKit 2について」を最初にご覧ください。
リソース
関連ビデオ
WWDC22
WWDC21
-
ダウンロード
TextKitとテキストビューの 新機能へようこそ TextKitエンジニアの Donna Tomです
iOS 15とmacOS Montereyでは 強力なテキストエンジンである TextKit 2を導入し 大きく向上したのは 正確性と安全性です
TextKit 2のビューポートベース レイアウトアーキテクチャは コンテンツの多い文書に対して 高性能のレイアウトを提供します
TextKit 2はグリフ操作から 複雑さを取り除くことにより 海外ユーザーにより良い テキスト体験を提供します OpenTypeなどの 最新フォントテクノロジーも 完全にサポートしています
テキストレイアウトを 制御するために オブジェクト操作で レイアウトを簡単に カスタマイズでき 少ないコードで クールに作成できます
今後TextKit 2のエンジンは Appleの全プラットフォームでの 基盤を形成します
将来のパフォーマンスの強化 更新また改善はすべて TextKit 2エンジンに 割り当てられます アップデートすることで あなたのAppは これらの改善のメリットを 享受できます TextKit 2の詳細については 「TextKit 2について」の動画を ご覧ください そちらの動画では独自の― レイアウト構築方法と 基本事項について説明しています この動画では 最新情報をカバーし テキストビュー(複数)を最大限に活用 する方法を説明します そう テキストビューが 複数なのは iOS 16とmacOS Venturaの UIKitとAppKitの全テキスト 制御において TextKit 2が使用されているからです システム全体でTextKit 2が 活躍しているのです AppをすぐにでもTextKit 2へ 移行することが重要なので 移行のためのツールも 追加されました 多くのAppではコードを書くことなく 移行が可能です テキストビューに特別な 変更のないAppにもこれは 該当します これについては後で もう少し説明します
まずはTextKit 2の 新機能について 先ほど触れたツールについても 説明します
続いてテキストビューの TextKit 1との― 互換モードについて詳しく説明します
最後にTextKit 2への移行準備の際 使用できる 戦略についても説明します
まずTextKit 2の新機能についてです
TextKit 2は iOS 15で初めてUIKitに登場し UITextFieldが アップグレードされました iOS16では UIKitのTextKit 2への 移行が完了し UITextViewを含む デフォルトで全てのコントロールで適用 されます ほぼ自動的にTextKit 2に オプトインされるので デベロッパ側での変更は 不要です テキストビューが適用 されない状況は 動画の互換性部分で 説明します
AppKitについても同様です macOS Big SurでAppKitに 登場したTextKit 2は macOS Montereyでは NSTextFieldをデフォルトに オプトインすることで 利用できました
macOS Venturaでは TextKit 2がデフォルトになりました ほとんどのNSTextViewは TextKit 2に自動的にオプトインし デベロッパ側での変更は 不要です
TextEditもmacOS Venturaで TextKit 2を フルに活用します macOS Big Sur以降プレーンテキストでは TextKit 2を使用します macOS Venturaではリッチ テキストモードでも使用されます
TextKit 2は新しいスタンダードのため UITextViewとNSTextViewに 便利なコンストラクターが追加されました これらのコンストラクターを 使用して初期化時の テキストエンジンを 選択します
TextKit 2によるビューを 作成するには 新しいコンストラクターを 使用して レイアウトパラメータに “true”を渡します 互換性が必要な場合は “false”を渡します
またInterfaceBuilderに よる新しいレイアウトも これを使用してレイアウト システムを インスタンスごとに 制御できます システムデフォルトは TextKit 2です
新旧どちらかを明示的に 使用することもできます
TextKit 2はテキスト コンテナもサポートします 穴やギャップがある コンテナの場合があります この場合テキストを画像や その他のコンテンツにラップできます
複雑なテキストコンテナを 作成するにはNSTextContainerで exclusivePaths プロパティを使用して テキストを配置しない 領域を定義します 例については動画の 関連リソースから TextKitAndTextViewサンプルコードを 確認してください 関連する例は「exclusion path」 タブにあります
TextKit 2の 改行エンジンを拡張して 両端揃えの段落に対して より均等な改行が可能になりました 微妙な変更ですが 長い段落でより明らかになります
これは 同じ領域に配置された テキストですが
従来の改行による 単語間の間隔に 着目してみてください
改行がはるかに少ない ことが お分かりいただけると 思います 読みやすくなった テキストは TextKit 2で無料で 入手できます 変更は必要ありません
最後に全プラットフォームで TextKit 2のリストをサポートします テキストリストを使用すると 表示のための 箇条書きリストまたは番号リストを プログラムで作成できます TextKit 2はNSTextListで テキストリストを表示します NSTextListは以前は AppKitのみでした iOS 16ではUIKitでも 利用可能です
NSTextListをNSmutable ParagraphStyleと使用して ストレージ内の段落を リストとしてフォーマットします テキストビューはストレージから これらの属性を取得し 段落の内容をリストとして 再フォーマットします
NSTextList自体は 新しいものではありません リストにはネストされた アイテムを 含められるのでツリー構造で 表示するのが自然です TextKit 2では子要素と 親要素にアクセスするための プロパティを持つツリーとしての 構造化をサポートしています
NSTextListElementという 新しいサブクラス要素が追加されました コンテンツマネージャが NSTextListを検知すると リスト内のアイテムを 生成します
リストの作成方法と アイテムの追加方法は TextKitAndTextViewサンプルコードを 参照してください 関連する例はタブに あります
サンプルコードでは 添付ファイル例も お見逃しなく TextKit 2でのプロバイダーAPIの 使用方法が紹介されています
これらのAPIでUIまたはNSViewを テキスト添付として使用し イベントを添付ビューで直接 処理できます これによりイベント処理が 簡単になりました TextKit 2ならではです これでTextKit 2の 新機能は以上です 次に互換モードの 詳細についてです TextKit 2は大きく 進化しているため 採用には時間がかかる 可能性もあるでしょう 旧版に特化したAppの 場合は特にそうかもしれません 移行が完了までAppが正常に 機能することがが望まれますので 特別なTextKit 1互換モードを UITextViewと NSTextViewに追加しました NSLayoutManagerの APIを 明示的に呼び出すと テキストビューが置換され TextKit 1を使用する よう再構成します これはテーブルなどの TextKit 2で 未サポート属性の検出や 印刷時にも発生しかねません
UITextViewのTextKit 1で予期しない ランタイムフォールバック発生時には ログでスイッチに関する 警告をご確認ください シンボルアンダースコアに ブレークポイントを設定し スタックトレースやデバッグ 情報をキャプチャします
NSTextViewに関してはwillSwitch またはdidSwitchToNSLayoutManagerに 登録することで予期しない フォールバック情報や通知などを 受け取るすることが可能です
TextKit 1に戻る必要がある場合は 初期化時にオプトアウトしたうえで 初期化された テキストビューを推奨します これには独自のテキストコンテナと TextKit 1レイアウトマネージャを使用します
別のオプションとしては 新しいコンストラクターによる TextKit 1ビューの初期化でパラメータ としてfalseを渡すことです これによりTextKit 1が 使用されるようになります
3番目のオプションは InterfaceBuilderを使って レイアウトオプションを TextKit 1に設定することです
注意すべきことが一点あります 初期化中や直後に テキストコンテナの― レイアウトマネージャを置換すると テキストビューは旧版に フォールバックします 初期化中にTextKit 2 オブジェクトを作成して 後で破棄するのは 非効率的です タイミングによっては 誤動作もあります 入力中に発生した場合 テキストビューの中断された 入力を再開するにはテキストビューの 選択が再度必要です 初期化時にオプトアウトして これを回避しましょう 互換モードに関する情報を紹介しました 続いてはAppを最新にアップデートし 回避する方法としての TextKit 2の採用についてお話します 覚えておいてほしい 重要な点が1つあります
テキストビューごとのレイアウト マネジャーは1つのみ採用できます NSTextLayoutManagerと NSLayoutManagerの両方を 同時に含めることは できません
TextKit 1に切り替わると 自動的には戻せる方法はありません レイアウトシステムの切り替えは コストがかかるうえ 切り替え時にあった UI状態はすべて 破棄されてしまいます 最適なパフォーマンスと 使いやすさのために テキストビューがTextKit 2に 戻ることはありません 一方向の操作です
これは互換モードの回避が 非常に重要だということです また互換モードに入る 理由もいくつかあります テキストビューが 互換モードに入る 最大の理由はlayoutManager プロパティへのアクセスです 他の理由は一般的ではありません
つまりテキストビューの レイアウトプロパティに アクセスしないように することが重要です テキストコンテナからレイアウト マネージャへのアクセスは避けてください これらプロパティの使用に 際しコードを監査し 削除するか同等の TextKit 2に置換します
また TextKit 2未搭載の 旧版OSにAppを デプロイしてもlayoutManagerコードを 削除できない可能性があります
その場合まずNSTextLayout Managerを確認します
TextKit 2コードをif句に 格納し 旧版コードをelse句に 格納し layoutManager アクセスも含めます このようにTextKit 2が使用 できない場合のみ実行され layoutManagerクエリによる TextKit 1へのフォールバックを防ぎます
これらすべてを実行 してもまだTextKit 1への 予期しないフォールバックが 発生する場合 フィードバックアシスタントで 報告してください フォールバック時に スタックトレースの キャプチャに含めるのが UIKitでは互換モード AppKitではwillSwitch になります
ではTextKit 1に関連する コードの更新の 詳細について説明します NSLayoutManagerクエリのコードを 監査したら NSLayoutManagerに 相当するTextKit 2を 精査する必要があります
一部のレイアウトマネジャー APIはTextKitの新旧間で 類似した名称のため 置換は簡単です いくつかの例をあげます 旧版ではNSLayoutManagerで usedRect(for:textContainer)を 呼び出して コンテナ内のテキストの 境界矩形を取得します TextKit 2ではNSTextLayoutManagerの usageBoundsForTextContainer プロパティからこれを取得します
TextKit 1では レンダリング属性の場合 「temporary attributes」という 名称で処理しました TextKit 2では 「rendering attributes」と呼びます
TextKit 1のAPIには TextKit 2に直接該当するものが ないものもあります その理由を理解するには グリフマッピング用のインド文字 カンナダ語などの例があります これらのスクリプトでは グリフを分割 並べ替えや再結合に 削除なども可能です
NSLayoutManagerの グリフベースのAPIで連続した 範囲の文字を 関連付けられると仮定しますが グリフはすべての スクリプトに該当しません これらAPIを使用すると レンダリングが崩れるのが カンナダ文字など では起こり得ます そのためTextKit 2には グリフAPIがありません 単一APIをTextKit 1 グリフAPIに置換できないので これらAPIの置換には 別アプローチが必要です
グリフベースのコードの 更新方法は次のとおりです まずグリフAPIを特定する ことから始めます 次にこれらAPIの使用法を 確認し その目的をハイレベルで 定義します グリフベースのコードの 詳細の多くは ハイレベルのタスクには 無関係です
ハイレベルのタスクを 定義したら 次にレイアウトや行に テキスト選択など TextKit 2で使用 できる構造を調べます これでタスク管理は万全です たとえばこのTextKit 1コード ここに2つの グリフAPIがあります numberOfGlyphsと lineFragmentRectです このTextKit 1コードは全グリフを 反復処理し 線の断片を処理します ハイレベルのタスクは 折り返されたテキストの 行数をカウントすることです このコードは 行フラグメントで機能するので 対応するTextKit 2構造は NSTextLineFragmentと NSTextLayoutFragmentです TextKit 2用に 書き直されたコードがこちらです グリフを反復処理する 代わりに ドキュメント内のレイアウト フラグメントを列挙し 各フラグメント内のすべての 行フラグメントを カウントするクロージャを 提供します
TextKit 2用にコードを更新するときは この例を念頭に置いてください 次にNSRangeに基づく コードの更新についてです
TextKit 1ではNSRangeを使って コンテンツをインデックス化します NSRangeは文字列への 線形インデックスです 「HelloTextKit 2!」 というテキストの場合 感嘆符を表すNSRangeは 6番目の文字から始まり 長さが10文字なので 位置が6で長さ10として 定義されます この線形モデルは 理解しやすく 文字列へのインデックス 作成に最適です
ただし線形モデルは 文字列よりも多くの 構造を持つコンテンツ では機能しません こちらが例です HTMLドキュメントは ツリー構造として表され 各タグはツリー内のノードです Hello TextKit 2!の場合 テキストはHTML ドキュメントの一部であり NSRangeがネストされた spanタグ内にあることを― 通知する方法はありません 線形モデルはその情報を 格納する表現がないため ネストされた構造にインデックス 付けるために使用できません これが今回テキストコンテンツ範囲を 表す新タイプを追加した理由です NSTextLocationは テキストコンテンツ内の 単一の位置を表す オブジェクトです NSTextRangeは 開始位置と終了位置で構成されます 終了位置は範囲から除外されます これらの新しいタイプは HTMLのネスト構造を表し 位置をDOMノードと 文字オフセットとして定義します
NSTextLocationはプロトコルであるため 任意のカスタムオブジェクトは NSTextLocationプロトコル メソッドを実装している限り定義可能です バッキングストア操作 の重要なインフラで モデルで構造化 データをサポートします
ただしテキストビューはこの構造を持たない NSAttributedStringストアに 基づいて構築されているため Appを壊すことなく 変更を加えることは できません したがってselectedRangeや scrollRangeToVisibleなどの テキストビューAPIを使用する場合は 引き続きNSRangeを使用します TextKit 2レイアウトやコンテンツ マネジャーと通信するには NSRangeとNSTextRange 間で変換する必要があります
NSRangeをNSTextRangeに 変換するには 位置を属性付き文字列の 整数インデックスとして定義します
NSTextRangeの開始位置 としてNSRangeの位置を使用し
NSRangeの位置と長さを NSTextRangeの終了位置として使用します 概念的にはこれがNSRangeから NSTextRangeにマップする方法です
実際にはコードが少し 異なって見えるのは NSTextLocationsは オブジェクトである必要があるためです
位置の計算はコンテンツ マネジャー経由で行います
開始位置については コンテンツマネジャーを参照し ドキュメントの先頭 位置について確認し 次にNSRangeの 位置でオフセットします 次に開始位置をNSRangeの 長さでオフセットして終了位置を取得します
反対方向に進むには コンテンツマネジャーを使用し 2つの異なるオフセットを 取得します
NSRangeの位置は ドキュメントの先頭と NSTextRangeの位置 からのオフセットです またNSRangeの長さは NSTextRangeの開始位置と 終了位置のオフセットです
UITextViewsとUITextFields はInputプロトコルに準拠し UITextPositionと 範囲を使用します ほとんどの場合 変換の必要はなく UITextRangeを NSTextRangeに直接― UITextViewまたは UITextFieldを使用できます ただしその場合は 2つのタイプの中間として 整数オフセットを使用 してください
UITextInputで カスタムビューを使用する場合は UITextPositionや UITextRangeサブクラスを ビューで使用して 直接制御できます UITextPosition サブクラスを作成し NSTextLocationに準拠し 必要なメソッドを実装し サブクラスを使用して NSTextRangesを直接作成します
ビューのコンテンツが類似している 場合でも 異なるビュー間でUITextPosition オブジェクトの再利用は不可です UITextPosition作成に 使用されたビューに対してのみ有効です
これでコードを最新化するための戦略を 利用できるようになりました これら戦略を適用すると AppはTextKit 2の メリットを享受できるようになります
TextKitとテキストビューの新機能でした 多くの改善点を取り上げ 古いバージョンとの互換性を 維持しながらAppを更新するための 戦略をいくつか共有しました 改善点を最大活用するには 今すぐTextKit 2の テキストビューをチェックして 意図せずに 旧版にフォールバックして いないことを確認します
モダナイゼーション戦略を 採用して あなたはどんなAppを 作成するのでしょうか ご覧いただきありがとうございます
-
-
13:21 - Check for NSTextLayoutManager first
if let textLayoutManager = textView.textLayoutManager { // TextKit 2 code goes here } else { let layoutManager = textView.layoutManager // TextKit 1 code goes here }
-
17:41 - Counting number of lines of wrapped text in a text view with TextKit 2
// 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 }
-
21:10 - Convert NSRange to 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)
-
21:40 - Convert NSTextRange to 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)
-
22:02 - Convert UITextRange to NSTextRange
let offset = textView.offset(from: textview.beginningOfDocument, to: uiTextRange.start) let startLocation = textContentManager.location(textContentManager.documentRange.location, offsetBy: offset)! let nsTextRange = NSTextRange(location: startLocation)
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。