AttributedString について知りたいと思ったら、TextEditor に関する内容だった。新しい TextEditor を使う機会が現状ないのと、装飾文字列を駆使する機会も今のところないが、文字列処理におけるスライスという言葉を知れて良かった。
0:00 – Introduction
AttributedStringを使ったリッチテキスト編集体験の構築TextEditorをAttributedString対応にアップグレードし、カスタムコントロールを構築、独自のテキスト書式定義を作成
1:15 – TextEditor and AttributedString
TextEditorのAttributedString対応:TextEditor(text:)に渡す@Binding var textの型をString→AttributedStringに変更するだけで大幅に機能強化- 太字、斜体、下線、取り消し線、カスタムフォント、文字サイズ、前景色・背景色、カーニング、トラッキング、ベースラインオフセット、Genmoji をサポート
- 加えて段落スタイリング(Line height / Text alignment / Writing direction)を新たにサポート
- ダークモードと Dynamic Type に対応
- システム UI による書式設定のトグル機能
AttributedStringの基本:- 文字列と属性の実行を含むシーケンス(属性実行)を格納
- 属性実行の例:
.largeTitle,.largtTitle+.orange
- 属性実行の例:
- Swift の値型で UTF-8 エンコーディング使用
Equatable,Hashable,Codable,Sendableに準拠- カスタム属性、属性スコープの定義も可能
- 文字列と属性の実行を含むシーケンス(属性実行)を格納
5:36 – Build custom controls
- サンプルに追加機能の実装:
- 選択したテキストを材料リストに追加するボタンを作成
PreferenceKeyを使用してビュー階層での値の伝達TextEdiorは$selection: AttributedTextSelectionを介して選択内容を伝達- 単独の
Rangeではなく、RangeSetを使用した複数範囲の選択をサポート(双方向テキスト対応)- 複数言語 LtoR RtoL が混在し、跨いで選択した場合は複数の選択範囲として扱われるため
- 双方向テキストの対応:
- 英語(左から右)とヘブライ語(右から左)のような混在テキスト
- 視覚的な選択が複数の範囲に分割される場合に対応
RangeSetによる不連続部分文字列のスライス機能- 例:
.indices(where:\.isUppercase)により、すべての大文字を検出 text[uppercaseRanges].foreground = .blueで、大文字だけ青文字にすることが可能
- 例:
- カスタム属性の作成:
IngredientAttributeによるテキスト範囲を材料としてマークAttributedString.Indexはテキスト内のひとつの場所を表す- パフォーマンスのため、
AttributedStringではコンテンツがツリー構造で保持、インデックスによりツリー内のパスが格納(16:25〜で図示) - このインデックスの動作により予想外のカーソル移動が発生してしまう
- パフォーマンスのため、
AttributedStringindices の注意点:- 変更により全てのインデックスが無効化
- 作成元の
AttributedStringでのみ使用可能 - インデックスが無効になったことを検出した SwiftUI は、クラッシュ回避のためカーソルを末尾に移動する
AttributedStringは テキストのUTF-8スカラ、UTF-16スカラへのビューが新たに得られるようになった。e.g.text.utf16[index]- テキスト変更時にインデックスと選択を更新
- 範囲や範囲の配列を受け取る
transform関数による安全なインデックス更新 e.g.text.transform(updating: &cookingRange) { text in ... }
- 範囲や範囲の配列を受け取る
- まとめ
for range in rangesのようなRangeのループはやめるRangeSetを使った、rangesにより一括でスタイル変更をする(スライス)- カーソル位置をテキスト変換と常に追従させるために、
transform(updating:)を使用
22:02 – Define your text format
AttributedTextFormattingDefinitionプロトコル:TextEditorが応答するAttributedStringKeysの定義- カスタムスコープでの属性制限(前景色、Genmoji、カスタム材料属性のみ許可)
.attributedTextFormattingDeinition(definition:)修飾子でカスタム定義をTextEditorに渡すAttributedStringKeysがスコープに含まれないものは、システム書式設定 UI に表示されない
AttributedTextValueConstraintプロトコル:AttributedTextValueConstriant.constrain(_:)で属性値の制約ロジックを実装- 例:材料属性がある場合は緑色、それ以外はデフォルト色
TextEditorによる自動的な妥当性検証- ↑ の変更よりシステム書式設定 UI のカラーコントロールも無効になる
- ペースト時もカスタム属性が維持される
- カスタム属性の詳細制御:
inheritedByAddedText:追加したテキストが属性継承をしなくなるinvalidationConditions:属性を削除する条件を定義(テキスト変更時など)runBoundaries:属性の実行境界を制約(段落境界など)
34:08 – Next steps
- サンプルプロジェクトの提供:
- SwiftUI の Transferable Wrapper によるドラッグ&ドロップや RTFD エクスポート
- Swift Data を使用した AttributedString の永続化
AttributedStringのオープンソース:- Swift の Foundation プロジェクトの一部
- GitHub での実装確認と貢献、Swift フォーラムでのコミュニティ交流
- Genmoji サポート:
- 新しい
TextEditorにより Genmoji 入力サポートが簡単に追加可能
- 新しい
- 開発のヒント:
- カスタム属性とフォーマット定義を組み合わせた高度なテキスト編集体験の構築