ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
SwiftUI APIの設計技術:プログレッシブディスクロージャ
SwiftUIの基本原則の1つであるプログレッシブディスクロージャについて解説しますので、それがどのようにAPIの設計に影響するのかご覧ください。さらに、プログレッシブディスクロージャの使用方法や、これが迅速な反復や探索をどうサポートするのかを解説しますので、自分用のコードでも活用できるようになります。
リソース
-
ダウンロード
こんにちは SwiftUIチームのエンジニア Samです SwiftUIを設計するとき 私たちは 明確に定義された 原則に基づいて 意思を 決定するよう努めて来ました その中から プログレッシブディスクロージャ 「段階的開示」を紹介します SwiftUIチームでは新しいAPIについて考え 構築することに多くの時間を費やしています あなたが気づいていないかもしれないことは 再利用可能なコンポーネントや 抽象化を構築した瞬間に あなたもAPIデザイナーであるということです この講演では 私たちの デザインプロセスを公開し 段階的開示について学んだことを共有します 次に 再利用可能なコンポーネントや抽象化を 構築する際 ツールベルトに 新しいツールを追加します
まずは 段階的開示の 本当の意味についてお話します 結論から言うと APIの設計に 限ったことではないのです 実際 この現象は macOSの 最も一般的なUIの1つである 保存ダイアログで見ることができます 最初に保存ダイアログが表示されたとき デフォルトの保存場所が設定されています さらに このダイアログには 共通の場所をドロップダウンで表示され よく使う場所に簡単にアクセスできます ファイルシステムをブラウズして パスを見つける場合は ダイアログの展開により複雑で 強力なUIが表示されます 必要に応じて明らかにできる 複雑なレイヤーが存在します これは私たちがAPIで提供したいものと同じです 素晴らしいUIを提供することに 相当するコードは APIを使いやすくすることです
開発者として 私たちは 自分のコードを書く場所 宣言のサイトという視点から コードを見ることが普通です しかし コードを使いやすくすることは コードが実際に使われる場所 つまりコールサイトと 呼ばれるところから 見てみる必要があります
段階的開示には 使用例の 複雑さに応じて コールサイトの 複雑さも対応できるよう APIを設計することです
理想的なAPIはシンプルでアプローチしやすく 強力な使用例に対応できることです
これは開発者にとっても大きなメリットです まず最初のビルドと実行に かかる時間を最小限にし APIを迅速に使用することができます またコードの学習曲線も 短縮され APIがすべての 関係ない概念で埋もれるのを 防ぐことができます
最後に 簡潔なフィードバックを作ることです 段階的開示を採用したAPIは 少しずつ要素を追加し 各ステップで作成したものを確認できます
Appの構築は一度に大きな 先行投資をするのではなく 迅速な改良の繰り返しとなります
段階的開示は有益な道標ですが その原則の受け入れのため どのようにAPIを設計すればよいでしょうか? SwiftUIチームでは一般的な 使用例の検討から始めます 段階的開示の機能のため例を見てみましょう シンプルな事例はどうあるべきか 確認が必要です
インテリジェトなデフォルトを提供れば 一般的には必要なものだけを指定できます 次にコールサイトの最適化を目指し コールサイトのすべての文字に 目的を持たせるようにします そして最後にAPIを可能性を列挙するのではなく 要素を構成するように設計します 早速 一般的な使用例を考慮する方法から始め SwiftUIからいくつかの例を見てみましょう SwiftUIが特に上手く行う 場所の1つはラベルです
ボタンを作成する場合 そのボタンのラベルを指定する必要があります ほとんどの場合このラベルは ボタンの目的を説明するテキストになります SwiftUIはそれを綴る簡潔な方法を提供します さらにボタンをカスタマイズしたい場合 SwiftUIは別のオーバーロードを提供します これは任意のViewwを ラベルとして受け取ります
これにより この単純なコントロールから 複雑な機能を構築することができます このAPIは一般的な使用例を丁寧に考慮するため 99%の確率で シンブルな バージョンのみ必要です
このラベルパターンは SwiftUIの至る所に現れます どこにでもある というのは本当にそう思います ですから 共通の使用例を考慮することは 私たち全体の仕事に関わることです 次にインテリジェトな デフォルトを提供するのです 一般的な使用例を合理化するために 明示的に指定しない すべての事例にたいしてインテリジェトな デフォルトを提供する必要があります SwiftUIの中で最もよく使われるAPIの1つである 「テキスト」ほど 良い例はありません テキストはインテリジェトな デフォルトの素晴らしい例で このようなコードを 何百回も書いたことがあるでしょう
このコードだけでSwiftUIは 環境のローカライズで Appバンドル内の ローカライズされた文字列を検索して テキストをローカライズします 現在のカラースキームに自動的に適応し ダークモードにもすぐに対応します そして 現在の動的タイプサイズの アクセス可能性に応じて 自動的にテキストを拡大 縮小します このような動作については 以前にもお話しましたが テキストはそれ以上に裏側で動いています
例えば 2つのテキストを 隣り合わせにして積み重ねると テキスト間のスペースは 現在の文脈内のテキストの正しい行間に 自動的に調整されます そのすべての動作は手動で 指定することができますが SwiftUIのインテリジェトなデフォルトでは 使用例に関連しない場合 コールサイトで表示されません
テキストはシンプルな例が 極めて少ないAPIの例ですが インテリジェトなデフォルトは コールサイトに適用されます 例えば ツールバーです ここでは ボタンが並んだ ツールバーを表示します ツールバーのボタンは位置を 明示的に指定することなく プラットホームの慣習に従って配置されます macOSではツールバーの先端に表示されます iOSではナビゲーションバーに表示され 末尾から表示されます 最後にwatchOSでは最初の項目のみが表示され ナビゲーションバーの下に固定されます この機能は ほとんどの場合において 素晴らしいものです しかし より多くの制御が必要な場合は アイテムの位置を明示的に指定するための 追加のAPIを提供します 繰り返しになりますが カスタマイズが必要な場合は インテリジェトなデフォルトが 大半の例を処理します
インテリジェトなデフォルト の提供は素晴らしい体験です しかし これらAPIの使用に 不便さがあり 洗練さがない場合 全体の効果を台無しにする可能性があります そこで最後の戦略はコールサイトの最適化です 別のAPIを見てみましょう テーブルです
複数列のテーブルは 機能豊富なコントロールです 設定することがたくさんあり 多くの機能があります しかし 大半のテーブルはもっとシンプルで そのような機能は必要ないのです 私たちはテーブルがより 複雑な動作を行うことを望んでいます 最も詳細な形式では それが可能です ソートや豊富なセルコンテンツを含む 複数列セクション可能な行などを サポートしています
より一般的な例でも素晴らしい体験を 提供したいのでこのシンプルなテーブルの フルスペックのコードを見て そのコールサイトを どのように最適化するかを見てみましょう まずこの例を分解してみましょう テーブルは各行のデータの 生成方法から始まります
ここでは 現在読み込み中のブックを繰り返し テーブルの行を作成しています 次に 各行のデータから列を 生成する方法を指定します ここではタイトル欄と著者欄を作成します
ソート順のバインディングを 取得し ユーザーがテーブル 列のヘッダをクリックすると ソートの変更を可能にします
最後に ソート順が変わるたびにテーブルの データを再ソートするコードを追加しました ずいぶんな情報量ですね 段階的開示の受け入れのため このコールサイトを最適化する方法を 紹介しましょう
一般的な使用例として すぐに目につくのは 行です 大抵は 行のフィールドはコレクションに対する ForEachです 各項目にテーブルの行を提供します
開発者が自分でループする必要はありません SwiftUIはこの処理を裏で行う機能を提供します コレクションを直接テーブルに 渡すことでForEachの動作を 裏で提供でき 全てのサイトを簡素化できますが これはまださらに簡素化できます 他によくある使用例は何でしょう? ほとんどの場合 テーブルに表示する値が文字列の場合 その文字列を列に表示するために テキストを使用します この場合もコールサイトを最適化します
keypathが文字列を指す場合は 常にTableColumnに関するViewを 省略できるようにします
これも大きな簡略化ですが まだまだ最適化は必要です コールサイトにはすべてのテーブルが 重要としない情報があります ソート順です テーブルの最も単純な使用例は ソートを全く気にしません そこでソートを気にしないバージョンの テーブルを用意しました 最終的なイテレーションです よりシンプルになりました このコールサイトのすべての 文字は明確な目的を持ち すべてのステップで2つの 重要な質問に行き着きました 「便利なものを作るべき最も 一般的な使用例は何か?」 「常に必要とされるべき必須情報とは何か?」 これらの指針的な質問は コールサイトを最適化するのに役立ちますが 慎重に適用する必要があります APIについてその意味をよく考えないと 道を踏み外しかねないです それが最終的な戦略です 列挙するのではなく構成です そしてこれを説明するためにSwiftUIの レイアウトシステムの一部のデザインについて お話しましょう スタック 特にHStackです まずHstackに必要な情報は 何かを考えてみましょう スタックにどのようなコンテンツが含まれ そのコンテンツの配置方法を 知る必要があります すでにViewビルダーを使用し HStackの中身を指定しているので 配置に注意しましょう もう一度質問に立ち返り HStackに要素を配置する際 最も一般的な使用例は何でしょうか? 私は時々 先頭から順番に ボックスを表示させるような スタックを見せたいと思うことがあります
もう1つよくある例は 要素を中央に配置したいことです そして最後に 要素を後縁に合わせたいこともあります
VStackはすでに同様のケースを 持つAPIがあるので スタック内の要素の配置のために 同様の列挙型を作成するのは 魅力的に見えるかもしれません これは ご紹介したすべての ケースに対応しています HStackの配置を指定することで 先頭 最後尾 中央の配置を選択することができました しかし 要素を均等に配置 したり 要素間のみスペースを 入れたり最後の要素の前にだけ スペースを入れたい場合は どうすればいいのでしょう? これは厄介なことになりましたね しかし もっと重要なのは 持続不可能だということです 望むすべての動作に列挙型の ケースの追加が必要で すべての有用なケースを 思いつくとは限りません よくあるケースを便利なものを提供するより 列挙していることに気がついたら APIを分解して 解決策を構築できるような 構成要素にしてみましょう 列挙するのではなく構成です
スタックの場合 SwiftUIはスペーサーを提供し それをスタックの要素と組み合わせて 列挙した スペーシングスキームを 構築することができます
段階的開示のための最高のエクスペリエンスを デザインすることはコールサイトを最小化する だけでなくどのように拡張するか 慎重に検討しました この場合コンポジションです
自分でコードを書く場合 作成するコンポーネントに 対して同じような配慮をする ことが非常に役立ちます そのためには まず一般的な 使用例を考慮することです 段階的開示の適用により あなたの書いたコードは 最も一般的な使用例で時間を節約できます 段階的開示があればよくあるケースで 細かいことを考える必要がありません コールサイトの最適化に取り組むことで 迅速な反復が可能になります 最後にコンポジションを活用することで 柔軟性のあるAPIを構築することができます
そして あなたはAPI設計者であるため 他の誰かのために設計されているのか 単に自分が使うためなのかにかかわらず 教訓を 毎日書くコードに適用することができます ご覧いただきありがとうございました
-
-
1:59 - Declaration Site Example
struct BookView: View { let pageNumber: Int let book: Book init(book: Book, pageNumber: Int) { self.book = book self.pageNumber = pageNumber } var body: some View { ... } }
-
2:13 - Call Site Example
VStack { BookView(book: favoriteBook, page: 1) BookView(book: savedBook, page: 234) }
-
4:18 - Button Label
Button("Next Page") { currentPage += 1 }
-
4:36 - Button label expanded
Button { currentPage += 1 } label: { Text("Next Page") }
-
4:43 - Button label advanced case
Button { currentPage += 1 } label: { HStack { Text("Next Page") NextPagePreview() } }
-
4:56 - Button label common case
Button("Next Page") { currentPage += 1 }
-
5:30 - Text example
Text("Hello WWDC22!")
-
6:12 - Stacks of Text
VStack { Text("Hello WWDC22!") Text("Call to Code.") }
-
6:46 - Toolbar
.toolbar { Button { addItem() } label: { Label("Add", systemImage: "plus") } Button { sort() } label: { Label("Sort", systemImage: "arrow.up.arrow.down") } Button { openShareSheet() }: label: { Label("Share", systemImage: "square.and.arrow.up") } }
-
7:20 - Toolbar with explicit placement
.toolbar { ToolbarItemGroup(placement: .navigationBarLeading) { Button { addItem() } label: { Label("Add", systemImage: "plus") } Button { sort() } label: { Label("Sort", systemImage: "arrow.up.arrow.down") } Button { openShareSheet() }: label: { Label("Share", systemImage: "square.and.arrow.up") } } }
-
8:09 - Advanced use case table
@State var sortOrder = [KeyPathComparator(\Book.title)] var body: some View { Table(sortOrder: $sortOrder) { TableColumn("Title", value: \Book.title) { book in Text(book.title).bold() } TableColumn("Author", value: \Book.author) { book in Text(book.author).italic() } } rows: { Section("Favorites") { ForEach(favorites) { book in TableRow(book) } } Section("Currently Reading") { ForEach(currentlyReading) { book in TableRow(book) } } } .onChange(of: sortOrder) { newValue in favorites.sort(using: newValue) currentlyReading.sort(using: newValue) } }
-
8:41 - Simpler table use case
@State var sortOrder = [KeyPathComparator(\Book.title)] var body: some View { Table(sortOrder: $sortOrder) { TableColumn("Title", value: \Book.title) { book in Text(book.title) } TableColumn("Author", value: \Book.author) { book in Text(book.author) } } rows: { ForEach(currentlyReading) { book in TableRow(book) } } .onChange(of: sortOrder) { newValue in currentlyReading.sort(using: newValue) } }
-
9:58 - Table collection convenience
@State var sortOrder = [KeyPathComparator(\Book.title)] var body: some View { Table(currentlyReading, sortOrder: $sortOrder) { TableColumn("Title", value: \.title) { book in Text(book.title) } TableColumn("Author", value: \.author) { book in Text(book.author) } } .onChange(of: sortOrder) { newValue in currentlyReading.sort(using: newValue) } }
-
10:23 - Table string key path convenience
@State var sortOrder = [KeyPathComparator(\Book.title)] var body: some View { Table(currentlyReading, sortOrder: $sortOrder) { TableColumn("Title", value: \.title) TableColumn("Author", value: \.author) } .onChange(of: sortOrder) { newValue in currentlyReading.sort(using: newValue) } }
-
10:51 - Table without sorting
var body: some View { Table(currentlyReading) { TableColumn("Title", value: \.title) TableColumn("Author", value: \.author) } }
-
13:37 - Stack example: leading
struct StackExample: View { var body: some View { HStack { // leading Box().tint(.red) Box().tint(.green) Box().tint(.blue) } } }
-
13:40 - Stack example: centered
struct StackExample: View { var body: some View { HStack { // centered Spacer() Box().tint(.red) Box().tint(.green) Box().tint(.blue) Spacer() } } }
-
13:42 - Stack example: evenly spaced
struct StackExample: View { var body: some View { HStack { // evenly spaced Spacer() Box().tint(.red) Spacer() Box().tint(.green) Spacer() Box().tint(.blue) Spacer() } } }
-
13:43 - Stack example: space only between elements
struct StackExample: View { var body: some View { HStack { // space only between elements Box().tint(.red) Spacer() Box().tint(.green) Spacer() Box().tint(.blue) } } }
-
13:46 - Stack example: space only before last element
struct StackExample: View { var body: some View { HStack { // space only before last element Box().tint(.red) Box().tint(.green) Spacer() Box().tint(.blue) } } }
-
13:47 - Stack example: space only after first element
struct StackExample: View { var body: some View { HStack { // space only after first element Box().tint(.red) Spacer() Box().tint(.green) Box().tint(.blue) } } }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。