ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
UIKitトレイトシステムの活用
UIKitのトレイトシステムの強化について紹介します。カスタムトレイトを定義して自身のデータをUITraitCollectionに追加する方法、伝播されたデータを変更してコントローラーや、トレイトがオーバーライドされたAPIのビューを表示する方法、APIを導入して柔軟性とパフォーマンスを向上する方法をご紹介します。また、UIKitトレイトとSwiftUI環境キーをつなげて、アプリのUIKitとSwiftUIコンポーネントの両方からシームレスにデータにアクセスする方法も説明します。
関連する章
- 0:57 - Understanding traits
- 7:44 - Defining custom traits
- 14:31 - Applying overrides
- 19:48 - Handling changes
- 25:24 - SwiftUI bridging
リソース
関連ビデオ
WWDC23
-
ダウンロード
♪ ♪
「UIKitトレイトシステムの活用」へ ようこそ 私はUIフレームワークのエンジニア Tyler Foxです 本日はiOS 17でご利用いただける 新しいUIKit機能を ご紹介できることに胸を躍らせています まず UIKitのトレイトシステムの 基礎についておさらいし 次に新しい機能や性能についてご紹介します これにはカスタムトレイトを定義して 自身のデータをUITraitCollectionに 追加する方法 アプリの階層でトレイトオーバーライドを 適用するためのより簡単な方法 トレイトが変更された場合に より柔軟に対処する方法が含まれます 最後は UIKitトレイトを SwiftUI environment keyとつなげ UIKitとSwiftUIコンポーネント間で データをパスする方法をご紹介します では基礎のおさらいから始めましょう トレイトはシステムがすべての ビューコントローラやアプリのビューに 自動的に伝搬するデータの独立した部分です UIKitはさまざまな 内蔵のシステムトレイトを提供します ユーザーインターフェーススタイル 横方向のサイズクラス 優先コンテンツサイズカテゴリなどが そうです iOS 17では自身のカスタムトレイトを 定義することもできます これにより新しい強力な方法で データを アプリのビューコントローラーと ビューに提供できるようになります カスタムトレイトついては 後でじっくり説明します UIKitでトレイトを使用する主な方法は Trait Collectionの使用です Trait Collectionにはトレイトと それに関連する値が含まれています iOS 17の新しいAPIは Trait Collectionの作業を 容易にします まずクロージャを解釈する 新しいinitializerがあります そのクロージャ内には値を設定できる 変更可能なトレイトコンテナを 受け取ります このコンテナはUIMutableTraitsという 名前の新しいプロトコルに従います クロージャ内でユーザーインターフェース イディオムを「phone」に設定し 横方向のサイズクラスを「regular」に 設定します クロージャの実行が完了したら initializerは私がクロージャ内に設定した トレイト値をすべて含む 変更不可能なUITraitCollection インスタンスを返します また 新しいmodifyingTraitsメソッドもあり クロージャ内の元のTrait Collectionからの 値を変更することで 新しいインスタンスを作成できます ここでは横方向のサイズクラスを 「compact」に変更し ユーザーインターフェーススタイルには dark値を生成しました ユーザーインターフェース イディオムは変更していないので 元のTrait Collectionで使用した 「= phone」が使用されます このように独自の Trait Collectionを作成できますが ほとんどの場合は トレイト環境から Trait Collectionを取得します アプリのトレイト環境は ウィンドウシーン ウィンドウ 表示コントローラ ビューコントローラ ビューです これらすべてのトレイト環境には 独自のTrait Collectionがあり それぞれのTrait Collectionには 異なる値が含まれています トレイト環境は アプリを通じてトレイトが流れる トレイト階層でつながっています こちらはトレイト階層のツリー構造の例です 各ウィンドウシーンから 独立したビューコントローラと ビューまでが示されています 各トレイト環境は 親環境からトレイト値を継承します 常に最も具体的なトレイト環境の Trait Collectionを使用してください トレイトがビューコントローラとビューを 通じて流れる方法について説明しましょう
この親ビューコントローラの例には 子ビューコントローラが含まれています 点線はビューコントローラの階層を 表しています 親コントローラはビューを所有しています 直線はこれらの関係を表しています 親ビューにはサブビューが1つあります ビューを通る点線は ビューの階層を表しています 子コントローラのビューは間にある ビューのサブビューです まず iOS 17以前にトレイトが ビューコントローラとビューを 流れていた方法について説明します ビューコントローラはトレイトを親ビュー コントローラから直接継承していました ビューコントローラが所有するビューは トレイトを自己のビューコントローラから トレイトを直接継承していました 最後に ビューコントローラのないビューは トレイトを自己のスーパービューから 直接継承していました この動作は ビュー階層のトレイトの流れが ビューコントローラが所有する 各ビューで止まることを意味します たとえば 親コントローラの ビューからのトレイト値は その直接のサブビューのみにより 継承されます 子コントローラのビューは これらのビューの下のビュー階層の サブビューであるのにその値を受信しません この動作は意外かもしれません iOS 17ではビューコントローラと ビューのトレイト階層を統一することで この問題を排除しました ビューコントローラは 親ビューコントローラからではなく ビューのスーパービューから Trait Collectionを継承するようになりました これによりトレイトは ビューコントローラとビューを通じて シンプルで直線的に流れます ビューコントローラは引き続き親ビュー コントローラからトレイトを継承しますが それぞれの間にあるビューを介して 間接的に行われます ビューコントローラはトレイトを ビュー階層から継承することになったため ビューコントローラのビューは ビューコントローラが最新のトレイトを 受信するために 階層内に存在する必要があります 結果として ビューが階層に追加される前に ビューコントローラのTrait Collectionに アクセスした場合 ビューコントローラのトレイト値は 更新されません 一般的にコードが最も影響を受ける場所は viewWillAppear内で これはビューが階層に追加される前に 常に呼び出されるためです 代わりにviewIsAppearingという 新しいコールバックを使用できます viewIsAppearingは ビューが階層に追加され ビューコントローラとビューの両方の Trait Collectionが更新されたら viewWillAppearの後に呼び出されます viewIsAppearingは 現在viewWillAppearを使用する ほぼすべてのケースに対する完全互換品です このメソッドのすばらしいところは iOS 13まで遡ってデプロイできる点です この新しいコールバックについての詳細と それがビューコントローラの ライフサイクルに適合する方法については 「UIKitの最新情報」をご覧ください iOS 17はビュートレイトアップデートの 一貫性とパフォーマンスも向上します ビューは 階層内にいる場合 自己のTrait Collectionのみを更新します 階層内に存在すると各ビューは レイアウトを実行する直前に 自己のビュートレイトを更新します レイアウト中にトレイトを使用するのが ベストプラクティスです ビューにとって それは layoutSubviewsメソッド内からの traitCollectionを使用するという意味です ビューでsetNeedsLayoutが 呼び出されるたびに layoutSubviewsが再実行されるので 実装が複数回呼び出される場合の 重複した作業を避けるようにしてください カスタムトレイトはiOS 17の 強力な新機能です 皆さんがデータを ビューコントローラとビューに 提供するためのまったく新しい方法を 提供してくれます アプリでデータの作業を行う場合 次の内容を考慮して 新しいカスタムトレイトをいつ定義するかを 決定しましょう トレイトは親ビューコントローラから 複数の子ビューコントローラ またはスーパービューから すべてのサブビューへと データを多くの子に伝播する際の すばらしい選択肢です トレイトを使用して データを直接の接続のない 多くの深いレイヤーのにネストし 他のコンポーネントに パスすることができます トレイトは階層を通じて継承されるため ビューコントローラを含むという 情報の提供など 環境に関するコンテキストを ビューとビューコントローラに提供できます トレイトシステムは強力ですが これを 使用したデータの伝播は無料ではありません 最良のパフォーマンスを実現するために 値を追加する際にトレイトを使用しますが データを直接パスできる場合は トレイトの使用を避けましょう では最初のカスタムトレイトを 定義する準備ができました 私のアプリに「設定」画面があり ビューが「設定」のビューコントローラ内に 含まれているかどうかを示す トレイトを実装するとしましょう コードを数行書くだけで カスタムトレイトを定義できます
最初に 新しい構造体を宣言し UITraitDefinitionプロトコルに従います 必要な1つの静的プロパティ defaultValueを実装します これは値が設定されていない場合の トレイトのデフォルト値です 各トレイトの定義には defaultValueから推測される 関連の値タイプがあります この場合はdefaultValueを falseとして割当てるので このトレイトの値のタイプは Boolと推測されます 以前 SwiftUIでカスタム environment keyを 定義したことがあるなら これに精通しているはずです トレイトを定義したらUITraitCollection およびUIMutableTraitsAPIsの 新しいAPIですぐに使用することができます トレイトを値を取得して設定するための 鍵だと考えればいいでしょう 新しいUITraitCollectionの initializerで UIMutableTraitsの添字演算子を使い トレイトの値を設定します 次にUITraitCollectionの 添字演算子を使用して トレイトの値を復唱します 2つの簡単なExtensionを追加することで すべてのシステムトレイトと同じように 標準プロパティ構文を使用して このトレイトにアクセスできます ここでは変更できない UITraitCollectionクラスのExtensionで 読み取り専用のプロパティを宣言します 次に読み取り/書き込みのプロパティを UIMutableTraitsプロトコルの Extensionで宣言します これらの非常にシンプルな Extensionを追加したところで 標準プロパティ構文を使用して あらゆる場所でトレイトにアクセスできます 独自のカスタムトレイトを定義する際は 必ずこれらのExtensionを書いてください カスタムトレイトに関する 別のアイデアがあります アプリでカスタムのカラーテーマの サポートを構築するとしましょう MyAppThemeという名前の列挙型があり これは私のアプリがサポートする 4つのカラーテーマを表します まず UITraitDefinition プロトコルに従う 新しい構造体を宣言します 標準テーマをこのトレイトの デフォルト値として使用します アプリのカスタム動的カラーで この新しい テーマトレイトを使用するつもりなので このトレイトが色の見えに 影響を与えることを示します システムはこのトレイトが変更すると ビューを自動的に再描画します 色の見えに影響を及ぼすトレイトは より高額なため 不定期に変更するトレイトのみに まばらに使用しましょう トレイトには名称もあり デバッガでトレイトを プリントする場合などに使用します デフォルトでは トレイトタイプ自体の 名称を使用します ここでは「テーマ」のような 短い名称を付けます 最後に identifierの文字列を提供します identifierによりトレイトは エンコードなどの追加の機能の対象となれます リバースDNS形式を使用して 各トレイトのidentifierがアプリで グローバルに固有であることを確実にします
通常のプロパティ構文を使用して このトレイトを設定し取得したいので 先ほどの例で行ったのと同じように UITraitCollectionとUIMutableTraitsを 拡張してプロパティを宣言します カスタムテーマトレイトの 実装の手順は以上です これでこの新しいトレイトの使用を 開始できます たとえば テーマに基づいて外観を変更する カスタム動的カラーの定義方法を 説明しましょう 動的プロバイダinitializerを使用して 新しいUIColorを作成します クロージャ内でパスされる Trait Collectionの テーマを使用してどのカラーが 返されるかを決定します 次に このカスタム背景色を ビューに設定します 定義した際に このトレイトが色の見えに 影響を及ぼすことを示したので このカスタム背景色を使用するビューは テーマが変更された際に 自動的に更新されます トレイトを定義する際に 最も重要な考慮事項は トレイト値に関連する データタイプです 優れたトレイトは シンプルな構造体や列挙型を含む 値タイプを中心に構築されています Swiftのクラスに基づいた トレイトは避けましょう トレイトの最も効率的なデータタイプは Bool Int Double または Intローバリューを使用する 列挙型です 列挙型はトレイトの 最も便利なデータタイプです 効率性を最大化するために Intを列挙型の生データタイプとして 明示的に指定してください トレイト値しとして使用する カスタム構造体データタイプには Equatableプロトコルの 効率的な実装があるべきです システムは頻繁にトレイト値を比較して トレイトがいつ変更されたかを決定するので 等価伝達関数は可能な限り 速くなければいけません
Objective-Cを使用するアプリでも 新しいトレイトシステム機能を ご利用いただけます カスタムトレイトのAPIは SwiftとObjective-Cでは異なります しかし カスタムトレイトを それぞれに定義し 同じ基礎データに方向づけることが できます 詳細と特別な考慮事項については ドキュメンテーションを参照してください カスタムトレイルの定義が完了したら 次は アプリのトレイト階層で その定義向けのデータを生成します
トレイトオーバーライドはトレイト階層内で データを変更するために使用する機構です iOS 17ではトレイトオーバーライドが これまで以上に簡単になりました ウィンドウシーン ウィンドウ ビュー ビューコントローラ 表示コントローラを含む それぞれのトレイト環境クラスに 新しいtraitOverridesプロパティがあります トレイト階層の図に戻ってみると トレイトオーバーライドはこのツリーの あらゆる場所でトレイトの値を変更します この階層でトレイト環境の1つに トレイトオーバーライドを適用すると そのトレイトの値を そのオブジェクトの Trait Collectionと そのすべての子孫で変更します トレイト階層からの 親と子のトレイト環境では トレイトオーバーライドは 次のように両方に影響を与えます 親に適用されたトレイトオーバーライドは 親の独自のTrait Collectionに影響を及ぼします 次に親のTrait Collectionからの値が 子に継承されます 子のトレイトオーバーライドは 子が継承した値に適用され 独自のTrait Collectionを生成します トレイトオーバーライドはオプションの入力 Trait Collectionは出力だと考えてください オーバーライドのないトレイトは すべて親から継承されます トレイトオーバーライドを使用して アプリの特定な部分のカラーテーマを 変更する例をお見せします 右には アプリのトレイト階層の 図があります まだ テーマトレイトの値を生成するために オーバーライドを適用していません そのためすべてのTrait Collectionには 標準テーマの デフォルト値がついています これからトレイトオーバーライドを ウィンドウシーンの 階層のルートに適用します traitOverridesプロパティは UIMutableTraitsプロトコルを活用し トレイト値の設定を容易にします そのため 先程説明したとおり ExtensionをUIMutableTraitsに 使用することで 標準のプロパティ構文でカスタムトレイトの オーバーライド値を設定できます ウィンドウシーン すべてのウィンドウ ビューコントローラ そのウィンドウシーン内の ビューのトレイトオーバーライドで テーマをパステルに設定することで 各自の Trait Collectionでパステル値が継承されます つまり 階層のルートで テーマを1か所に設定することで 伝播されるデフォルト値を その階層で すべてに変更したのです たとえば ウィンドウシーン内の ビューコントローラの Trait Collectionからテーマを読み取り pastelを返すことができます 次に階層の深部にあるビューで traitOverridesプロパティを使用し そのビューとその下にあるすべての テーマを変更できます ここではこのビューにモノクロのテーマの トレイトオーバーライドを設定します このモノクロ値は 自己のサブビューにより継承され 階層上位からパステル値を オーバーライドします トレイトオーバーライドへの変更は Trait Collectionで 即座に反映されない場合があります たとえば ビューはTrait Collectionの 更新をレイアウトの直前に行うため ビューのトレイトオーバーライドの変更は layoutSubviewsが実行される直前まで Trait Collectionに反映されません また traitOverridesプロパティを 使用すると オーバーライドが適用されオーバーライドが 削除されたかどうかを確認できます こちらはオーバーライドのトグリングの例で containsメソッドを使用して オーバーライドが存在することを確認し removeメソッドを使用して オーバーライドを完全に削除します このメソッドが呼び出されるたびに 既存のオーバーライドを削除するか まだ存在しない場合は 新しいテーマオーバーライドを適用します トレイトオーバーライドは 値を設定するための入力方法です トレイト値の読み取りには必ず traitCollectionプロパティを使用します オーバーライドが設定されておらず traitOverridesから読み取る場合は 例外が発生します
トレイトオーバライドを使用する際の パフォーマンスに関する 考慮事項を説明します まず各トレイトオーバーライドには 少額のコストがかかるので 必要な場所のみに トレイトオーバーライドを設定し 使用されないトレイトオーバーライドの 設定は回避しましょう また トレイトオーバーライドを 変更するたびに システムは階層の子孫のTrait Collectionを 更新する必要があります そのためトレイトオーバーライドの 変更回数は最小限に抑えましょう 最後にトレイトオーバーライドは 階層のルート付近で適用され ウィンドウシーンやウィンドウ その下のすべてのものに影響を及ぼします これは非常に便利で ウィンドウシーンやウィンドウに トレイトオーバーライドを適用する ユースケースはたくさんありますが トレイトが階層の深部にある 一部のビューにしか影響しない場合は 代わりにトレイトオーバーライドを 共通スーパービューや ビューコントローラなど これらのビューに 最も近い共通の親ビューに適用します そうすることで そのデータを 使用するのが階層の一部分である場合に トレイトを階層全体に伝播する際に発生する コストを回避することができます トレイトの定義方法と 階層でデータを生成する方法を 把握したところで 値が変更した場合に処理する方法を 理解する必要があります
traitCollectionDidChangeは iOS 17で廃止されました traitCollectionDidChangeを実装する場合 システムは皆さんがどのトレイトに 依存するかを知りません そのため トレイトが値を変更するたびに そのメソッドを呼び出す必要があります しかし ほとんどのクラスは 一握りのトレイトしか使用せず 他への変更に注意を払いません より多くのカスタムトレイトを追加しても traitCollectionDidChangeが スケーリングされないのはこのためです その代わりに より柔軟性が高く パフォーマンスを向上する 新しいトレイト登録APIがあります 特定のトレイトに変更を登録することで システムは皆さんがどのトレイトに 依存するかを把握します 新しいAPIはtarget-actionパターン またはクロージャを使用して コールバックを受信します サブクラスでメソッドを オーバーライドする必要がなくなったため どこからでも簡単に トレイトの変更を 観察できるようになりました traitCollectionDidChangeの 既存の実装を アップデートする方法から説明します こちらは私の既存の実装です 私がupdateViewを呼び出す前に horizontalSizeClassトレイトが 変更されたかどうかを 確認する方法に注目してください このメソッドは この1つのトレイトのみに依存します アプリを古いiOSバージョンに デプロイしているために traitCollectionDidChangeを 継続して使用する必要がある場合 皆さんが依存する特定のトレイトの変更が 実装により 確認することを確実にしてください ではこの実装をiOS 17の 新しいトレイト登録メソッドに置き換えます クロージャベースのメソッドから 始めましょう registerForTraitChangesを呼び出し 登録するトレイトの配列をパスします 横方向のサイズクラス向けの このシンボルのように すべてのシステムトレイトに対して 新しいUltraitシンボルがあります 次に 特定のトレイトが変更された際に 呼び出されるクロージャをパスします クロージャはその他のトレイトへの 変更では呼び出されないため 古いトレイト値と新しいトレイト値を 比較する必要はありません トレイトが変更されたオブジェクトは 最初のパラメータとして クロージャにパスされます そのオブジェクトに対し 弱い参照を キャプチャしなくて済むように このパラメータを使用します selfでトレイトの変更を登録する場合 必ず「self: Self」と書いてください 異なるトレイト環境で トレイトの変更を観察することもできます ここでは2つのトレイトに変更を登録します 横向きのサイズクラスと 先程定義したContainedInSettings カスタムトレイトです クロージャはこの別のビューで 変更が行われた場合に実行されます 登録するビューのタイプを クロージャの最初の パラメータとして書きます
こちらは新しいtarget-actionベースの メソッドの例です registerForTraitChangesを呼び出し 登録するトレイトの配列をパスし 変更を呼び出すターゲットと アクションのメソッドをパスします ターゲットパラメータはオプションです 省く場合 ターゲットは registerForTraitChangesが 呼び出されるのと同じオブジェクトに なります この場合 「self」です クロージャメソッドと同様に その他のトレイト環境で 変更を登録することもできます ここでは他のビューで トレイトの変更を登録していますが handleTraitChangeというselfで メソッドを呼び出す設定にします target-actionを使用して トレイトの変更を登録する場合 アクションメソッドのパラメータを 0 1 2のいずれかにすることができます 最初のパラメータは常に トレイトが変更するオブジェクトとなります このパラメータを使用して 新しいtraitCollectionを取得します 2つ目のパラメータは常に 変更前のオブジェクトの 以前のTrait Collectionとなります 個々のトレイトの登録に加えて 新しいシステムトレイトの セマンティックセットを使用しても登録できます たとえば systemTraitsAffectingColorAppearanceは システム動的カラーの 解像方法に影響を及ぼす システムトレイトを返します また systemTraitsAffectingImageLookupは 「UIImage(named:)」を使用して 画像を読み込む場合に 考慮されるシステムトレイトの サブセットを返します いずれかのセットを直接 registerForTraitChangesにパスして カスタム無効化を実行します
新しいメソッドを使用して トレイトの変更を登録した場合 登録は自動的に整理されます 詳細なユースケースがある場合は それぞれの登録メソッドで返された トークンを使用して手動で 登録を解除できます しかしそういうケースは 非常に稀なので registerForTraitChangesを 呼び出す際は 一般的に 戻り値を無視します 新しいトレイト登録APIを 導入するにあたり 考慮すべきベストプラクティスが 2つあります まず 関係のないトレイトの値が 変化したときに作業を行わないよう 実際に依存するトレイトのみを登録します もう1つは即座にアップデートを行わないで トレイトの変更に対して無効化を 行うようにしましょう たとえば ビューサブクラスの layoutSubviewsメソッド内で トレイトを使用する場合 setNeedsLayoutを 呼び出してトレイトの変更を無効化します これはビューがlayoutSubviewsを 受信するようにスケジュールしますが 即座にアップデートを行いません UIKitのトレイトシステムを使い 自身の データを伝播できるようになったところで アプリ内でUIKitとSwiftUI間で データをシームレスにパスするための 完全に新しい方法が利用可能になります UIKitのカスタムトレイトはSwiftUIの カスタム environment keyと非常によく似ています UIKitとSwiftUIをブリッジすることで 同じデータにアクセスできるようになります UIKit内にSwiftUIコンポーネントを 埋め込む場合でも SwiftUI内にUIKitコンポーネントを 埋め込む場合でも ブリッジされたデータは どちらもシームレスにパスします 同じ基礎データを UIKitコードのトレイトAPIと SwiftUIコードの環境APIを使用して 読み書きすることができます アプリのUIKitコードで定義した 新しいカラーテーマのトレイトを 対応するSwiftUIの environment keyに ブリッジするのは非常に簡単です
UIKitにカスタムトレイト SwiftUIにカスタム environment keyがあり 同じデータを表していることを推定します この2つをブリッジするには UITraitBridgedEnvironmentKey プロトコルに適合性を 追加するだけです これを行うために UIKitのトレイトを読み込み SwiftUIに値を返すための メソッドを1つ実装し SwiftUIの環境値をUIKitトレイトに 書き込むメソッドを1つ実装します これでUIKitトレイトとSwiftUI environment keyは 統一されたストレージにアクセスでき いずれかのフレームワークを使用して 書き込まれたコンポーネントから 同じデータを読み書きできるようになります こちらはブリッジされたトレイトと environment keyを使用する方法の例です アプリのルートでテーマトレイトの トレイトオーバーライドを UIKitウィンドウシーンに適用します これによりモノクロのテーマ値が ウィンドウシーン内に含まれる すべてのものに伝播されます 次にそのウィンドウシーンの ウィンドウの深部に UIKitコレクションビューがあります このコレクションビューには UIHostingConfigurationを使用している セルがあり 各セルにはSwiftUIビューが表示されます SwiftUI CellView内には 「テーマ」という名前のプロパティがあり これは Environmentプロパティ ラッパーを使用して SwiftUI環境の値を読み込みます この環境の値はブリッジされた UIKitのトレイトと一致します 最後にテーマプロパティを使用して このSwiftUIビュー内の テキストの色を制御します SwiftUIはデータ依存性を 自動的に追跡するため UIKitウィンドウシーンのテーマの トレイトオーバライドが 異なる値に変更した場合 SwiftUI セルビューは新しいテーマに反映するように 自動的に更新されます ブリッジは反対の場合でも機能します この例では アプリの設定を表示する SwiftUIビューがあります environment modifierを使用して 標準テーマを設定すると 設定コントローラ内にある すべてのものに適用されます これはUIKitでトレイトオーバライドを 適用するのと概念的に同じです 次に UIViewControllerRepresentable内に 含まれている UIKitベースの設定ビューコントローラで ブリッジされたトレイトの テーマ値を読み込み それを このビューコントローラ向けに 表示されたタイトルの更新に使用します このようにブリッジされたUIKitトレイトと SwiftUI environment keyを使用して シームレスにデータにアクセスできます これらの強力な新機能について 学んだところで トレイトシステムを活用できる場所を アプリ内で見つけて カスタムトレイトを定義することで データを自動的に伝搬してみましょう 次に 新しいtraitOverrides プロパティを導入し トレイト階層にあるデータを簡単に変更して より柔軟なトレイト登録APIで 使用するトレイトで 正確な依存関係を作成します カスタムUIKitトレイトと カスタム SwiftUI environment keyをブリッジして データが両方でシームレスに 移動するのを実現しましょう トレイトのパワーを解き放つのは 皆さん次第です ご視聴ありがとうございます ♪ ♪
-
-
1:51 - Working with trait collections
// Build a new trait collection instance from scratch let myTraits = UITraitCollection { mutableTraits in mutableTraits.userInterfaceIdiom = .phone mutableTraits.horizontalSizeClass = .regular } // Get a new instance by modifying traits of an existing one let otherTraits = myTraits.modifyingTraits { mutableTraits in mutableTraits.horizontalSizeClass = .compact mutableTraits.userInterfaceStyle = .dark }
-
9:06 - Implementing a simple custom trait
struct ContainedInSettingsTrait: UITraitDefinition { static let defaultValue = false } let traitCollection = UITraitCollection { mutableTraits in mutableTraits[ContainedInSettingsTrait.self] = true } let value = traitCollection[ContainedInSettingsTrait.self] // true
-
10:23 - Implementing a simple custom trait with a property
struct ContainedInSettingsTrait: UITraitDefinition { static let defaultValue = false } extension UITraitCollection { var isContainedInSettings: Bool { self[ContainedInSettingsTrait.self] } } extension UIMutableTraits { var isContainedInSettings: Bool { get { self[ContainedInSettingsTrait.self] } set { self[ContainedInSettingsTrait.self] = newValue } } } let traitCollection = UITraitCollection { mutableTraits in mutableTraits.isContainedInSettings = true } let value = traitCollection.isContainedInSettings // true
-
11:00 - Implementing a custom theme trait
enum MyAppTheme: Int { case standard, pastel, bold, monochrome } struct MyAppThemeTrait: UITraitDefinition { static let defaultValue = MyAppTheme.standard static let affectsColorAppearance = true static let name = "Theme" static let identifier = "com.myapp.theme" } extension UITraitCollection { var myAppTheme: MyAppTheme { self[MyAppThemeTrait.self] } } extension UIMutableTraits { var myAppTheme: MyAppTheme { get { self[MyAppThemeTrait.self] } set { self[MyAppThemeTrait.self] = newValue } } }
-
12:33 - Using a custom theme trait
let customBackgroundColor = UIColor { traitCollection in switch traitCollection.myAppTheme { case .standard: return UIColor(named: "StandardBackground")! case .pastel: return UIColor(named: "PastelBackground")! case .bold: return UIColor(named: "BoldBackground")! case .monochrome: return UIColor(named: "MonochromeBackground")! } } let view = UIView() view.backgroundColor = customBackgroundColor
-
18:05 - Managing trait overrides
func toggleThemeOverride(_ overrideTheme: MyAppTheme) { if view.traitOverrides.contains(MyAppThemeTrait.self) { // There's an existing theme override; remove it view.traitOverrides.remove(MyAppThemeTrait.self) } else { // There's no existing theme override; apply one view.traitOverrides.myAppTheme = overrideTheme } }
-
21:00 - Trait change handling on older iOS versions
// Efficient implementation that only updates when necessary override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { if traitCollection.horizontalSizeClass != previousTraitCollection?.horizontalSizeClass { updateViews(sizeClass: traitCollection.horizontalSizeClass) } } func updateViews(sizeClass: UIUserInterfaceSizeClass) { // Update views for the new size class... }
-
21:28 - Registering for trait changes using a closure
// Register for horizontal size class changes on self registerForTraitChanges( [UITraitHorizontalSizeClass.self] ) { (self: Self, previousTraitCollection: UITraitCollection) in self.updateViews(sizeClass: self.traitCollection.horizontalSizeClass) } // Register for changes to multiple traits on another view let anotherView: MyView anotherView.registerForTraitChanges( [UITraitHorizontalSizeClass.self, ContainedInSettingsTrait.self] ) { (view: MyView, previousTraitCollection: UITraitCollection) in // Handle the trait change for this view... }
-
22:48 - Registering for trait changes using a target-action
// Register for horizontal size class changes on self registerForTraitChanges( [UITraitHorizontalSizeClass.self], action: #selector(UIView.setNeedsLayout) ) // Register for changes to multiple traits on another view let anotherView: MyView anotherView.registerForTraitChanges( [UITraitHorizontalSizeClass.self, ContainedInSettingsTrait.self], target: self, action: #selector(handleTraitChange(view:previousTraitCollection:)) ) @objc func handleTraitChange(view: MyView, previousTraitCollection: UITraitCollection) { // Handle the trait change for this view... }
-
24:20 - Registering for changes to system traits affecting color appearance
registerForTraitChanges( UITraitCollection.systemTraitsAffectingColorAppearance, action: #selector(handleColorAppearanceChange) ) @objc func handleColorAppearanceChange() { // Handle the color appearance trait changes... }
-
24:37 - Manually unregistering for trait changes
// Store the returned registration token let registration = registerForTraitChanges([UITraitHorizontalSizeClass.self], action: #selector(handleTraitChange)) // Later, use the stored registration token to manually unregister unregisterForTraitChanges(registration) @objc func handleTraitChange() { // Handle the trait change... }
-
26:19 - Implementing a bridged UIKit trait and SwiftUI environment key
enum MyAppTheme: Int { case standard, pastel, bold, monochrome } // Custom UIKit trait struct MyAppThemeTrait: UITraitDefinition { static let defaultValue = MyAppTheme.standard static let affectsColorAppearance = true } extension UITraitCollection { var myAppTheme: MyAppTheme { self[MyAppThemeTrait.self] } } extension UIMutableTraits { var myAppTheme: MyAppTheme { get { self[MyAppThemeTrait.self] } set { self[MyAppThemeTrait.self] = newValue } } } // Custom SwiftUI environment key struct MyAppThemeKey: EnvironmentKey { static let defaultValue = MyAppTheme.standard } extension EnvironmentValues { var myAppTheme: MyAppTheme { get { self[MyAppThemeKey.self] } set { self[MyAppThemeKey.self] = newValue } } } // Bridge SwiftUI environment key with UIKit trait extension MyAppThemeKey: UITraitBridgedEnvironmentKey { static func read(from traitCollection: UITraitCollection) -> MyAppTheme { traitCollection.myAppTheme } static func write(to mutableTraits: inout UIMutableTraits, value: MyAppTheme) { mutableTraits.myAppTheme = value } }
-
27:01 - Setting a UIKit trait and reading the bridged environment value from SwiftUI
// UIKit trait override applied to the window scene let windowScene: UIWindowScene windowScene.traitOverrides.myAppTheme = .monochrome // Cell in a UICollectionView configured to display a SwiftUI view let cell: UICollectionViewCell cell.contentConfiguration = UIHostingConfiguration { CellView() } // SwiftUI view displayed in the cell, which reads the bridged value from the environment struct CellView: View { @Environment(\.myAppTheme) var theme: MyAppTheme var body: some View { Text("Settings") .foregroundStyle(theme == .monochrome ? .gray : .blue) } }
-
28:16 - Setting a SwiftUI environment value and reading the bridged trait from UIKit
// SwiftUI environment value applied to a UIViewControllerRepresentable struct SettingsView: View { var body: some View { SettingsControllerRepresentable() .environment(\.myAppTheme, .standard) } } final class SettingsControllerRepresentable: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> SettingsViewController { SettingsViewController() } func updateUIViewController(_ uiViewController: SettingsViewController, context: Context) { // Update the view controller... } } // UIKit view controller contained in the SettingsControllerRepresentable class SettingsViewController: UIViewController { override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() title = settingsTitle(for: traitCollection.myAppTheme) } func settingsTitle(for theme: MyAppTheme) -> String { switch theme { case .standard: return "Standard" case .pastel: return "Pastel" case .bold: return "Bold" case .monochrome: return "Monochrome" } } }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。