View in English

  • メニューを開く メニューを閉じる
  • Apple Developer
検索
検索を終了
  • Apple Developer
  • ニュース
  • 見つける
  • デザイン
  • 開発
  • 配信
  • サポート
  • アカウント
次の内容に検索結果を絞り込む

クイックリンク

5 クイックリンク

ビデオ

メニューを開く メニューを閉じる
  • コレクション
  • トピック
  • すべてのビデオ
  • 利用方法

その他のビデオ

ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。

  • 概要
  • トランスクリプト
  • コード
  • SwiftUIの基本

    Appleの宣言型ユーザーインターフェイスフレームワークである、SwiftUIのツアーにご参加ください。ビュー、状態変数、レイアウトなど、SwiftUIでアプリを構築するうえで基本となる概念について解説します。豊富な機能により充実した体験を提供するアプリを実現し、独自性のあるカスタムコンポーネントを作成するうえで役立つ、多彩なAPIもご紹介します。SwiftUI初心者の方も、経験豊富なデベロッパの方も、SwiftUIのメリットを活用して優れたアプリを構築する方法を習得できます。

    関連する章

    • 0:00 - Introduction
    • 1:34 - Fundamentals of views
    • 13:06 - Bulit-in capability
    • 17:36 - Across all platforms
    • 20:30 - SDK interoperability

    リソース

    • Forum: UI Frameworks
    • Learning SwiftUI
    • SwiftUI
    • SwiftUI Pathway
      • HDビデオ
      • SDビデオ

    関連ビデオ

    WWDC24

    • Xcodeの基本

    WWDC20

    • SwiftUIのご紹介
  • このビデオを検索

    こんにちは Taylorです 「SwiftUI Essentials」へようこそ SwiftUIはAppleの宣言型ユーザー インターフェイスフレームワークです Appleのすべてのプラットフォームで アプリの構築に使用されます 新規アプリの基盤として Apple内で広く採用されており 既存のアプリについても 段階的に採用されています 新しいアプリや新機能を開発する時は SwiftUIは最適なツールです これにはいくつか理由があります SwiftUIには幅広い機能が備わっています これにより デバイスを活用し Appleのプラットフォームで自然に利用でき 優れたインタラクティブ機能が追加された アプリを開発できます さらにこれらの機能を追加するための コードは少なくてすみます プロトタイプから本番環境への 迅速な移行が可能なので 独自のアプリの開発に集中できます SwiftUIは 段階的な導入に対応しているため 必要な場所でのみ利用できます アプリ全体で SwiftUIを利用する必要はありません したがって誰でも簡単に SwiftUIを使用したアプリの 構築方法を学習できます SwiftUIがこれらの特徴を どのように実現しているかを理解して 最大限に活用しましょう まず ビューの基本的な仕組みについて 説明します その後 SwiftUIに組み込まれた いくつかの機能を紹介し Appleのプラットフォームでの 役割について見ていきます 最後に 他のフレームワークと統合する SwiftUIの機能について説明します

    その前に SwiftUIとその利用について もう1つ紹介したいことがあります 飼っているペットの中でどの種類が一番か 議論になることがよくあります これをできるだけ客観的な方法で 解決したいと思いました どのペットが 最も上手に芸をできるかです 厳しいコンテストもあります

    このセッションでは SwiftUIを使って ペットとその芸を追跡し それを比較するアプリを作成します 何から始めればよいでしょう SwiftUIではビューから始まります ビューはユーザーインターフェイスの 基本的な構成要素であり SwiftUIでのあらゆる操作で重要です 画面に表示されるすべてのピクセルは 何らかの形でビューによって定義されます SwiftUIビューを特別なものにしている 特徴は3つあります 宣言型 コンポジション性 状態駆動型です ビューは宣言によって表現されます

    ユーザーインターフェイスに 表示したいビューを記述すると SwiftUIがその結果を生成します テキスト SF Symbolsを使った画像 ボタンなどのコントロールを生成できます

    このコードで作成される横方向スタックは アイコンとタイトルの 組み合わせによるラベル スペーサ テキストで構成されます この宣言型構文は 他のコンテナにも適用されます 例えばスクロールリストです

    このリストにはペットのコレクションが 追加され それぞれに横方向のスタックが作成されます すべてのペットが 「Whiskers」というわけではないので スタックのビューを更新して 代わりに 各ペットのプロパティを使用します

    このUIを作成するために必要なアクションを 記述する必要はありませんでした 例えば リストの行を追加したり 削除したりするアクションです

    これが宣言型プログラミングと 命令型プログラミングの違いです ペットに芸を教えたことがある方は 命令型コマンドになじみがあるでしょう

    命令型プログラミングと同様に ここでRufusに ホームランを打つ手順を 1つずつ指示できます Rufus 来なさい Rufus バットを持ちなさい Rufus 打席について などなど 各手順を記述します

    これに対し 宣言型のペットの芸では 起こってほしいことを記述し あらかじめ準備した犬にやらせます あとは必要なことを言うだけです 「Rufusにホームランを打ってほしい」 芸の一部をカスタマイズすることもできます 例えば Rufusが着るオーダーメイドのシャツを 用意したりなどです 宣言型と命令型のプログラミングは 相互排他的ではありません 宣言型コードでは そこに至る手順ではなく 期待される結果に集中することができます 命令型コードは 状態を変更する時や 既存の宣言型コンポーネントがない場合に 適しています SwiftUIはこれら両方に対応します この好例はボタンです ボタンは宣言によって ユーザーインターフェイスに追加され その宣言には タップされた時に実行する アクションが含まれます このアクションは命令型コードを使用して 変更を行います この例では 新しいペットをリストに追加します

    SwiftUIビューはUIの現在の状態が どうであるべきかという記述です 時間の経過とともに命令を受け取る 長期間存在するオブジェクトインスタンス ではありません これがSwiftUIビューが 値型である理由であり クラスではなく構造体で定義されます

    SwiftUIはこれらの記述を受け取り 効率的なデータ構造を作成して それらを表現します このデータ構造は バックグラウンドで維持されます そして様々な出力の作成に使用されます 例えば 画面に表示される要素や ジェスチャおよび ビューのインタラクティブな側面 そのアクセシビリティの表現などです ビューは宣言型記述にすぎないため 1つのビューを複数に分割しても アプリのパフォーマンスには影響しません 最適なパフォーマンスを得るために コードの整理方法を 犠牲にする必要はありません

    コンポジションは SwiftUI全体で使用され すべてのユーザーインターフェイスの 重要な要素です 先ほど作成した HStackはコンテナビューであり レイアウトを目的としています 横方向スタックに子を配置しています コンテナビューを使った調整や実験は SwiftUIではとても簡単です コード自体は 作成するビューの階層と似ています

    横方向スタックには 次の3つのビューがあります 画像 縦方向スタック スペーサです 縦方向スタック自体にも 2つのビューがあります ラベルとテキストです

    この構文はViewBuilderクロージャを使って コンテナの子を宣言します この例では HStackの イニシャライザを使っています これには ViewBuilder content パラメータがあります これはSwiftUIのすべてのコンテナビューで 使用されるパターンの1つです

    コンポジションはビューモディファイアと 呼ばれる別のSwiftUIパターンで 重要な役割を果たします ビューモディファイアは ベースビューに修飾を適用し そのビューのあらゆる側面を変更できます Whiskerのかわいい写真から 始めましょう まず円形にクリップし 影を加え 上に緑色の境界線を重ねます 彼女の好きな色です

    構文的にはコンテナビューと 大きく異なるように見えますが 結果としては同じような 階層型構造になります 階層と効果の順序は モディファイアの順序に基づいて 定義されています 複数のモディファイアをつなぐことで 結果を生成する手順と そのカスタマイズ方法が明確になります すべて読みやすい構文で 構成されています

    ビューの階層はカスタムビューと ビューモディファイアにカプセル化できます カスタムビューはViewプロトコルに準拠し 表現するビューを返す bodyプロパティがあります ボディから返されたビューは 先ほどと同じ ビュー構築構文を使用することで 同じコンポジション機能と 迅速な反復処理を可能にします 追加のビューのプロパティを作成して 希望に合わせてコードを 整理することができます プロファイル画像の構築を リファクタリングして プライベートビュープロパティにしました

    このように段階的に進めていくことで 行のビューを 希望通りに繰り返し作成できます

    カスタムビューにはボディの作成方法を 変更する入力を設定できます この行に示される ペットのプロパティを追加して そのプロパティをボディから返される ビューで使用しました

    この変更により 同じビューを再利用して Whiskersに関する情報を 表示できます RoofusとBubblesも表示できます

    カスタムビューは 他のビューと同様に使用できます ここではペットごとに作成するビューとして リストで使用しました

    リストはビューコンポジションの力を よく表しています このリストイニシャライザには コレクション引数があります ForEachビューを作成するのに便利です ForEachはコレクションに含まれる 要素ごとにビューを生成し それらをコンテナに提供します このビューベースの リストイニシャライザにより より高度な構造を作成できます 例えば セクションごとにまとめられた 複数のデータコレクションです 1つは自分のペット用 もう1つは他のユーザーのペット用です

    リストはビューモディファイアを使用して カスタマイズすることもできます 例えば各行に スワイプアクションを追加します

    追加のコンテナとモディファイアの コンポジションによって アプリ全体を少しずつ構築できます

    SwiftUIのビューの3つ目の特性は 状態駆動型であることです 時間の経過とともに ビューの状態が変化した時 SwiftUIによってUIが自動的に 最新の状態に保たれ 定型文とアップデートの 両方のバグを排除できます SwiftUIは内部で実行されている ユーザーインターフェイスの 表現を維持します データが変化すると 新しいビュー値が作成され SwiftUIに渡されます SwiftUIはこれらの値を使用して 出力の更新方法を決定します これでペットとその芸のリストが アプリに追加されました しかしペットコンテストで 最も重要なのは 最高の芸を持つペットに 報酬を与えることです これはSheldonです 報酬としてイチゴを欲しがっています 各行にスワイプアクションを追加しました 準備は整っています

    ボタンをタップすると アクションが呼び出されます 関連するペットオブジェクトが変更され hasAwardがtrueに変更されます

    SwiftUIはこのペットに依存する ビューを追跡します 例えば行ビューです

    ペットへの参照が含まれており そのボディから ペットが報酬を受けたかどうかを読み取り 依存関係を確立します

    SwiftUIは更新されたペットで このビューのボディを再度呼び出します

    画像を含む結果が返されるようになり Sheldonの報酬を反映します

    SwiftUIはこの結果に基づいて 出力を更新し 新しい画像を画面に表示します

    ビューがボディで使用するデータはすべて そのビューの依存関係です このアプリでは Observablepetクラスを作成しました SwiftUIはビューボディで使用される 特定のプロパティへの 依存関係を作成します SwiftUIには状態を管理する 複数のツールがあります その他にStateとBindingの 2つが重要です Stateはビューに対して データの新しい内部ソースを作成します ビューのプロパティを @Stateとしてマークすると SwiftUIはそのストレージを管理し それを返して ビューが読み書きできるようにします

    Bindingは他のビューの状態に対する 双方向の参照を作成します

    これらを使用する別のビューも 作成しました このビューでは ペットの芸を評価できます Stateを使って 現在の評価を追跡し 時間の経過とともに変更します 値は中央に目立つように表示され 値を増減するボタンが2つ付いています

    SwiftUIはこの状態の値を 内部で管理します

    先ほどの例と同様 ボタンがタップされると そのアクションが呼び出されます 今回は ビューの内部のStateを増やします

    SwiftUIはこの変化に気づき RatingViewのボディを呼び出します 新しいテキスト値が返され 画面上の結果が更新されます

    ボディのビューに注目します ここでは状態の変更が行われます

    今のところすぐに変更が行われています アニメーションはありません SwiftUIのアニメーションは 既に説明したのと同じ データ駆動型更新に基づいて 構築されます

    withAnimationで この状態変更をラップすると その結果のビュー更新はデフォルトの アニメーションと共に適用されます

    SwiftUIはデフォルトの クロスフェードトランジションを テキストに適用しました トランジションを カスタマイズすることもできます

    この場合 数値テキストのコンテンツトランジションが 適しています

    状態とアニメーションで 必要なやり取りを行うカプセル化された ビューコンポーネントを作りました 最終的には アプリの他の部分についても このビューを作成します

    もう1つのビューは RatingContainerViewです これはボディでGaugeと RatingViewを組み合わせます

    現在 これらのビューには それぞれ独自の状態があり 独立した独自の正確なソースとして 役割を果たします しかし これは評価ビューが 自身の状態を増加させた時に コンテナビューの状態とゲージが 変化しないということです

    RatingViewを更新して 入力としてBindingを受け取り 双方向の参照がコンテナビューによって 提供できるようにします

    コンテナビューの状態が 信頼できる唯一のソースとなり Gaugeに値を提供し Bindingを RatingViewに提供します 同期して更新され アニメーション化された状態変化が ゲージにも適用されます

    SwiftUIには様々なレベルの 機能が組み込まれており 有利な条件で アプリの作成を開始できます

    私はペットやペットの芸を 追跡するアプリに取りかかり 今のところ満足しています SwiftUIは自動的に多くの側面で アダプティビティを提供します

    このアプリはダークモードでも 問題なく表示されます Dynamic Typeなどの アクセシビリティ機能も サポートされています ローカライズする準備もできています 例えば 右から左に書く 疑似言語でプレビューすると ヘブライ語やアラビア語で どのように表示されるかを確認できます

    これはXcodeプレビューの 優れた点の1つです 様々なコンテキストでどのように見えるか 一目でわかります この処理はコードを記述する際に実行され アプリを繰り返し実行する必要はありません

    プレビューはインタラクティブで デバイス上で直接行えます 作業中の機能がどんな感じか正確に 理解できます それも作成している最中にです

    SwiftUIの宣言型ビューのメリットの1つは アダプティビティです SwiftUIの提供するビューでは その視覚的構造ではなく 機能の目的を記述することがよくあります

    先ほどスワイプアクションがボタンなどの ビューで構成されていることを紹介しました ボタンはアダプティブビューの好例です ボタンには2つの基本的な特性があります アクションと アクションを説明するラベルです

    これらは様々なコンテキストで 使用できますが ラベルの付いたアクションの目的は 常にそのままです

    これらは様々なスタイルに適応します 境界なし 境界付き 突出型などです 様々なコンテキストに自動的に適応します スワイプアクション メニュー フォームなどです このパターンはSwiftUIの すべてのコントロールに当てはまります トグルも同様です トグルには独自のスタイルがあります 例えば スイッチ チェックボックス トグルボタンがあります 異なるコンテキストでは 慣用的なスタイルで表示され オン/オフを切り替えるものを表現します

    SwiftUI全体のビューの多くは 同じアダプティビティがあり コンポジションを活かして 行動に影響を与え カスタマイズを可能にします これは一部のビューモディファイアにも 当てはまります 私の好きな例は検索機能です これはペットのリストに適用します

    searchableモディファイアを追加すると 適用先のビューが検索可能であると 記述していることになります SwiftUIはすべての詳細を担当し 慣用的な方法で実現します その他のモディファイアを 段階的に採用することで 体験をカスタマイズすることもできます 提案 スコープ トークンの追加などです

    SwiftUIの宣言ビューと アダプティブビューは わずか数行のコードに 多くの機能が詰め込まれています

    ボタン トグル ピッカーなどの コントロール NavigationSplitViewや カスタマイズ可能な 複数列テーブルなどのコンテナビュー シートやインスペクタなどの プレゼンテーションをはじめ 他にも使用できる例が ドキュメントに記述されています

    独自のカスタム体験を 作成する準備ができたら SwiftUIにはAPIの別の層もあり 低レベルコントロールを提供します 独自のコントロールスタイルを作成できます Canvasを使用して 高パフォーマンスの命令型描画を実現したり 完全にカスタマイズされた レイアウトを作成したり カスタムMetalシェーダを SwiftUIビューに直接適用したりできます

    私のアプリでは スコアボードが この低レベルツールを使用して 独自の体験を作成するのに 最適な場所でした 古典的なフリップボードを彷彿とさせます アニメーション グラフィックスのトリック いくつかのMetalシェーダを使いました

    Sheldonはこの最後の芸で 着地に失敗したので 7点を付けなければなりません 次はがんばってね

    SwiftUIの機能は ビューだけではありません アプリの定義全体が ビューの遵守する同じ原則に基づいて 構築されています アプリは宣言型構造体で シーンによって定義されます WindowGroupもシーンの一種です 画面に表示するコンテンツビューで 作成されます シーンを組み合わせて 構成することもできます

    macOSなどの マルチウインドウプラットフォームでは 追加のシーンによって様々な方法で アプリの機能を操作できます

    このパターンはカスタムウィジェットの 作成にも拡張できます ウィジェットは ホーム画面とデスクトップに表示され 複数のビューで構成されています スコアボードビューの一部を再利用して Sheldonの最新評価を表示します SwiftUIの機能はその動作する あらゆるプラットフォームに拡張され 成果を1つのプラットフォームにまとめたり 他のプラットフォームで ネイティブアプリを構築したりできます SwiftUIは どのAppleプラットフォーム向けの アプリを構築する場合でも利用できます。

    またSwiftUIは 努力の成果を何倍にもしてくれます 一度1つのプラットフォーム向けに ユーザーインターフェースを構築すれば そのUIを他のプラットフォームに持ち込む 素晴らしいスタートになります

    iOS用に私が作ったアプリは その好例です

    アダプティブビューとシーンが どのAppleプラットフォームでも 特有の見た目と操作性を実現します macOSの場合 キーボードでの操作や マルチウインドウの作成を 自動的にサポートします

    検索候補機能の使用方法も同じで macOSでは標準的な ドロップダウンメニューが表示され iOSではオーバーレイリストが 表示されます

    低レベルAPIによって作成された カスタムビューは どのプラットフォームでも 同じ結果が得られ 必要に応じて同じビューを 再利用するのに最適です

    すべてのプラットフォームを対象に スコアボードのアニメーションを 作成できました

    SwiftUIではこのように コードを共有できますが 「一度記述すれば どこでも実行できる」 わけではありません 一度学べば どのコンテキストでも どのAppleプラットフォームでも 使用できるツールセットということです

    SwiftUIにはプラットフォーム間で 共通の高レベルと低レベルの コンポーネントがありますが プラットフォームごとに 専用のAPIもあります

    プラットフォームごとに使い方が異なり デザインも異なります

    ヒューマンインターフェイス ガイドラインには コンポーネント パターン プラットフォームに関する 考慮事項が記述されています

    NavigationSplitViewは 詳細を表示するソースリストという watchOSのデザインに 自動的に適応します

    スコアボードなどのカスタムビューも 再利用しました しかし watchOSに特化した変更を1つ 加えたいと思います タッチやキーボードの代わりに Digital Crownを使って 評価を素早く 選択できるようにしたいのです

    同じスコアボードビュー上に watchOSのモディファイアを追加しました digitalCrownRotationです

    これでDigital Crownを回すと 希望のスコアを選択できます

    Macでペットとペットの芸を見直すと 過去のデータを詳しく調べて ペット間で比較したくなりました macOSの柔軟なウインドウ管理モデルを 様々なシーンタイプで活用できます 使い慣れたmacOSの コントロールライブラリ 情報密度 正確な入力を最大限に活用するビューを 使用することもできます

    このアプリを visionOSに移植して 他のプラットフォームからの ビューを利用して ボリュームのあるコンテンツを 追加することもできます SwiftUIはアプリがどこへ行ってもその品質を 高めるよう支援します これも段階的な性質を持っています SwiftUIアプリではプラットフォームを 複数サポートする必要がありません 準備ができた時は 有利に導入を開始できます 最後に取り上げる分野は SwiftUI自体の 組み込み機能ではありません 他のフレームワークの機能と SwiftUIの相互運用機能です SwiftUIは各プラットフォームの SDKに含まれています 他の様々なフレームワークも SDKに含まれており それぞれ独自の魅力的な 機能を備えています これらのフレームワークを すべて使うアプリはありませんが 必要なテクノロジーを提供する フレームワークを選んで使用できます

    SwiftUIはこの機能すべてに 相互運用性を提供し 多くの場合 別のビューやプロパティをアプリに 追加するのと同じくらい簡単です

    UIKitとAppKitは 命令型のオブジェクト指向 ユーザーインターフェイスフレームワークです SwiftUIと同様の 構成要素を提供しますが 様々なパターンを使用して ビューの作成と更新を行います 長年にわたって利用されてきた豊富な機能は SwiftUIの基盤となっています

    SwiftUIの基本機能はそれらとの シームレスな相互運用性です

    UIKitまたはAppKitからのビューや ビューコントローラがある場合で SwiftUIで使用したい場合は ViewRepresentableを作成できます

    これは特別な SwiftUIビュープロトコルであり 命令型コードを使用して 関連する UIKitビューまたはAppKitビューを 作成および更新します

    その結果SwiftUIの宣言型ビュービルダーで 使用できるビューが生成され HStackで使用するなど 他のビューと同じように使用できます 逆もまた同じです UIKitまたはAppKitビュー階層に SwiftUIビューを埋め込みたい場合 Hosting View Controllerなどの クラスを使用できます これはルートSwiftUIビューで作成され UIKitまたはAppKitビューコントローラ階層に 追加できます Apple所有のアプリは これらのツールを使用して 段階的にSwiftUIを取り入れます SwiftUIを 既存のアプリに活用する場合も まったく新しいSwiftUIアプリを作成して Kitビューを組み込む場合でも同様です これらのツールは すべてツールボックスにあり 素晴らしいアプリを作成できます アプリが完全にSwiftUIになって SwiftUIを活用しなければならない ということは想定されていません

    SDKのすべてのフレームワークには 独自の機能があります SwiftDataを使うと 永続性モデルアプリに素早く追加でき SwiftUIビューを使って それらのモデルに接続して照会するための APIが含まれています

    Swift Chartsは高度にカスタマイズ可能な グラフ作成フレームワークです SwiftUIをベースに 情報の美しい視覚表現を 簡単に作成できるようにします

    これらのフレームワークはすべて 優れたアプリの作成に 役立てることができます SwiftUIは宣言型 コンポジション性 状態駆動型のビューを基盤として 構築されています その上にプラットフォームに慣用的な機能と 幅広いSDKとの統合を提供します これらはすべてアプリの独自性を 高めることに集中する上で役立ちます より少ないコードで作成できます 慣用的で魅力的なアプリにつながる 幅広いコンポーネントを提供しています また あらゆるステップで段階的に 導入することができます

    SwiftUIを始めてみましょう Xcodeを起動して 最初のアプリを作成してみましょう 既存のアプリに SwiftUIを組み込むこともできます SwiftUIに関する その他のセッションもご覧ください 次は「Introduction to SwiftUI」を お勧めします

    SwiftUIのチュートリアルに沿って 進めましょう 様々なアプリの作成方法を 紹介しています ドキュメントにはこの他にも 様々なメリットがあります

    ペットコンテストについて言えば どのペットが一番かを アプリで決めるのは難しそうです 今のところの結論は どのペットも愛すべき存在です

    • 2:30 - Declarative views

      Text("Whiskers")
      
      Image(systemName: "cat.fill")
      
      Button("Give Treat") {
         // Give Whiskers a treat
      }
    • 2:43 - Declarative views: layout

      HStack {
         Label("Whiskers", systemImage: "cat.fill")
      
         Spacer()
      
         Text("Tightrope walking")
      }
    • 2:56 - Declarative views: list

      struct ContentView: View {
          @State private var pets = Pet.samplePets
      
          var body: some View {
              List(pets) { pet in
                  HStack {
                      Label("Whiskers", systemImage: "cat.fill")
      
                      Spacer()
      
                      Text("Tightrope walking")
                  }
              }
          }
      }
      
      struct Pet: Identifiable {
          enum Kind {
              case cat
              case dog
              case fish
              case bird
              case lizard
              case turtle
              case rabbit
              case bug
      
              var systemImage: String {
                  switch self {
                  case .cat: return "cat.fill"
                  case .dog: return "dog.fill"
                  case .fish: return "fish.fill"
                  case .bird: return "bird.fill"
                  case .lizard: return "lizard.fill"
                  case .turtle: return "tortoise.fill"
                  case .rabbit: return "rabbit.fill"
                  case .bug: return "ant.fill"
                  }
              }
          }
      
          let id = UUID()
          var name: String
          var kind: Kind
          var trick: String
      
          init(_ name: String, kind: Kind, trick: String) {
              self.name = name
              self.kind = kind
              self.trick = trick
          }
      
          static let samplePets = [
              Pet("Whiskers", kind: .cat, trick: "Tightrope walking"),
              Pet("Roofus", kind: .dog, trick: "Home runs"),
              Pet("Bubbles", kind: .fish, trick: "100m freestyle"),
              Pet("Mango", kind: .bird, trick: "Basketball dunk"),
              Pet("Ziggy", kind: .lizard, trick: "Parkour"),
              Pet("Sheldon", kind: .turtle, trick: "Kickflip"),
              Pet("Chirpy", kind: .bug, trick: "Canon in D")
          ]
      }
    • 3:07 - Declarative views: list

      struct ContentView: View {
          @State private var pets = Pet.samplePets
      
          var body: some View {
              List(pets) { pet in
                  HStack {
                      Label(pet.name, systemImage: pet.kind.systemImage)
      
                      Spacer()
      
                      Text(pet.trick)
                  }
              }
          }
      }
      
      struct Pet: Identifiable {
          enum Kind {
              case cat
              case dog
              case fish
              case bird
              case lizard
              case turtle
              case rabbit
              case bug
      
              var systemImage: String {
                  switch self {
                  case .cat: return "cat.fill"
                  case .dog: return "dog.fill"
                  case .fish: return "fish.fill"
                  case .bird: return "bird.fill"
                  case .lizard: return "lizard.fill"
                  case .turtle: return "tortoise.fill"
                  case .rabbit: return "rabbit.fill"
                  case .bug: return "ant.fill"
                  }
              }
          }
      
          let id = UUID()
          var name: String
          var kind: Kind
          var trick: String
      
          init(_ name: String, kind: Kind, trick: String) {
              self.name = name
              self.kind = kind
              self.trick = trick
          }
      
          static let samplePets = [
              Pet("Whiskers", kind: .cat, trick: "Tightrope walking"),
              Pet("Roofus", kind: .dog, trick: "Home runs"),
              Pet("Bubbles", kind: .fish, trick: "100m freestyle"),
              Pet("Mango", kind: .bird, trick: "Basketball dunk"),
              Pet("Ziggy", kind: .lizard, trick: "Parkour"),
              Pet("Sheldon", kind: .turtle, trick: "Kickflip"),
              Pet("Chirpy", kind: .bug, trick: "Canon in D")
          ]
      }
    • 4:24 - Declarative and imperative programming

      struct ContentView: View {
          @State private var pets = Pet.samplePets
      
          var body: some View {
              Button("Add Pet") {
                  pets.append(Pet("Toby", kind: .dog, trick: "WWDC Presenter"))
              }
      
              List(pets) { pet in
                  HStack {
                      Label(pet.name, systemImage: pet.kind.systemImage)
      
                      Spacer()
      
                      Text(pet.trick)
                  }
              }
          }
      }
      
      struct Pet: Identifiable {
          enum Kind {
              case cat
              case dog
              case fish
              case bird
              case lizard
              case turtle
              case rabbit
              case bug
      
              var systemImage: String {
                  switch self {
                  case .cat: return "cat.fill"
                  case .dog: return "dog.fill"
                  case .fish: return "fish.fill"
                  case .bird: return "bird.fill"
                  case .lizard: return "lizard.fill"
                  case .turtle: return "tortoise.fill"
                  case .rabbit: return "rabbit.fill"
                  case .bug: return "ant.fill"
                  }
              }
          }
      
          let id = UUID()
          var name: String
          var kind: Kind
          var trick: String
      
          init(_ name: String, kind: Kind, trick: String) {
              self.name = name
              self.kind = kind
              self.trick = trick
          }
      
          static let samplePets = [
              Pet("Whiskers", kind: .cat, trick: "Tightrope walking"),
              Pet("Roofus", kind: .dog, trick: "Home runs"),
              Pet("Bubbles", kind: .fish, trick: "100m freestyle"),
              Pet("Mango", kind: .bird, trick: "Basketball dunk"),
              Pet("Ziggy", kind: .lizard, trick: "Parkour"),
              Pet("Sheldon", kind: .turtle, trick: "Kickflip"),
              Pet("Chirpy", kind: .bug, trick: "Canon in D")
          ]
      }
    • 5:33 - Layout container

      HStack {
         Label("Whiskers", systemImage: "cat.fill")
      
         Spacer()
      
         Text("Tightrope walking")
      }
    • 5:41 - Container views

      struct ContentView: View {
          var body: some View {
              HStack {
                  Image(whiskers.profileImage)
      
                  VStack(alignment: .leading) {
                      Label("Whiskers", systemImage: "cat.fill")
                      Text("Tightrope walking")
                  }
      
                  Spacer()
              }
          }
      }
      
      let whiskers = Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers")
      
      struct Pet: Identifiable {
          enum Kind {
              case cat
              case dog
              case fish
              case bird
              case lizard
              case turtle
              case rabbit
              case bug
      
              var systemImage: String {
                  switch self {
                  case .cat: return "cat.fill"
                  case .dog: return "dog.fill"
                  case .fish: return "fish.fill"
                  case .bird: return "bird.fill"
                  case .lizard: return "lizard.fill"
                  case .turtle: return "tortoise.fill"
                  case .rabbit: return "rabbit.fill"
                  case .bug: return "ant.fill"
                  }
              }
          }
      
          let id = UUID()
          var name: String
          var kind: Kind
          var trick: String
          var profileImage: String
      
          init(_ name: String, kind: Kind, trick: String, profileImage: String) {
              self.name = name
              self.kind = kind
              self.trick = trick
              self.profileImage = profileImage
          }
      }
    • 6:23 - View modifiers

      struct ContentView: View {
          var body: some View {
              Image(whiskers.profileImage)
                  .clipShape(.circle)
                  .shadow(radius: 3)
                  .overlay {
                      Circle().stroke(.green, lineWidth: 2)
                  }
          }
      }
      
      let whiskers = Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers")
      
      struct Pet: Identifiable {
          enum Kind {
              case cat
              case dog
              case fish
              case bird
              case lizard
              case turtle
              case rabbit
              case bug
      
              var systemImage: String {
                  switch self {
                  case .cat: return "cat.fill"
                  case .dog: return "dog.fill"
                  case .fish: return "fish.fill"
                  case .bird: return "bird.fill"
                  case .lizard: return "lizard.fill"
                  case .turtle: return "tortoise.fill"
                  case .rabbit: return "rabbit.fill"
                  case .bug: return "ant.fill"
                  }
              }
          }
      
          let id = UUID()
          var name: String
          var kind: Kind
          var trick: String
          var profileImage: String
      
          init(_ name: String, kind: Kind, trick: String, profileImage: String) {
              self.name = name
              self.kind = kind
              self.trick = trick
              self.profileImage = profileImage
          }
      }
    • 7:05 - Custom views: Intro

      struct PetRowView: View {
          var body: some View {
             // ...
          }
      }
    • 7:14 - Custom views

      struct PetRowView: View {
          var body: some View {
              Image(whiskers.profileImage)
                  .clipShape(.circle)
                  .shadow(radius: 3)
                  .overlay {
                      Circle().stroke(.green, lineWidth: 2)
                  }
          }
      }
      
      let whiskers = Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers")
      
      struct Pet: Identifiable {
          enum Kind {
              case cat
              case dog
              case fish
              case bird
              case lizard
              case turtle
              case rabbit
              case bug
      
              var systemImage: String {
                  switch self {
                  case .cat: return "cat.fill"
                  case .dog: return "dog.fill"
                  case .fish: return "fish.fill"
                  case .bird: return "bird.fill"
                  case .lizard: return "lizard.fill"
                  case .turtle: return "tortoise.fill"
                  case .rabbit: return "rabbit.fill"
                  case .bug: return "ant.fill"
                  }
              }
          }
      
          let id = UUID()
          var name: String
          var kind: Kind
          var trick: String
          var profileImage: String
      
          init(_ name: String, kind: Kind, trick: String, profileImage: String) {
              self.name = name
              self.kind = kind
              self.trick = trick
              self.profileImage = profileImage
          }
      }
    • 7:20 - Custom views: iteration

      struct PetRowView: View {
          var body: some View {
              HStack {
                  Image(whiskers.profileImage)
                      .clipShape(.circle)
                      .shadow(radius: 3)
                      .overlay {
                          Circle()
                              .stroke(.green, lineWidth: 2)
                      }
      
                  Text("Whiskers")
      
                  Spacer()
              }
          }
      }
      
      let whiskers = Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers")
      
      struct Pet: Identifiable {
          enum Kind {
              case cat
              case dog
              case fish
              case bird
              case lizard
              case turtle
              case rabbit
              case bug
      
              var systemImage: String {
                  switch self {
                  case .cat: return "cat.fill"
                  case .dog: return "dog.fill"
                  case .fish: return "fish.fill"
                  case .bird: return "bird.fill"
                  case .lizard: return "lizard.fill"
                  case .turtle: return "tortoise.fill"
                  case .rabbit: return "rabbit.fill"
                  case .bug: return "ant.fill"
                  }
              }
          }
      
          let id = UUID()
          var name: String
          var kind: Kind
          var trick: String
          var profileImage: String
      
          init(_ name: String, kind: Kind, trick: String, profileImage: String) {
              self.name = name
              self.kind = kind
              self.trick = trick
              self.profileImage = profileImage
          }
      }
    • 7:24 - Custom views: view properties

      struct PetRowView: View {
          var body: some View {
              HStack {
                  profileImage
      
                  Text("Whiskers")
      
                  Spacer()
              }
          }
      
          private var profileImage: some View {
              Image(whiskers.profileImage)
                  .clipShape(.circle)
                  .shadow(radius: 3)
                  .overlay {
                      Circle().stroke(.green, lineWidth: 2)
                  }
          }
      }
      
      let whiskers = Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers")
      
      struct Pet: Identifiable {
          enum Kind {
              case cat
              case dog
              case fish
              case bird
              case lizard
              case turtle
              case rabbit
              case bug
      
              var systemImage: String {
                  switch self {
                  case .cat: return "cat.fill"
                  case .dog: return "dog.fill"
                  case .fish: return "fish.fill"
                  case .bird: return "bird.fill"
                  case .lizard: return "lizard.fill"
                  case .turtle: return "tortoise.fill"
                  case .rabbit: return "rabbit.fill"
                  case .bug: return "ant.fill"
                  }
              }
          }
      
          let id = UUID()
          var name: String
          var kind: Kind
          var trick: String
          var profileImage: String
      
          init(_ name: String, kind: Kind, trick: String, profileImage: String) {
              self.name = name
              self.kind = kind
              self.trick = trick
              self.profileImage = profileImage
          }
      }
    • 7:34 - Custom views: complete row view

      struct PetRowView: View {
          var body: some View {
              HStack {
                  profileImage
      
                  VStack(alignment: .leading) {
                      Text("Whiskers")
                      Text("Tightrope walking")
                          .font(.subheadline)
                          .foregroundStyle(.secondary)
                  }
      
                  Spacer()
              }
          }
      
          private var profileImage: some View {
              Image(whiskers.profileImage)
                  .clipShape(.circle)
                  .shadow(radius: 3)
                  .overlay {
                      Circle().stroke(.green, lineWidth: 2)
                  }
          }
      }
      
      let whiskers = Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers")
      
      struct Pet: Identifiable {
          enum Kind {
              case cat
              case dog
              case fish
              case bird
              case lizard
              case turtle
              case rabbit
              case bug
      
              var systemImage: String {
                  switch self {
                  case .cat: return "cat.fill"
                  case .dog: return "dog.fill"
                  case .fish: return "fish.fill"
                  case .bird: return "bird.fill"
                  case .lizard: return "lizard.fill"
                  case .turtle: return "tortoise.fill"
                  case .rabbit: return "rabbit.fill"
                  case .bug: return "ant.fill"
                  }
              }
          }
      
          let id = UUID()
          var name: String
          var kind: Kind
          var trick: String
          var profileImage: String
      
          init(_ name: String, kind: Kind, trick: String, profileImage: String) {
              self.name = name
              self.kind = kind
              self.trick = trick
              self.profileImage = profileImage
          }
      }
    • 7:41 - Custom views: input properties

      struct PetRowView: View {
          var pet: Pet
          var body: some View {
              HStack {
                  profileImage
      
                  VStack(alignment: .leading) {
                      Text(pet.name)
                      Text(pet.trick)
                          .font(.subheadline)
                          .foregroundStyle(.secondary)
                  }
      
                  Spacer()
              }
          }
      
          private var profileImage: some View {
              Image(pet.profileImage)
                  .clipShape(.circle)
                  .shadow(radius: 3)
                  .overlay {
                      Circle().stroke(pet.favoriteColor, lineWidth: 2)
                  }
          }
      }
      
      struct Pet: Identifiable {
          enum Kind {
              case cat
              case dog
              case fish
              case bird
              case lizard
              case turtle
              case rabbit
              case bug
      
              var systemImage: String {
                  switch self {
                  case .cat: return "cat.fill"
                  case .dog: return "dog.fill"
                  case .fish: return "fish.fill"
                  case .bird: return "bird.fill"
                  case .lizard: return "lizard.fill"
                  case .turtle: return "tortoise.fill"
                  case .rabbit: return "rabbit.fill"
                  case .bug: return "ant.fill"
                  }
              }
          }
      
          let id = UUID()
          var name: String
          var kind: Kind
          var trick: String
          var profileImage: String
          var favoriteColor: Color
      
          init(_ name: String, kind: Kind, trick: String, profileImage: String, favoriteColor: Color) {
              self.name = name
              self.kind = kind
              self.trick = trick
              self.profileImage = profileImage
              self.favoriteColor = favoriteColor
          }
      }
    • 7:53 - Custom views: reuse

      PetRowView(pet: model.pet(named: "Whiskers"))
      
      PetRowView(pet: model.pet(named: "Roofus"))
      
      PetRowView(pet: model.pet(named: "Bubbles"))
    • 7:59 - List composition

      struct ContentView: View {
          var model: PetStore
          var body: some View {
              List(model.allPets) { pet in
                  PetRowView(pet: pet)
              }
          }
      }
      
      @Observable
      class PetStore {
          var allPets: [Pet] = [
              Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers", favoriteColor: .green),
              Pet("Roofus", kind: .dog, trick: "Home runs", profileImage: "Roofus", favoriteColor: .blue),
              Pet("Bubbles", kind: .fish, trick: "100m freestyle", profileImage: "Bubbles", favoriteColor: .orange),
              Pet("Mango", kind: .bird,  trick: "Basketball dunk", profileImage: "Mango", favoriteColor: .green),
              Pet("Ziggy", kind: .lizard, trick: "Parkour", profileImage: "Ziggy", favoriteColor: .purple),
              Pet("Sheldon", kind: .turtle, trick: "Kickflip", profileImage: "Sheldon", favoriteColor: .brown),
              Pet("Chirpy", kind: .bug, trick: "Canon in D", profileImage: "Chirpy", favoriteColor: .orange)
          ]
      }
    • 8:14 - List composition: ForEach

      struct ContentView: View {
          var model: PetStore
          var body: some View {
              List {
                  ForEach(model.allPets) { pet in
                      PetRowView(pet: pet)
                  }
              }
          }
      }
      
      @Observable
      class PetStore {
          var allPets: [Pet] = [
              Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers", favoriteColor: .green),
              Pet("Roofus", kind: .dog, trick: "Home runs", profileImage: "Roofus", favoriteColor: .blue),
              Pet("Bubbles", kind: .fish, trick: "100m freestyle", profileImage: "Bubbles", favoriteColor: .orange),
              Pet("Mango", kind: .bird,  trick: "Basketball dunk", profileImage: "Mango", favoriteColor: .green),
              Pet("Ziggy", kind: .lizard, trick: "Parkour", profileImage: "Ziggy", favoriteColor: .purple),
              Pet("Sheldon", kind: .turtle, trick: "Kickflip", profileImage: "Sheldon", favoriteColor: .brown),
              Pet("Chirpy", kind: .bug, trick: "Canon in D", profileImage: "Chirpy", favoriteColor: .orange)
          ]
      }
    • 8:27 - List composition: sections

      struct ContentView: View {
          var model: PetStore
          var body: some View {
              List {
                  Section("My Pets") {
                      ForEach(model.myPets) { pet in
                          PetRowView(pet: pet)
                      }
                  }
                  Section("Other Pets") {
                      ForEach(model.otherPets) { pet in
                          PetRowView(pet: pet)
                      }
                  }
              }
          }
      }
      
      @Observable
      class PetStore {
          var myPets: [Pet] = [
              Pet("Roofus", kind: .dog, trick: "Home runs", profileImage: "Roofus", favoriteColor: .blue),
              Pet("Sheldon", kind: .turtle, trick: "Kickflip", profileImage: "Sheldon", favoriteColor: .brown),
          ]
      
          var otherPets: [Pet] = [
              Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers", favoriteColor: .green),
              Pet("Bubbles", kind: .fish, trick: "100m freestyle", profileImage: "Bubbles", favoriteColor: .orange),
              Pet("Mango", kind: .bird,  trick: "Basketball dunk", profileImage: "Mango", favoriteColor: .green),
              Pet("Ziggy", kind: .lizard, trick: "Parkour", profileImage: "Ziggy", favoriteColor: .purple),
              Pet("Chirpy", kind: .bug, trick: "Canon in D", profileImage: "Chirpy", favoriteColor: .orange)
          ]
      }
    • 8:36 - List composition: section actions

      PetRowView(pet: pet)
          .swipeActions(edge: .leading) {
              Button("Award", systemImage: "trophy") {
                  // Give pet award
              }
              .tint(.orange)
      
              ShareLink(item: pet, preview: SharePreview("Pet", image: Image(pet.name)))
          }
    • 9:31 - View updates

      struct ContentView: View {
          var model: PetStore
          var body: some View {
              List {
                  Section("My Pets") {
                      ForEach(model.myPets) { pet in
                          row(pet: pet)
                      }
                  }
                  Section("Other Pets") {
                      ForEach(model.otherPets) { pet in
                          row(pet: pet)
                      }
                  }
              }
          }
      
          private func row(pet: Pet) -> some View {
              PetRowView(pet: pet)
                  .swipeActions(edge: .leading) {
                      Button("Award", systemImage: "trophy") {
                          pet.giveAward()
                      }
                      .tint(.orange)
      
                      ShareLink(item: pet, preview: SharePreview("Pet", image: Image(pet.name)))
                  }
          }
      }
      
      struct PetRowView: View {
          var pet: Pet
          var body: some View {
              HStack {
                  profileImage
      
                  VStack(alignment: .leading) {
                      HStack(alignment: .firstTextBaseline) {
                          Text(pet.name)
      
                          if pet.hasAward {
                              Image(systemName: "trophy.fill")
                                  .foregroundStyle(.orange)
                          }
                      }
                      Text(pet.trick)
                          .font(.subheadline)
                          .foregroundStyle(.secondary)
                  }
      
                  Spacer()
              }
          }
      
          private var profileImage: some View {
              Image(pet.profileImage)
                  .clipShape(.circle)
                  .shadow(radius: 3)
                  .overlay {
                      Circle().stroke(pet.favoriteColor, lineWidth: 2)
                  }
          }
      }
      
      @Observable
      class PetStore {
          var myPets: [Pet] = [
              Pet("Roofus", kind: .dog, trick: "Home runs", profileImage: "Roofus", favoriteColor: .blue),
              Pet("Sheldon", kind: .turtle, trick: "Kickflip", profileImage: "Sheldon", favoriteColor: .brown),
          ]
      
          var otherPets: [Pet] = [
              Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers", favoriteColor: .green),
              Pet("Bubbles", kind: .fish, trick: "100m freestyle", profileImage: "Bubbles", favoriteColor: .orange),
              Pet("Mango", kind: .bird,  trick: "Basketball dunk", profileImage: "Mango", favoriteColor: .green),
              Pet("Ziggy", kind: .lizard, trick: "Parkour", profileImage: "Ziggy", favoriteColor: .purple),
              Pet("Chirpy", kind: .bug, trick: "Canon in D", profileImage: "Chirpy", favoriteColor: .orange)
          ]
      }
      
      @Observable
      class Pet: Identifiable {
          enum Kind {
              case cat
              case dog
              case fish
              case bird
              case lizard
              case turtle
              case rabbit
              case bug
      
              var systemImage: String {
                  switch self {
                  case .cat: return "cat.fill"
                  case .dog: return "dog.fill"
                  case .fish: return "fish.fill"
                  case .bird: return "bird.fill"
                  case .lizard: return "lizard.fill"
                  case .turtle: return "tortoise.fill"
                  case .rabbit: return "rabbit.fill"
                  case .bug: return "ant.fill"
                  }
              }
          }
      
          var name: String
          var kind: Kind
          var trick: String
          var profileImage: String
          var favoriteColor: Color
          var hasAward: Bool = false
      
          init(_ name: String, kind: Kind, trick: String, profileImage: String, favoriteColor: Color) {
              self.name = name
              self.kind = kind
              self.trick = trick
              self.profileImage = profileImage
              self.favoriteColor = favoriteColor
          }
      
          func giveAward() {
              hasAward = true
          }
      }
      
      extension Pet: Transferable {
          static var transferRepresentation: some TransferRepresentation {
              ProxyRepresentation { $0.name }
          }
      }
    • 10:57 - State changes

      struct RatingView: View {
          @State var rating: Int = 5
      
          var body: some View {
              HStack {
                  Button("Decrease", systemImage: "minus.circle") {
                      rating -= 1
                  }
                  .disabled(rating == 0)
                  .labelStyle(.iconOnly)
      
                  Text(rating, format: .number.precision(.integerLength(2)))
                      .font(.title.bold())
      
                  Button("Increase", systemImage: "plus.circle") {
                      rating += 1
                  }
                  .disabled(rating == 10)
                  .labelStyle(.iconOnly)
              }
          }
      }
    • 11:51 - State changes: animation

      struct RatingView: View {
          @State var rating: Int = 5
      
          var body: some View {
              HStack {
                  Button("Decrease", systemImage: "minus.circle") {
                      withAnimation {
                          rating -= 1
                      }
                  }
                  .disabled(rating == 0)
                  .labelStyle(.iconOnly)
      
                  Text(rating, format: .number.precision(.integerLength(2)))
                      .font(.title.bold())
      
                  Button("Increase", systemImage: "plus.circle") {
                      withAnimation {
                          rating += 1
                      }
                  }
                  .disabled(rating == 10)
                  .labelStyle(.iconOnly)
              }
          }
      }
    • 12:05 - State changes: text content transition

      struct RatingView: View {
          @State var rating: Int = 5
      
          var body: some View {
              HStack {
                  Button("Decrease", systemImage: "minus.circle") {
                      withAnimation {
                          rating -= 1
                      }
                  }
                  .disabled(rating == 0)
                  .labelStyle(.iconOnly)
      
                  Text(rating, format: .number.precision(.integerLength(2)))
                      .contentTransition(.numericText(value: Double(rating)))
                      .font(.title.bold())
      
                  Button("Increase", systemImage: "plus.circle") {
                      withAnimation {
                          rating += 1
                      }
                  }
                  .disabled(rating == 10)
                  .labelStyle(.iconOnly)
              }
          }
      }
    • 12:22 - State changes: multiple state

      struct RatingContainerView: View {
          @State private var rating: Int = 5
      
          var body: some View {
              Gauge(value: Double(rating), in: 0...10) {
                  Text("Rating")
              }
              RatingView()
          }
      }
      
      struct RatingView: View {
          @State var rating: Int = 5
      
          var body: some View {
              HStack {
                  Button("Decrease", systemImage: "minus.circle") {
                      withAnimation {
                          rating -= 1
                      }
                  }
                  .disabled(rating == 0)
                  .labelStyle(.iconOnly)
      
                  Text(rating, format: .number.precision(.integerLength(2)))
                      .contentTransition(.numericText(value: Double(rating)))
                      .font(.title.bold())
      
                  Button("Increase", systemImage: "plus.circle") {
                      withAnimation {
                          rating += 1
                      }
                  }
                  .disabled(rating == 10)
                  .labelStyle(.iconOnly)
              }
          }
      }
    • 12:45 - State changes: state and binding

      struct RatingContainerView: View {
          @State private var rating: Int = 5
      
          var body: some View {
              Gauge(value: Double(rating), in: 0...10) {
                  Text("Rating")
              }
              RatingView(rating: $rating)
          }
      }
      
      struct RatingView: View {
          @Binding var rating: Int
      
          var body: some View {
              HStack {
                  Button("Decrease", systemImage: "minus.circle") {
                      withAnimation {
                          rating -= 1
                      }
                  }
                  .disabled(rating == 0)
                  .labelStyle(.iconOnly)
      
                  Text(rating, format: .number.precision(.integerLength(2)))
                      .contentTransition(.numericText(value: Double(rating)))
                      .font(.title.bold())
      
                  Button("Increase", systemImage: "plus.circle") {
                      withAnimation {
                          rating += 1
                      }
                  }
                  .disabled(rating == 10)
                  .labelStyle(.iconOnly)
              }
          }
      }
    • 14:16 - Adaptive buttons

      Button("Reward", systemImage: "trophy") {
          // Give pet award
      }
      // .buttonStyle(.borderless)
      // .buttonStyle(.bordered)
      // .buttonStyle(.borderedProminent)
    • 14:53 - Adaptive toggles

      Toggle("Nocturnal Mode", systemImage: "moon", isOn: $pet.isNocturnal)
      // .toggleStyle(.switch)
      // .toggleStyle(.checkbox)
      // .toggleStyle(.button)
    • 15:19 - Searchable

      struct PetListView: View {
          @Bindable var viewModel: PetStoreViewModel
      
          var body: some View {
              List {
                  Section("My Pets") {
                      ForEach(viewModel.myPets) { pet in
                          row(pet: pet)
                      }
                  }
                  Section("Other Pets") {
                      ForEach(viewModel.otherPets) { pet in
                          row(pet: pet)
                      }
                  }
              }
              .searchable(text: $viewModel.searchText)
          }
      
          private func row(pet: Pet) -> some View {
              PetRowView(pet: pet)
                  .swipeActions(edge: .leading) {
                      Button("Reward", systemImage: "trophy") {
                          pet.giveAward()
                      }
                      .tint(.orange)
      
                      ShareLink(item: pet, preview: SharePreview("Pet", image: Image(pet.name)))
                  }
          }
      }
      
      @Observable
      class PetStoreViewModel {
          var petStore: PetStore
          var searchText: String = ""
      
          init(petStore: PetStore) {
              self.petStore = petStore
          }
      
          var myPets: [Pet] {
              // For illustration purposes only. The filtered pets should be cached.
              petStore.myPets.filter { searchText.isEmpty || $0.name.contains(searchText) }
          }
          var otherPets: [Pet] {
              // For illustration purposes only. The filtered pets should be cached.
              petStore.otherPets.filter { searchText.isEmpty || $0.name.contains(searchText) }
          }
      }
    • 15:20 - Searchable: customization

      struct PetListView: View {
          @Bindable var viewModel: PetStoreViewModel
      
          var body: some View {
              List {
                  Section("My Pets") {
                      ForEach(viewModel.myPets) { pet in
                          row(pet: pet)
                      }
                  }
                  Section("Other Pets") {
                      ForEach(viewModel.otherPets) { pet in
                          row(pet: pet)
                      }
                  }
              }
              .searchable(text: $viewModel.searchText, editableTokens: $viewModel.searchTokens) { $token in
                  Label(token.kind.name, systemImage: token.kind.systemImage)
              }
              .searchScopes($viewModel.searchScope) {
                  Text("All Pets").tag(PetStoreViewModel.SearchScope.allPets)
                  Text("My Pets").tag(PetStoreViewModel.SearchScope.myPets)
                  Text("Other Pets").tag(PetStoreViewModel.SearchScope.otherPets)
              }
              .searchSuggestions {
                  PetSearchSuggestions(viewModel: viewModel)
              }
          }
      
          private func row(pet: Pet) -> some View {
              PetRowView(pet: pet)
                  .swipeActions(edge: .leading) {
                      Button("Reward", systemImage: "trophy") {
                          pet.giveAward()
                      }
                      .tint(.orange)
      
                      ShareLink(item: pet, preview: SharePreview("Pet", image: Image(pet.name)))
                  }
          }
      }
    • 16:58 - App definition

      @main
      struct SwiftUIEssentialsApp: App {
          var body: some Scene {
              WindowGroup {
                  ContentView()
              }
          }
      }
    • 17:15 - App definition: multiple scenes

      @main
      struct SwiftUIEssentialsApp: App {
          var body: some Scene {
              WindowGroup {
                  ContentView()
              }
      
              WindowGroup("Training History", id: "history", for: TrainingHistory.ID.self) { $id in
                  TrainingHistoryView(historyID: id)
              }
      
              WindowGroup("Pet Detail", id: "detail", for: Pet.ID.self) { $id in
                  PetDetailView(petID: id)
              }
         }
      }
    • 17:23 - Widgets

      struct ScoreboardWidget: Widget {
          var body: some WidgetConfiguration {
              // ...
          }
      }
      
      struct ScoreboardWidgetView: View {
          var petTrick: PetTrick
          
          var body: some View {
              ScoreCard(rating: petTrick.rating)
                  .overlay(alignment: .bottom) {
                      Text(petTrick.pet.name)
                          .padding()
                  }
                  .widgetURL(petTrick.pet.url)
          }
      }
    • 19:37 - Digital Crown rotation

      ScoreCardStack(rating: $rating)
         .focusable()
         #if os(watchOS)
         .digitalCrownRotation($rating, from: 0, through: 10)
         #endif

Developer Footer

  • ビデオ
  • WWDC24
  • SwiftUIの基本
  • メニューを開く メニューを閉じる
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    メニューを開く メニューを閉じる
    • アクセシビリティ
    • アクセサリ
    • App Extension
    • App Store
    • オーディオとビデオ(英語)
    • 拡張現実
    • デザイン
    • 配信
    • 教育
    • フォント(英語)
    • ゲーム
    • ヘルスケアとフィットネス
    • アプリ内課金
    • ローカリゼーション
    • マップと位置情報
    • 機械学習
    • オープンソース(英語)
    • セキュリティ
    • SafariとWeb(英語)
    メニューを開く メニューを閉じる
    • 英語ドキュメント(完全版)
    • 日本語ドキュメント(一部トピック)
    • チュートリアル
    • ダウンロード(英語)
    • フォーラム(英語)
    • ビデオ
    Open Menu Close Menu
    • サポートドキュメント
    • お問い合わせ
    • バグ報告
    • システム状況(英語)
    メニューを開く メニューを閉じる
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles(英語)
    • フィードバックアシスタント
    メニューを開く メニューを閉じる
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program(英語)
    • News Partner Program(英語)
    • Video Partner Program(英語)
    • セキュリティ報奨金プログラム(英語)
    • Security Research Device Program(英語)
    Open Menu Close Menu
    • Appleに相談
    • Apple Developer Center
    • App Store Awards(英語)
    • Apple Design Awards
    • Apple Developer Academy(英語)
    • WWDC
    Apple Developerアプリを入手する
    Copyright © 2025 Apple Inc. All rights reserved.
    利用規約 プライバシーポリシー 契約とガイドライン