忙しい人向けの Structure your app for SwiftUI previews - #WWDC20
忙しい人向けシリーズの第20弾。
冒頭
Xcode プレビューは、SwiftUI のコードを書いたり、一度に複数のプレビューをしたり、コードの変更を素早くテストするのに役立つ。
しかし、アプリを構造化してプレビュー可能にすることで、より理解しやすく、よりテストしやすく、より保守しやすいものになり、アプリ全体に利益をもたらすものとなる。
このセッションでは、アプリを構造化してプレビューを最大限に活用するための4つの方法を紹介していく。
Previewing multiple files
最初は複数のファイルを一度にプレビューして、より大きなコンテキストで編集する方法。
このセッションを通して使用するサンプルアプリは、写真のコラージュをするもの。
あるプレビューを表示させたまま、特定のファイルを編集したい場合がある。そうした際は、プレビューエリア左下の 📌 をクリックしてピン留めしてから別のファイルを開くと・・・
以下のように2つのプレビューが同時に表示できる。
なお、ダークモードの確認もしたくなったら+ボタンで複製してから、インスペクタの Color Scheme から Dark を選択すればよい。
また、ここでは Asset カタログに事前に登録しておいた色に変更したいが、それは Library の Color タブから選択できる。
ピン留めは Asset カタログの編集時などにも活用できる。
ピン留めのもう一つの活用方法として、開発中にずっと必要なプレビューを専用のファイルとして用意し、それをピン留めしてずっと表示させておくというアイディアもある。
ここまでのピン留めの活用方法をまとめると。
・より大きなコンテキスト内での編集
・Swiftファイル以外の編集
・特定のタスクのための追加プレビュー
App life cycle
次に iOS 14 / iPadOS 14 から導入されたライフサイクルについて。
アプリの起動時には、コストのかかる初期化処理をすることが多い。SwiftUI のプレビューではこのような高価な処理をしたくない。
サンプルアプリでは、ネットワーク用のモデルをプロパティとして保持しており、アプリ起動時に初期化されるようになっている。プレビューを実行した時のアプリの動作は Debug Preview を実行することで確認できる。これはブレークポイントを設定してデバッグするのに便利だし・・・
今回のケースではデバッグゲージを開いて、アプリの動作を確認するのにも使える。この例では、プレビュー実行時にもかなりのリソースが使われているのが分かる。
SwiftUI に用意された @StateObject という Property-wrapper を活用することで解決できる。これはアプリ実行時にのみ初期化されるもので、プレビュー実行時には初期化されない。
先程に比べて、ずっと低リソースでプレビューできているのが分かる。
@StateObject の詳細については、App Essentials in SwiftUI のセッションも参考のこと。
しかし、データが初期化されないとしたら、どうやってプレビューにデータを取り込めばよいのか?2つのパートにわけて解説していく。
Sample data
まずは「どこに(where)」データを定義するのか。
このサンプルアプリでは写真を扱うため、開発中は多くの写真を用意しておきたいが、これは製品版には含めたくない。
プロジェクト設定の最下部にある Development Assets を利用することでそれを実現できる。ファイルやフォルダのパスで指定することができ、それらは開発中のバージョンのみに含まれるようになる。
SwiftUI のテンプレートを使用してプロジェクトを作成した場合、デフォルトで設定されているが、独自のものを追加することもできる。
なお、アセットだけでなくソースファイルも対象となるので便利。
Structuring views
アプリには CoreData や CloudKit などを活用したリッチなデータモデルが含まれるが、最終的にユーザと対話する際は String / Bool などのシンプルなデータ型を利用することになる。
その中間には多数の View が存在することになるが、これらの階層のどこでデータ変換を行うべきなのか?
一般的な経験則としては、リッチデータ型からシンプルデータ型への変換が早ければ早いほど、再利用性、テスト性、プレビュー性が向上する。
プレビュー可能にするための4つの例を見ていく。
1. Immutable inputs
最初は Immutable なデータを渡す例。以下において、セルは Collaborator という観測可能(Observable)なオブジェクトを受け取って構築される。
プレビューを作成するために、CKUserIdentity の指定が必要であると分かったが、CloudKit のデータモデルは先程のデモでオフにしてしまっている。
その View に渡す必要のあるデータの最小量を特定することが大切。今回は3つのプロパティだけで十分なので次のようなコードになった。
これはプレビューを作成するのも簡単。
複数のコンフィギュレーションを適用するのもとても簡単。
しかし、シンプルすぎるデータ型を選んでしまう場合がある。主に日付などのユーザのロケールに基づいてフォーマットするものなど。
ここではユーザ名の表示に String 型の代わりに PersonNameComponents 型を渡すように変更した。文字列のインターポレーションでフォーマッタを指定することができる。
2. Mutable inputs
次に View が変更できるシンプルなデータ型として、SwiftUI のバインディングを使った例を見ていく。
このインスペクタでは ImageSlot という観測可能なオブジェクトを受け取っているが、その中身を見ると・・・
backingRecord が CloudKit での動作を前提にしてしまっている。
先程と同様に View が必要としている最小のデータを渡すようにしたバージョンを見てみる。ここでは SlotEffects のバインディングを渡すようにしている。SlotEffects 型は浮動小数点の集まりでシンプルなデータ型。
このようにプレビューも簡単に作れる。ここでは .constant() を利用して定数バインディングにしているため値の変更はできない。しかし、中間のデータ構造を作ることで動的なバインディングを渡すこともできる。
ここでは EffectsContainer という中間データ型を作成している。これには @State で SlotEffects を保持しており、それを $effects でバインディングとして渡している。
プレビューを Live mode にしてスライダーを動かしてみると、実際に値が変更できているのが分かる。
もう1つ、この View には写真を置換するボタンを追加したい。必要なすべてのデータを渡してロジックをここに記述する方法も考えられるが、ボタンをタップした時の挙動はクライアントに決めてもらいたい。
そのような時には、ボタンをタップした時に実行するクロージャを受け取ることで解決できる。
プレビューでクロージャを渡してみると、ボタンタップ時に正しく呼び出されることが確認できる。
プレビューは実機上でも実行できる。Preview on Device ボタンから iPhone XR を選択してみると・・・
同じようにプレビューが表示され操作できるようになる。標準のプレビューと同様に、コードを変更するとそれがシームレスに反映される。
ホーム画面には Xcode previews というアイコンが表示されるようになる。これは最後に実行したプレビューを保持しているため、Mac から切断して同僚などに見せにいくこともできる。
3. Generic inputs
3つ目として、メインエディタにデータを渡すためにジェネリクスを利用する例を見ていく。
データモデルを見てみると、CKShare という CloudKit に依存したデータを保持している。
そのため、前回までと同様に必要なデータをバインディングとして渡す方法を利用してみる。ここでは名前・レイアウト・スロットを渡しているが・・・
ImageSlot は前回のデモで見たようにリッチなデータ型なので、プレビュー用のデータを作成するのが困難であることに気づく。
今までと同様に必要最小限のデータを渡す方法を考えてみる。ここで必要となるのは Protocol を使った抽象化。
ここでは ImageSlot の代わりに、SlotProtocol という Protocol に準拠したデータ型を受け取るようにした CollageProtocol を定義した。これによってプレビュー用のデータモデルが作れるようになる。
プレビュー用として CollageProtocol に準拠した DesignTimeCollage を作成した。名前・レイアウト・スロットを持っており・・・
DesignTimeImageSlot は SlotProtocol に準拠して次のように定義されている。
プレビュー対象となるエディターは、次のように2つのジェネリックパラメータで構成されている。CollageProtocol は先ほど見たもので、もう1つの ImagePickerType はデザイン時に簡単に写真選択できるようにするためのもの。
プレビューは以下のように定義できる。PhotoPicker を作るためのクロージャには、プレビュー用として先ほど見た Asset カタログからデータを取得する DesignTimePhotoPicker を生成する関数を渡している。
これでエディタは完全な機能を持つようになった。Live Preview を実行するとキャンバス上で一通りの操作ができる。
スロット用のデータを渡すこともできる。画像については Asset カタログから読み込み、それを Publisher に変換して渡している。
iPad での横向きでの確認も Preview on Device の機能によって、インタラクティブに行える。
4. Environment inputs
最後に他とは少し違う例を見ていく。基本的にデータは依存関係を明示的に入力として受け取ったほうがいいが、環境(Environment)として受け取ると便利な場合がある。
ここではクラウドとの同期状況を表す CloudSyncStatus を @EnvironmentObject で受け取っている。SwiftUI ではそれを受け取るためには、View 階層のより上位で環境を設定しておく必要がある。
プレビューでは、各 View に environmentObject モディファイアを利用して指定することで実現できる。
他の例と同じように、複数の設定での確認もかんたんに行える。
振り返り
このセッションでは多くのことを見てきた。振り返ると以下のようになる。
【Previewing multiple files】
最初に、より大きなコンテキストで View を編集できるように、ピン留めによって複数のファイルを同時にプレビューする方法を見た。
【App Lifecycle】
2つ目に、プレビューと SwiftUI アプリのライフサイクルを理解し、必要なときだけ初期化されるように @StateObject を利用する方法を見た。
【SampleData】
3つ目に、サンプルデータを定義する場所と、Development Assets を利用して開発時だけに有効なデータを用意する方法を見た。
【Structuring views】
最後に、View をよりプレビュー可能(pre-viewable)にする4つの方法を見た。
最後に
1つの大きなアイディアとして、アプリをよりプレビュー可能(pre-viewable)にする投資はアプリ全体に利益をもたらす。
このセッションを見たことで、プレビュー可能なアプリは、より理解しやすく、よりテストしやすく、より保守しやすいアプリであると理解してもらえただろうか。
関連
忙しい人向けの App Essentials in SwiftUI
免責
・本記事は公開情報のみに基づいて作成されています。
・要約(意訳)のみなので、詳細はセッション動画をご確認ください。
役に立った記事などありましたらサポート頂けると嬉しいです。