-
SwiftUIの基礎
クパティーノでライブ収録されたこのビデオで、SwiftUIの基本的なコンセプトをご確認ください。「View」にはコンテキストに応じてさまざまな意味があり、それらをアプリでどのように考えるべきかを学びます。ビューを組み込むメリット、ビューをカスタマイズする方法、依存関係のあるコードを構築しシステムとシームレスに連携させる方法も取り上げます。
このセッションは、「Appleに相談」アクティビティである「SwiftUIの基礎:SwiftUIで優れたアプリを構築」の一部として実施されました。ビデオ全編を視聴して、詳しい情報や関連セッションをご確認ください。リソース
-
このビデオを検索
本日は、SwiftUIの仕組みの核心となる 概念についてお話しします。
これらの概念は、より良いコードを書くのに 役立つだけでなく、本日の残りのプレゼンテーションの 土台ともなります。
まずは、SwiftUIのビューで頻繁に 使われる用語について詳しく見ていきます。
次に、SwiftUIに 組み込まれているビューと、
新しいカスタムビューを作成する 際の仕組みについて解説します。
最後に、依存関係と、意図を持って コードを構成する方法について解説します。
各セクションでは、そのセクションで最も 重要な概念と、それらがコード作成という 大きな枠組みの中でどのように 関連しているかについてお伝えします。 優れたSwiftUIコード。
それでは始めましょう。 SwiftUIにおいて、「ビュー」 という用語は複数の意味を持つ用語です。
文脈によって3つの異なる 意味を持つことがあり、 これが少し混乱を招くことがあります。
多くのフレームワークでは、 「ビュー」はユーザーインターフェースや 画面上のピクセルを指します。
SwiftUIにおいても、「ビュー」 はプロトコルでもあります。 これは、画面上に何を表示したいかを 記述したものです。
各ビューは構造体(struct)であるため、 ビューの各インスタンスには、 特定の値が設定されています。
これら3つの概念はすべて関連しています。 画面上のピクセルは、ビューの記述と、 その特定のビュー構造体の実際の値によって 生成される結果です。
ビューの記述と、それを駆動する値が 組み合わさることで、 画面上の最終的なピクセルが形成されます。
「ビュー」のこれら3つの意味は、 アプリのさまざまな場面で登場します。
記述とは、コードそのものです。
値とは、メモリ上のインスタンスのことです。 したがって、SwiftUIは
ピクセルをレンダリングすることができ、 画面上のピクセルには最新かつ最も正確な情報が 表示される必要があります。
本プレゼンテーションでは、ビューのこれらの 異なる意味について、 それぞれ詳しく解説していきます。 文脈は重要であり、 皆様自身のSwiftUIコードにおける 問題点を整理する上で役立つでしょう。
まずは、記述としてのビューについて お話しします。
SwiftUIにおいて、ビューとは、 意図的な方法で構築される記述のことです。
例えば、英語で「シュノーケリングをしている クールな水中シーン」の画像が欲しいとします。
これを記述するには、「image of snorkeling underwater」 というフレーズを書きます。
SwiftUIでは、この記述を viewプロトコルを使って 構築します。「Image」は SwiftUIのビューであり、 「deep sea」は画像の名前です。
これは、私が英語で書いたフレーズと同じです。 ビューは、SwiftUIで私が 求めているものを記述するものです。
SwiftUIは、そのビューを受け取り、 画面に何をレンダリングするかを決定します。 このようにです。素敵な写真ですね。
すべてのSwiftUIビューは、 コードで記述された記述です。
これは、組み込みのビューであっても、 自分で作成したカスタムビューであっても 同様です。
` Image` は、SwiftUIの組み 込みビューの一例です。
ビューはあらゆるアプリの構成要素であり、 組み込みビューは開発を始めるのに 最適な方法です。 SwiftUIには多くの組み 込みビューがあり、 それぞれの名前はそのビューが 何を生み出すかを表しています。
例えば、「Image」が 画像を可視化するのと同様です。
「Color」は、 フレーム全体を紫色などの特定の色で 塗りつぶすビューです。
SwiftUI が組み込み ビューをハードウェアと統合しているため、 組み込みビューは非常に強力です。
ここで表示されている紫色は、 実際にはコンテキストに応じた色です。
実際の色値は、デバイスのコンテキスト (例えば、スマートフォンが ライトモードかダークモードか、あるいは画面に 日光が強く当たっているかどうかなど) に基づいて調整されます。
Text ビューは、 「Kona Deep Dive」 のような文字列を表示します。
Color ビューと同様に、 Text ビューも表示される 環境に合わせて最適化されています。
SwiftUIは、現在の プラットフォームに適した フォントを使用して文字列を描画します。 iMacのような大型ディスプレイでは、 Apple Watchのような小型デバイスに 比べて、 テキストビューの物理的なサイズも 大きくなります。
テキストビューは、アクセシビリティ機能である 「ダイナミックタイプ」にも対応しています。
ダイナミックタイプを使用すると、 ユーザーはデバイス上で 表示されるテキストのサイズを調整し、 快適に読むことができます。
ここでは、右側のスマートフォンのシステムフォントサイズが 大きくなっています。
私のSwiftUIコード内の テキストビューは、 コンテンツが読みやすい状態を保つよう自動的に サイズ調整されます。これには、 私の側で追加のコードを書く必要はありません。 各ビュー、画像、色、テキストについて、 ビュー名は私が意図する 内容と、「deepSea purple」
や「Kona Deep Dive」といった値が 組み合わさったものです。 それらが画面上のピクセルとして表現されます。
組み込みビューは、そのままでも 便利であるだけでなく、 カスタマイズも可能であるため、 素晴らしい出発点となります。
ビューをカスタマイズすることで、 SwiftUIの組み込み ビューの利点を活かしつつ、 アプリの独自の個性を表現することができます。
SwiftUIでは、ビューをカスタマイズする 方法はいくつかあります。
先ほどご紹介した私のTextViewは 問題なく見えますが、 もっと印象的なものにすることができます。 ビューモディファイアは、個々のビューを カスタマイズするためのツールです。 それでは、コードを詳しく見ていきましょう。 テキストビューをカスタマイズする 方法はたくさんあります。 例えば、フォントのサイズや 色を変更することなどです。 これは、ウィッシュリストアプリで 頻繁に行っていた作業です。 今回は、まずは簡単なことから始めましょう。 明るい色を背景にして、テキストビューを 視覚的に際立たせたいと思います。
backgroundモディファイアは、ビューの 背景を設定します。 背景として、任意のビューを 指定することができます。 私はオレンジ色を選びました。
backgroundは、ビューモディファイアの 一例です。
ビューモディファイアとは、textなどの 特定のビューに対して呼び 出されるメソッドであり、 元のビューを含む新しいビューを返します。
元のテキストビューは、 ビューモディファイアによる変更でラップされ、 新しいビューが生成されます。
私は、ビュー修飾子をビューを包み込む 「ラッパー」のようなものだと考えています。
これら2行のコードを組み合わせることで、 元の文字列「Kona Deep Dive」に オレンジ色の背景が 適用されたビューが生成されます。 これは、元のテキストビューをわずかに 変更したものです。 複数のビュー修飾子を適用することで、 複合的なカスタム効果を 作成することができます。
パディングもまた、ビュー修飾子のひとつです。 パディングは、適用された ビューの端にポイント(余白)を追加します。 この場合、パディングによって TextViewの四辺すべてに 余白が追加され、その後、 backgroundpadding がその前のすべてを 覆うように、 オレンジ色の背景が塗りつぶされます。 つまり、これら 3 行のコードは、 ビュー階層において 1 つの大きなビューを表しています。 ビューモディファイアに関する 最後の注意点として、順序が重要です。 各ビューモディファイアは、その直上の コード行にのみ影響を与えます。
順序を入れ替えて、background を 先頭にするとします。 オレンジ色は元の TextView のみを覆うことになります。
灰色の点線で示されたパディングは、 残りの部分に適用されます。
ビュー修飾子の順序については、 意図的に設定するようにしてください。 コードが期待通りに動作しない場合は、 ビューが理にかなった順序で 囲まれているか確認してください。
ビュー修飾子は個々のビューを カスタマイズするものですが、 コンポジションは複数のビューを組み 合わせる手法です。
既存のビューを組み合わせることで、 独自のカスタムビューを作成できます。
例えば、「Kona Deep Dive」 のウィッシュリストにある 検索タブに、このような行を 1つ作成したいとします。
以前使用した組み込み ビューをいくつか活用できます。
まず、検索行のビューを定義します。
これは構造体(struct)であり、 Viewプロトコルに準拠しています。
次に、ビューの本体を追加します。 これは、すべてのビューに必須の要素です。
そして、深海を写した ImageView を追加します。
さらに、「Kona Deep Dive」 と表示される TextView も追加します。 デフォルトでは、SwiftUI はこれらを 縦に積み重ねます。
並べて表示したいので、 画像とテキストを横並びにする 水平スタック(Hスタック)を追加します。 Hスタックもビューの一種ですが、
画像のように「何をレンダリングするか」 を記述するのではなく、 「どのようにレンダリングするか」 を記述します。 SwiftUIのHスタックは、 画像やテキストといったすべての サブビューを水平一列に 配置します。
本日の後半では、Catが レイアウトやSwiftUIに 関するツールやテクニックについてさらに 詳しく解説します。 より洗練されたビューを作成するにあたって、 一点強調しておきたいことがあります。 その中核となる概念は変わりません。
私の検索行は、私が 実現したいことを記述したものであり、 SwiftUIはその記述に 基づいて画面上にピクセルを描画します。
コンポジションによりコードの可読性が向上し、
一度検索行を作成すれば、 たった1行のコードで アプリの他の箇所でもそれを利用できます。
私の検索ビューでは、3つのスタックや 3つの画像、3つのテキストビューを用意する 必要はありません。 単に3つの複合検索行ビューを 使用するだけです。
また、後で検索行の背景を紫色にするなど、 何かを変更したい場合でも、 そのビュー内だけで対応できます。
これにより、記述する 必要のあるコード量が減り、 コードの可読性や理解しやすさが向上します。
ビューは構造体(struct)だからです。 軽量であるため、このように ビューを小さなビューに 分割しても、パフォーマンスに 悪影響はありません。
組み込みのビューと同様に、 カスタムビューも、SwiftUIが 画面上に何を描画すべきかについての 記述です。
「Search View」のような 名前も重要ですが、 それ以上に重要なのは、 ` body` 内に含まれるビューと、 それらがリストされている順序です。 SwiftUIは、コード内の記述に 基づいて画面上のピクセルを描画します。
さて、ここからさらに一歩進めてみましょう。 私の検索行は意図したデザインにかなり 近づいていますが、まだ完全ではありません。
現在、「Kona Deep Dive」 のインスタンスが3つありますが、 皆さんはどうでしょうか。 私は毎年、休暇の行き 先を変えたいと思っています。 アプリは動的であり、 変化するデータを表しています。
バケットリストにある他の 旅行先を表示できるように、 これを切り替える方法が必要です。
ここで、最後のトピックである 「依存関係」についてお話しします。
SwiftUIのビューは データによって駆動されます。 そのデータが、深海の画像や 「Kona Deep Dive」 という文字列のように変わらない場合でも、 あるいはデータが変化する場合でも同様です。 本日ご紹介したすべてのビューは、 データに依存しています。
画面上のピクセルは、 一部は「画像」のような説明の結果であり、 一部は「深海」のようなデータの 結果でもあります。
先ほど、すべてのビューは 構造体であると述べました が、これはすべてのビューが値型であり、 ビューの各インスタンスには 値があることを意味します。
私は、ビューのインスタンスをこのように 視覚化するのが 好きです。上部に 「image」のようなビュー名、 下部に「deep sea」 のようなデータがあります。
SwiftUIは、画面上の ピクセルを描画するために、 データを含む特定のビューの インスタンスを使用します。
ピクセルが描画されると、SwiftUIはその インスタンスを破棄します。 もはや必要ないからです。
これが「ビュー」の3つ目の意味です。 値としてのビューです。
ビューのインスタンス。特定の値が、 画面上のピクセルを駆動しています。
各ビューのインスタンスは一時的に存在します。 その寿命は短く、 必要なときにのみ生成されます。 SwiftUIのメモリ内に存在します。 そして、ピクセルがレンダリングされると、 ビューの役割は完了し、 インスタンスは破棄されます。
なお、インスタンスを破棄することは、 悪いことでも、重大なリスクを 伴うことでもありません。 ビューは軽量で、簡単に作成できます。
私はビューをテンプレートのようなものだと 考えています。同じものを次々と生成し、 画面上のピクセルを作り 出すことができるのです。 多くの場合、これらのテンプレートは、 柔軟で動的なデータを表しています。
例えば、私のアプリでは 深海に集中していますが、 京都の画像も必要になります。
これもビューのインスタンスの一つですが、 このインスタンスの値は少し異なります。 「京都」という画像の名前は、 「深海」とは異なります。
SwiftUIは、画像名によって値が 異なるこれらのImageViewを受け 取り、それぞれ異なる画像を表示します。
この柔軟性を活かして、 検索行のコードを見直してみましょう。
これで、画像名やテキストをハードコーディングする 代わりに、 検索行は画像名と旅行のタイトルを引数として 受け取るようになりました。
このバージョンの検索行は、 より柔軟性が高くなっています。 これを使って「Kona Deep Dive」 の行を作成することもできます が、他の旅行にも活用できます。
これにより、本来のデザインに 一歩近づくことができました。 検索ビューで新しい検索行を使用する際、
「Mammoth Blush」や「Kyoto Mystique」 といった、さまざまな画像や 旅行名をすべて指定します。
これで、はるかに良くなりました。
データが画面上のピクセルを駆動しており、 コンポジションのすっきりとしたコードを 引き続き活用できます。
なお、この検索ビューの実装は、 ウィッシュリストにある最終バージョンに 比べてまだ簡略化されたものである 点にご留意ください。 実際の検索では、データを動的に検索し、 検索フィールドに入力された 画像や文字列に基づいて検索行に データを表示します。
これらの検索行について、 もう一点強調しておきたいことがあります。
先ほどご紹介した画像ビューと同様に、 検索行の各インスタンスは、 旅行名や写真名に基づいて異なる 値を持っています。
SwiftUI はこれらを簡単に比較し、 等しくないことを認識できます。
SwiftUI が画面上に ピクセルを描画した後、 ビューのインスタンスを解放します。
ピクセルがレンダリングされたため、 それらはもはや必要ないからです。
さて、SwiftUI が 依存関係(dependencies) をどのように扱うかについて、 もう少し詳しくお話ししたいと思います。
私の検索行を例に挙げます。 説明部分は、私のビューのコードです。
ビューの値が異なると、 画面上のピクセルも異なります。
SwiftUI は、 依存関係を使って重要な値を追跡します。 ここで言う「重要な値」とは、 データに基づいてピクセルが 正しく表示されるか、あるいは間違って 表示されるかを左右するような値のことです。
その仕組みは次の通りです。 SwiftUIが初めて` body` を実行する 際、読み込んだすべての 値をグラフで追跡します。
このグラフの最初の部分は、 検索バーを表すノードです。
` body` 内で、 SwiftUIは画像ビューの「写真名」 の値を読み取るため、
「写真名」のノードと、 検索バーを指す下向きの矢印を追加します。
また、SwiftUIは「旅行名」の値も 読み取るため、「旅行名」 のノードと矢印を追加します。 SwiftUIがこれらを追跡するのは、 ピクセルが正しく表示されるためには、 正しい「写真名」と「旅行名」 を表している必要があるからです。
もしどちらかが変更された場合。 例えば、新しい「写真名」を指定するとします。 これを赤い点で表していますが、その場合、 検索行のピクセルは 古くなってしまい、SwiftUIは 新しいピクセルを描画する必要があります。
依存関係は、ビューへの入力のようなものです。
依存関係追跡は、SwiftUIが 更新処理を非常に効率的に 行える理由の一つです。
多くのビューと大量のデータを含むアプリでは、 何か一つが変更されるたびにSwiftUIがすべてを 再描画しなければならないと、 極めて非効率的になってしまいます。
アプリではデータが頻繁に変化するため、 これでは多くの不要な更新が 発生してしまいます。
その代わりに、SwiftUIは 依存関係を追跡します。
ここでは矢印で表現していますが、 これらはどのビューが特定のデータに 依存しているかを示しています。
このシステムにより、何かが変更された際、 SwiftUIは下流にあるもののみを 再計算し、再描画します。
アプリ内では状況が頻繁かつ急速に 変化しますが、依存関係追跡により、 SwiftUIは効率性を維持しています。 これは洗練された仕組みですが、 さらに素晴らしいニュースがあります。 このグラフを自分で構築する必要はありません。
SwiftUIが依存関係グラフとして 自動的に構築してくれます。
各ビューが` body` を実行するたびに 読み込むプロパティを追跡します。
このシステムにより、 グラフは常に最新の状態に保たれます。
このグラフを構築したり 維持したりする必要はありませんが、 コードを書く上で 留意すべき点がいくつかあります。
ビューを構築する際は、 ビューを軽量に保つようにしてください。 また、 ` body` 内で必要だとSwiftUIに 伝えた情報が何であるか、 そしてそれらのビューへの 変更によってビュー全体が 無効化されるべきかどうかを検討してください。
依存関係を最小限に抑えてビューを軽量に 保つことが最善です。そうすることで、 SwiftUIは重要な場合のみ ビューを再描画するようになります。
これは、複雑なビューを作成できないという 意味ではありません。
つまり、先ほど私が検索ビューの行で 行ったように、複雑なビューをより 小さく、よりシンプルなパーツに 分解すべきだということです。
適切なツールを使用してデータを表現し、 画面上のピクセルを変更する 必要がある場合にのみ、 変更を伝達するようにしてください。 ビューがデータに 依存する方法はいくつかあります。 その一つは、先ほど私が行ったように、 画像や旅行の具体的な名前を指定して、 データをビューに直接渡す方法です。
データを作成するもう一つの方法は、 「at state」を使用することです。
同僚のコールが、プレゼンテーションの 中で「at state」 やその他のデータフローの選択肢についてさらに 詳しく解説しますが、 ここでは基本的な点について 触れておきたいと思います。
「at state」で ラップされたプロパティ。 SwiftUIに対し、 その情報をビューの存続期間全体にわたって 保存するよう指示します。
これは、ビューがビュー階層内にある 全期間を指します。 簡単な例で説明いたします。
ウィッシュリストの検索タブで、 検索バーに入力を始めると、 結果が絞り込まれていきます。
文字「J」を入力すると、 最新の項目が、「ハイキング」「ジョシュア・ ツリー」「クリフジャンプ」といった 検索文字列を含む私の旅行や アクティビティに置き換わります。
さらに別の文字、例えば「O」を入力すると、 検索結果はさらに絞り込まれていきます。
アプリに追加したすべての旅行や アクティビティの中で、 「ジョシュア・ツリー」だけが 「j o」という文字列を含んでいます。 これがアプリの状態です。 検索フィールドに入力した文字列が、 検索結果と画面上の 表示を決定づけています。
「状態」がどのように機能するかを示すために、 簡略化した例を作成してみます。
これは検索ビューの簡略版で、 「Simple Search」と 呼んでいます。 実際の検索ビューの中核となる 機能は備えていますが、 デザインは一部簡略化されています。
本体には2つのビューしかありません。 テキストフィールドとTextViewを 使用しています。
TextViewは、 「results here」と表示される 単なるプレースホルダーです。 これは、プロトタイプ作成中に 私がよく行う手法です。 プレースホルダーを配置し、最終的には 検索結果を表示する カスタムビューに置き換える予定です。
テキストフィールドも、SwiftUIに 組み込まれているビューの一つです。 これは入力を収集するためのものです。 ユーザーがこれをタップすると、 キーボードが開き、 入力した文字列が表示されます。
ここでは初期プロンプトを設定し、 入力された文字列を「Search Value」 という変数に 保存するようSwiftUIに指示しています。
ドル記号はバインディングを表す構文で、 これもデータフローのツールの一つです。 これについては、コールが後で 詳しく説明します。
` searchValue` プロパティに ` AtState` プロパティラッパーを適用しました。
これにより、SwiftUI は 更新のたびに ` searchValue` の値を保持するよう指示されます。
初期値は空の文字列です。
テキストフィールドをタップすると、 カーソルが点滅し、 文字を入力できる状態になります。 文字「J」を入力すると、 「searchValue」の値が 空の文字列から「J」に 変更されます。SwiftUIは その値を保持し、それに 応じてシンプルな検索用のピクセルを 更新することができます。
文字「O」を追加して 「Joshua Tree」へと絞り 込みを進めても、同じことが起こります。
SwiftUIは「searchValue」 の値「j o」 を保持するため、更新のたびに 連続した進行状況を実現できます。
「state」は、ビューの存続期間全体を 通じて保持すべき情報をモデル化する 最も簡単な方法です。
検索タブにいる間、検索値は保持されるため、 引き続き追加することができます。
タブから移動して後で戻ってきた場合、 値は空の文字列から再び最初から始まります。
これは、ウィッシュリストに 実際の検索ビューを構築するための 最初のステップに過ぎません。 次に、プレースホルダーのテキストビューを、 検索値の文字列に 一致する旅行やアクティビティに 置き換える必要があります。
ひとまず、ステートのこれらの 側面に集中してください。
` @ State` は、SwiftUIにおける プロパティラッパーです。
SwiftUIでプロパティに` @ State` が指定されると、 SwiftUIはそのプロパティが アプリの「真実の源」 であることを認識し、 更新のたびにその値を保持します。
stateは、ビューの存続期間全体を通じて メモリ内に保持されます。
つまり、アプリでは、テキストフィールドに 入力された文字列のように、 動的で複合的なデータを表示することが 可能になります。 SwiftUIでは依存関係を構築する 方法がいくつかありますが、 Coleが後ほどそれぞれについて 詳しく解説します。 現時点では、一つだけ強調しておきたいことが あります。これらのツールがどのように 機能するかを学ぶために、 時間を割いてください。 そうすることで、SwiftUI で 機能するように依存関係を適切に 設計できるようになります。
本日お話ししたように、 ビューは SwiftUI の構成要素です。 ビューが使用されるさまざまな文脈について 理解を深めることで、 フレームワークそのものを 理解できるだけでなく、 コードの品質も向上させることができます。
本日の残りの プレゼンテーションについてですが、 ウィッシュリストのさまざまな分野を探求される 際は、これらのソリューションの背後にある 理論に焦点を当てることをお勧めします。
そうすることで、学んだことをご自身の アプリに応用できるようになります。
私のプレゼンテーションから、 以下の点を心に留めておいてください。
SwiftUIの組み込みビューを活用して、 独自のカスタムビューを作成してください。 さまざまな修飾子を試して、 アプリならではの独自性を表現してください。
ビューを構築したり、 既存のビューを見直したりする際は、 ビューを軽量に保つことを心がけてください。 各ビューが実際にどのデータに 依存すべきかを検討し、 複雑なビューをよりシンプルで 小さなパーツに分解してください。 これらの基本を念頭に置いておくことで、 課題が生じた際に、より的確に 対処できるようになります。ご聴講いただき、 誠にありがとうございました。
-