View in English

  • Apple Developer
    • 今すぐ始める

    「今すぐ始める」を詳しく見る

    • 概要
    • 学ぶ
    • Apple Developer Program

    最新情報

    • 最新ニュース
    • Hello Developer
    • プラットフォーム

    プラットフォームを詳しく見る

    • Appleプラットフォーム
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    • App Store

    特集

    • デザイン
    • 配信
    • ゲーム
    • アクセサリ
    • Web
    • Home
    • CarPlay
    • テクノロジー

    テクノロジーを詳しく見る

    • 概要
    • Xcode
    • Swift
    • SwiftUI

    特集

    • アクセシビリティ
    • App Intent
    • Apple Intelligence
    • ゲーム
    • 機械学習とAI
    • セキュリティ
    • Xcode Cloud
    • コミュニティ

    コミュニティを詳しく見る

    • 概要
    • 「Appleに相談」イベント
    • コミュニティによるイベント
    • デベロッパフォーラム
    • オープンソース

    特集

    • WWDC
    • Swift Student Challenge
    • デベロッパストーリー
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Center
    • ドキュメント

    ドキュメントを詳しく見る

    • ドキュメントライブラリ
    • テクノロジー概要
    • サンプルコード
    • ヒューマンインターフェイスガイドライン
    • ビデオ

    リリースノート

    • 注目のアップデート
    • iOS
    • iPadOS
    • macOS
    • watchOS
    • visionOS
    • tvOS
    • Xcode
    • ダウンロード

    ダウンロードを詳しく見る

    • すべてのダウンロード
    • オペレーティングシステム
    • アプリ
    • デザインリソース

    特集

    • Xcode
    • TestFlight
    • フォント
    • SF Symbols
    • Icon Composer
    • サポート

    サポートを詳しく見る

    • 概要
    • ヘルプガイド
    • デベロッパフォーラム
    • フィードバックアシスタント
    • お問い合わせ

    特集

    • アカウントヘルプ
    • App Reviewガイドライン
    • App Store Connectヘルプ
    • 近日導入予定の要件
    • 契約およびガイドライン
    • システムステータス
  • クイックリンク

    • イベント
    • ニュース
    • Forum
    • サンプルコード
    • ビデオ
 

ビデオ

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

その他のビデオ

  • 概要
  • Summary
  • トランスクリプト
  • コード
  • Code Along:SwiftDataによる永続性の追加

    SwiftData活用の実例を確認しながら、既存のアプリに永続性を追加する方法を学びましょう。データモデルを定義し、永続化したデータをSwiftUIとシームレスに統合する方法をお見せします。また、この表現力に優れた宣言型のAPIを使ってアプリの状態を管理するための基本的なスキルも学びます。

    関連する章

    • 0:00 - Introduction
    • 1:05 - Identify relevant state
    • 3:17 - Define your schemas
    • 9:41 - Define model relationships
    • 13:33 - Update the view layer
    • 21:47 - Next steps

    リソース

    • Wishlist: Planning travel in a SwiftUI app
    • SwiftData
      • HDビデオ
      • SDビデオ

    関連ビデオ

    WWDC26

    • SwiftDataの新機能

    WWDC25

    • SwiftData:継承とスキーマの移行の詳細
  • このビデオを検索

    こんにちは Matthew Turkです SwiftDataチームのエンジニアです。 今日は既存のSwiftUIアプリの 動的データを使って Appleのすべてのプラットフォームで 動作する最新の永続化レイヤに SwiftDataで接続する 方法を紹介します。 まずWishlistというリストベースの アプリのソースコードから始めます。 Wishlistはアイデアを記録して 旅行計画をスマートフォンで整理するアプリで 旅行を季節ごとの コレクションにまとめられます。 developer.apple.comからサンプルアプリを ダウンロードして試してみてください このビデオではプロジェクトファイルを 確認してデータ型を特定し SwiftDataのモデルとスキーマに 使う変数を確認します それらのモデルがデータベース内で どう関連すべきかや SwiftUIビューをどのように 更新するかも説明します パフォーマンスを意識しながら 新しいモデルを表示するために 相互運用性や拡張性を考慮した より複雑なユースケースに対応します。

    実際のデータフローを プレビューしてみましょう。

    Wishlistタブでは 上部に最近の旅行が表示されます テーマ別の旅行リストが 下にスクロールして続きます。

    Goalsタブでは さまざまなゴールのバッジが確認でき それらの旅行で達成したい ゴールが表示されます。 Searchタブでは旅行や アクティビティを絞り込めます。 海岸沿いのトレイルを 検索してみましょう。 上位の結果です。

    この旅行には5つの アクティビティが残っています。 Point Reyesには以前訪れたことがあり 11頭のエルクを見たので これをチェックオフします。

    Wishlistタブに戻り プラスボタンで新しい旅行を追加します。 いつかオーロラを また見てみたいです。 旅行名を"Northern Lights"にして 写真を選びます。

    を押せば 新しい旅行の追加完了です。

    このデータフローが可能なのは Wishlist内のビューが SwiftUI環境を通じて DataSource変数を取得するためです。 DataSourceクラスはプリインストールされた すべての旅行データを管理します。 旅行 ゴール 検索結果はすべて オンデマンドでメモリ内で処理されます。 小規模な例では これで問題ありません。 ですが実際には 最適化が必要になります アプリのフロント部分を 軽量に保つためにです。 さらにWishlistはRAMに依存して データを処理・保存しており このデータを処理・保存するのに 使われていますが 後で新しい旅行やアクティビティを 保存できる安定した場所ではありません。

    アプリを閉じて再起動すると

    新しい旅行のすべてが消えてしまい プリインストールのコンテンツに リセットされます。

    しかしこのままである必要はありません。 これはまさにSwiftDataが 解決する問題です。 ここまでで関連するstateを 特定しました 旅行コレクション ゴールの状態 検索結果などです。 SwiftDataはそのstateを ModelContextを通じて 永続ストレージに接続できます このビデオの最後には そこまで到達します。

    次はコード内でこれらの データ構造の場所を見つけて SwiftDataスキーマを使ったモデルに リファクタリングします。 これによりインメモリの DataSourceを置き換えて 永続化したModelContextに 移行できます ビューを駆動する効率的な データベースクエリを記述できます。 Activityタイプというモデルの1つを 詳しく見てみましょう。

    Activitiesを永続化するには まずSwiftDataをimportして このObservableマクロを Modelマクロに置き換えます。 SwiftDataがobservable準拠を 自動的に生成してくれます。 いいですね! 補足として nameとisCompleteの これらのdidSetオブザーバーは どちらかのプロパティが変更されると activityのdateEdited値を設定します。

    パラグライディングの activityがあるとします。 まだ完了しておらず 最後の編集は 4月1日の午前9時41分です。 イベントのタイムラインです。 次に activityをパラグライディングから スイミングに変更するとします。 nameのプロパティオブザーバーが 発火してdateEditedを更新します。 その後 ビーチへ出かけて activityをチェックオフします。 isCompleteのプロパティオブザーバーが 発火してdateEditedを更新します。 自動更新されるdateEditedプロパティは リストのソートやフィルタリングに 非常に役立ちます。 ただしプロパティオブザーバーと 算出プロパティは常に互換性があるとは限らないため dateEditedを最新の状態に保つために 別のテクニックを使います。 ただしその前にプロジェクトが ビルドできる状態に戻します。 この図には後で戻ります。 今はこれらのdidSetブロックを 削除して

    dateEditedはそのままにします。 次はTripクラスです。 同様にSwiftDataをimportして

    マクロを入れ替えます。

    次にビルドエラーを確認して 何が不足しているかを把握します。 このエラーはcreationDateを 変更可能にする必要があると示しています。 そのためvarを使うように 宣言を変更します。

    これでSwiftDataは実行時に このプロパティを データベースから読み込んだ値で 設定できます。 次のエラーはTripCollectionプロパティと すべてのモデルプロパティが Codableである必要があるということです。 この要件が存在するのは SwiftDataがプロパティを データベースの列にシリアライズするためです。 trip collectionは旅行の 季節テーマを格納します。 スキーマに含めて 永続化する必要があります。 Command+クリックで 宣言箇所にジャンプします。 そしてCodable準拠を 明示的に追加します。 ビルドエラーがいくつか残っています。 スキーマを構築し続ける中で これらを修正します。 次にWishlistでのゴール追跡の 仕組みを見てみましょう。 このGoalタイプの変更は ObservableをModelに変換するような 単純なものではありません。 Goalはenumerationとして 宣言されています。 各goalにはname kindなどの プロパティがあります kindはactivityの完了か 旅行の完了かを示します そしてgoalを達成するための 目標旅行数またはactivity数もあります。 正確に18個のGoalがあり すべて事前に定義されています enumerationは閉じた値の集合を 定義するためです。 元のインターフェースデモでは それで問題ありませんが 永続化モデルに必要なのは クラスです。 クラスはプロパティを格納でき 何度でもインスタンス化できます。 インターフェースを正しく実装するには Goalの設計を再考する必要があり Goalの処理と表示のロジックと 調和するようにします。 大まかに言うと これらのgoalはそれぞれ バッジに対応しています Wishlistで特定の条件が満たされるかどうかに 応じて表示されます。 これらのgoalの状態を 正確に永続化するには 特定のgoalの条件が 満たされたかどうかを 最小限の表現で キャプチャして保存する方法が必要です。 SwiftDataではclassが その最小表現を表現するための 基盤として選ばれます。 Goalをclassに変換します 同じ3つのプロパティから始めます name kind targetCountです。

    元のWishlistアプリでは 完了したアイテム数は別途保存されていました enumerationにはstored propertyがないためです。 永続化されたclassができたので progressの値もgoal自体に 格納しましょう completedCountと呼びます。

    そしてisCompleteという Boolean型プロパティも追加します。 completedCountがtarget以上のとき trueを格納します。 targetと等しいかそれ以上の場合です。 stored propertyとして ビューレイヤでクエリを始めるときに 完了したgoalと まだのgoalを区別するのに役立ちます。

    次にKindプロパティを 処理する必要があります。 Wishlistはこれを使ってgoalが 旅行の完了に関するものか activityの完了に関するものかによって 異なる詳細を表示します。 これらの細かい違いを 新しいサブクラスに分けられます SwiftDataのモデル継承のサポートを 使って。 継承はソフトウェア設計パターンで 明確に定義されたクラスの階層がある場合に 各サブクラスがスーパークラスと 同じアイデアを表す場合に有効です ただし共通のプロパティセットの より具体的な形で表現されます。 たとえば渦巻き銀河と レンズ状銀河は 銀河のサブクラスです どんな種類の銀河にも見られる いくつかの特性を継承します 星や塵などです 同時に見た目の形に カテゴリ的な違いもあります。 同様にtrip goalと activity goalがあり goalのスーパークラスから 共通のプロパティを継承できます。 継承を使ってさまざまな goalの種類をモデル化します GoalからKindプロパティを 削除してです。 TripGoalとActivityGoalの サブクラスを導入します。

    モデル継承を使うべき場合の詳細は このセッションを確認してください WWDC 2025の"SwiftData: Dive into inheritance and schema migration"です。

    これでこれらすべてのモデル間の リレーションシップを定義するために 必要なものが揃いました。 Wishlistでは各旅行が activityのセットに関連付けられます。 これはto-manyリレーションシップです。 各永続化されたTripモデルは 潜在的に多くの永続化されたActivityモデルを持ちます。 Wishlistのいくつかの部分は to-manyリレーションシップを dictionaryや配列をループする関数で 近似していました。 たとえばactivity IDに基づいて TripからActivityにマッピングするものがあります。 すぐにこれらのdictionaryを 適切なto-manyリレーションシップに 型間で変換します 各Tripが0以上の Activityを持てるように。 配列を宣言することが SwiftDataに対して1種類のモデルが 必要に応じてModelContextから 別の種類のモデルを参照できることを 伝える慣用的な方法です TripにActivityの配列を宣言します。 ここではrelationshipマクロも追加して Tripのactivities配列を リレーションシップとして明示的にマークします これによりデータベースからtripを削除すると その旅程にあった activityモデルも削除されます。 最後にphotoURLプロパティを 調整する必要があります。 今はただのファイルパスで ファイルがリネームされたり 別のディレクトリに移動されると 意味を失います。 また高解像度の画像は 表示が必要なときだけ読み込むべきです TripDetailViewのように 全画面で表示する場合にです。 旅行のカルーセルをスクロールするときは サムネイルだけ表示します。 そのためここにthumbnailDataという 新しいプロパティを追加します。 これは選択した写真の 低解像度バージョンをキャッシュして その生のバイト列を データベースにインライン格納します。 そして別途 URLの代わりに 高解像度画像を格納します 永続化された外部ファイル参照を 使って。 SwiftDataではこれを 画像専用の新しいモデルを作成して行います。 TripImageタイプとして すでに追加してあります。 ここでは実装の詳細は 説明しませんが 後でサンプルコードで 詳しく読むことをお勧めします。 photoURLはURLではなくなったので 右クリックでphotoにリネームします 宣言を右クリックして このリファクタメニューのを選択します。 マルチカーソル編集を使っているので 複数のファイルが一度に更新されます。 またこのコメントをクリックして 新しい変数名で更新できます。 Returnキーを押します。 次にinitializerの ラベルをリファクタリングして

    本文をactivitiesを 直接設定するように調整します。

    このリレーションシップを設定することで 以前の機能を再現しました activity主導のビューが親旅行の名前 季節を参照して表示できます その他の詳細も同様です。 SwiftDataの機能の統合を 本格的に始めたことで プロジェクトに不要なファイルが いくつか出てきました。 TripEditModelはもう不要です SwiftUIビューがSwiftDataモデルに 直接バインドして 編集をリアルタイムで反映できるからです。 DataSourceのすべての責務は ModelContextが自動的に処理します クエリとリレーションシップも。 削除します。

    その最後の点について 振り返る価値があります。 プロジェクトから数百行のコードを 削除しました。 state管理 ストレージロジック フィルタリング ソート リレーションシップのトラバース 検索が自動的に機能します。 WindowGroupで最後の仕上げをすれば モデルレイヤは完成です。 このmodelContainerシーン モディファイアはSwiftUIに伝えます queryマクロで 新しいスキーマを使うように。

    次にビューレイヤを更新します。 スキーマとmodel containerが 整ったことで 自動保存がデフォルトで 有効になります。 それを実際に確認する前に これらの永続化モデルを統合します モデルを表示する各サブビューに 効率的でターゲットを絞ったクエリから始めます。 次にSwiftUIのビューモディファイアを使って 実行時に発生する可能性のあるエラーを キャプチャして表示します ディスク容量不足や非対応のpredicateなど。 最後に不足しているプロパティ オブザーバーを追加して UIイベントが正しいデータを すべて伝播させ 期待する副作用が生じるように します。 SwiftDataアプリにフィルタリングを追加する際に 留意すべき重要な点が2つあります。 クエリの内部ではFetchDescriptorが 取得して表示したいモデルを model contextやqueryを通じて 計画する方法です。 2つ目としてモデルはアプリの アドレス空間の外に存在する ストレージメディアに保存されます ローカルファイルシステムの データベースやリモートサーバーなど。 この種のストレージは 永続化レイヤの基盤ですが メモリからの読み取りより 桁違いに遅くなる場合があります そのためアプリで 永続化レイヤを設計する際は どのデータがどこにいつ必要かを 考えることが重要です 最良の体験のためにです。 説明します。 以前は旅行 activity goalに関する データは allGoalsのような グローバル変数に格納されていました。 アプリのコンパイル済みバイナリの 一部として読み込まれていました。 この方法でのデータアクセスは高速ですが たとえばallGoalsに 多くの要素をハードコードすると アプリのメモリフットプリントが 顕著に増加します アプリのライフタイム全体で。 新しいgoalを追加しても アプリを閉じると すべての情報が消えて 再起動すると見たとおりに消えます。 SwiftDataを使えばgoalを挿入したり 既存のgoalを更新して保存できます model contextで。 保存後はいくつかの方法で アプリに取り戻せます。

    1つの方法を紹介します。 このコードはすべてのGoalを取得して 関係ないものを破棄します。 継続的なメモリ使用は少ないですが I/Oが増えます。 このアプローチは図書館の 司書にすべての棚から すべての本を持ってくるよう 頼むようなものです 自分でお気に入りの著者の本を 見つけるために。 棚を回るついでに その著者の本だけを 頼めばよかったのです。 predicateを使ってfetchするのは その司書に最初から 欲しいものを伝えるようなことです。 ここでは要求したgoalだけを 取得します。

    GoalsViewでは predicateとともにqueryマクロを使います。 これはmodel contextで fetchを呼び出すことと同等です。 利点はqueryの結果が変わると SwiftUIビューが自動的に更新される点です。 クエリ結果が変更されると。 やってみましょう。 SwiftDataをimportして

    このdataSourceの environmentプロパティを置き換えます 達成されたgoalを 達成日順でfetchするqueryで。 2つ目のqueryは 残りの関連goalをfetchします。 RecentTripsPageViewでも 同じ考え方です。

    SwiftDataをimportして

    dataSourceをqueryに置き換えます。

    旅行を逆の時系列順で 取得して フェッチ上限を5に設定します。 最新の5件の旅行が取得でき このForEachに渡されます。 TripCollectionViewではすべての 旅行を取得して季節別にまとめます。 各季節が1つの trip collectionです 各tripCollectionに対して このビューのインスタンスが1つ作られます。 個々のTripCollectionViewは 初期化されるまでどの季節を 表示するか分かりません そのためqueryを宣言して

    initializer内で動的に 構築します。

    目的のCollectionと一致する すべての旅行の明示的なqueryです。 queryがpredicateを受け取り predicateの内部では tripCollectionパラメータが initializerから直接キャプチャされ データベースに送られます。 queryの結果は このForEachに渡されます。

    次はアプリの3番目のタブにある SearchResultsListViewです。 右のプレビューに表示されている スーパービューが 検索フィールドとテキストを持ち 値をこのビューのinitializerに渡します。 再びdataSourceをquery宣言に 置き換えて initializerからの パラメータが これらのqueryのpredicateの 構築方法を決定します。 検索テキストが空の場合 最新の3件の旅行をfetchします。 それ以外の場合は名前が検索テキストと 一致するすべての旅行を辞書順で取得します。 activityの検索でも 同じことをします。 テキストが旅行に属する activityの名前と一致するか確認して 同様にproperty wrapperを 設定します。

    最後にqueryした値をListで使います。

    このリストにはoverlay ビューモディファイアもあり 検索結果が見つからない場合 ContentUnavailableViewを上部に表示します。 元のdataSourceの条件を 直接確認するものに置き換えます trips.isEmptyと activities.isEmptyを。

    アプリを再び 動かすには十分です。 Wishlistに旅行を追加して 再起動します。

    今回は旅行がそのまま残っています。

    "Northern Lights"です。 SwiftDataへの移行は ほぼ完了です。 残りの細かい点を いくつか整理するだけです。 ActivityItemViewの updateGoalAchievementsメソッドを 見てみましょう。 activityを完了するとprogressの値を 直接更新します。 エラーがスローされる 場合があります。 これらのエラーを state変数でキャプチャします。 またエラーをテレメトリシステムに 渡して 将来アプリを 改善できるようにします。 適切な場合はアラートを表示して ユーザーに エラーからの回復方法を 伝えます。 エラーケースの処理が完了しました。 UIにはstateが古くなる 場所がいくつかあります。 先ほどdateEditedを設定していた didSetオブザーバーを削除しました。 そのふるまいを今から 追加し直します。 バグはこちらです。 別の旅行のactivityを dateEdited順にソートしたいとします。 ソートのドロップダウンで "Date Edited"を選びます。 "Meditate under a tree"という activityをチェックオフすると リストの一番上に 移動するはずです プロパティの1つを 更新したばかりなので。 しかしそのままです。 永続化レイヤの観点では エラーはスローされていませんが 何かがおかしいです。 永続化を実装したので dateEditedプロパティへの リアルタイム更新を再有効化します Continuous Observation機能を使って 2027リリースで Observationフレームワークに追加されました。

    ActivityItemViewのinitializerで 新しいwithContinuousObservation関数を使って オブザーバーを設定します。 このビューはユーザーが activityを編集する場所なので observationを設置するのに 適しています。 isCompleteまたはActivityのnameが 変更されると Observationフレームワークがこのコードを実行して dateEditedを設定します 現在の時刻に queryをトリガーして activityのリストを自動更新します。 ここはTripへの 副作用を追加する自然な場所でもあります。 activityの状態が切り替わったり activityが追加・削除されると 旅行全体のisCompleteプロパティを 更新します。

    これでAppleの宣言的永続化フレームワークを 自分のアプリに統合するのに 必要なことがわかりました。 まずアプリのstateの適切な 表現を検討することから始めて スキーマを構成するModelタイプを 宣言します。 次にpredicateを使ったターゲットを絞った クエリを記述してメモリ使用量と ディスク上のストレージの バランスを保ちます。 そしてSwiftUIビューを 継続的に調整する方法を SwiftDataとの最適な 相互運用性のために更新し続けてください。 それではこれで今日の最後の activityをチェックオフして

    バッジを獲得します。

    ご視聴ありがとうございます よい旅を。

    • 3:39 - Convert Activity to a persistent model with @Model

      import Foundation
      import SwiftData
      
      // SwiftData automatically generates Observable conformance
      @Model
      class Activity {
          var name: String
          var isComplete: Bool = false
          var dateCreated = Date.now
          var dateEdited = Date.now
      }
    • 6:06 - Add Codable conformance to TripCollection

      enum TripCollection: String, CaseIterable, RawRepresentable, Codable {
          case springEscapes
          case summerVibes
          case fallGetaways
          case winterRetreats
      }
    • 10:32 - Set up model relationships between Trip, TripImage, and Activity

      import Foundation
      import SwiftData
      
      @Model
      class Trip {
          var name: String
          var collection: TripCollection
        
          var photo: TripImage
          var thumbnailData: Data?
        
          @Relationship(deleteRule: .cascade, inverse: \Activity.trip)
          var activities: [Activity] = []
        
          private(set) var creationDate = Date.now
          var subtitle: String?
          var isComplete: Bool = false
      }
    • 13:21 - Enable interoperability between your schema and SwiftUI views

      import SwiftUI
      import SwiftData
      
      @main
      struct WishlistApp: App {
          let container: ModelContainer = {
              do {
                  let modelContainer = try ModelContainer(for: Trip.self, Activity.self, TripImage.self, Goal.self, TripGoal.self, ActivityGoal.self)
                  try SampleData.seedIfNeeded(in: modelContainer.mainContext)
                  return modelContainer
              } catch {
                  fatalError("Could not create model container: \(error)")
              }
          }()
      
          var body: some Scene {
              WindowGroup {
                  ContentView()
                      .preferredColorScheme(.dark)
              }
              .modelContainer(container)
          }
      }
    • 16:27 - Fetch achieved and upcoming goals

      @Query(filter: #Predicate<Goal> { $0.isAchieved }, sort: \Goal.dateAchieved, order: .reverse)
      private var achievedGoals: [Goal]
      
      @Query(filter: #Predicate<Goal> { !$0.isAchieved }, sort: \Goal.sortOrder)
      private var upcomingGoals: [Goal]
    • 16:49 - Fetch recent trips

      import SwiftUI
      import SwiftData
      
      struct RecentTripsPageView: View {
          // Fetch most recent trips in reverse chronological order
          @Query(FetchDescriptor<Trip>(sortBy: [SortDescriptor(\Trip.creationDate, order: .reverse)], fetchLimit: 5))
          private var trips: [Trip]
      
          @Namespace private var namespace
      
          var body: some View {
              TabView {
                  ForEach(trips) { trip in
                      NavigationLink {
                          TripDetailView(trip: trip)
                              .navigationTransition(
                                  .zoom(sourceID: trip.id, in: namespace))
                      } label: {
                          TripImageView(trip: trip)
                              .overlay(alignment: .bottomLeading) {
                                  VStack(alignment: .leading) {
                                      Text("RECENTLY ADDED")
                                          .font(.subheadline)
                                          .fontWeight(.bold)
                                          .foregroundStyle(.limeGreen)
      
                                      Text(trip.name)
                                          .font(.title)
                                          .fontWidth(.expanded)
                                          .fontWeight(.medium)
                                          .foregroundStyle(.primary)
                                  }
                                  .padding(.horizontal)
                                  .padding(.bottom, 54)
                              }
                              .matchedTransitionSource(id: trip.id, in: namespace)
                      }
                      .buttonStyle(.plain)
                  }
              }
              .tabViewStyle(.page)
              .containerRelativeFrame([.horizontal, .vertical]) { length, axis in
                  if axis == .vertical {
                      return length / 1.3
                  } else {
                      return length
                  }
              }
          }
      }
    • 17:26 - Dynamically construct a query in the initializer of TripCollectionView

      init(tripCollection: TripCollection, cardSize: TripCard.Size, namespace: Namespace.ID) {
          _trips = Query(filter: #Predicate<Trip> { $0.collection == tripCollection }, sort: \Trip.name)
          self.tripCollection = tripCollection
          self.cardSize = cardSize
          self.namespace = namespace
      }
    • 18:13 - Search for trips and activities by name

      import SwiftUI
      import SwiftData
      
      private struct SearchResultsListView: View {
          @Query(sort: \Trip.name) private var trips: [Trip]
          @Query(sort: \Activity.name) private var activities: [Activity]
      
          var searchText: String
          var namespace: Namespace.ID
      
          init(searchText: String, namespace: Namespace.ID) {
              self.searchText = searchText
              self.namespace = namespace
      
              if searchText.isEmpty {
                  _trips = Query(FetchDescriptor(sortBy: [SortDescriptor(\Trip.creationDate, order: .reverse)], fetchLimit: 3))
                  _activities = Query(filter: #Predicate<Activity> { _ in false })
              } else {
                  // All trips whose name matches searchText, sorted lexicographically
                  let tripSearchPredicate = #Predicate<Trip> { $0.name.localizedStandardContains(searchText) }
                  _trips = Query(filter: tripSearchPredicate, sort: \Trip.name)
                  // All matching activities that belong to a trip
                  let activitySearchPredicate = #Predicate<Activity> { $0.trip != nil && $0.name.localizedStandardContains(searchText) }
                  _activities = Query(filter: activitySearchPredicate, sort: \Activity.name)
              }
          }
      
          var body: some View {
              List {
                  if !trips.isEmpty {
                      TripSearchSectionView(trips: trips, namespace: namespace, title: searchText.isEmpty ? "Recent Trips" : "Trips")
                  }
      
                  if !activities.isEmpty {
                      ActivitySearchSectionView(activities: activities)
                  }
              }
              .overlay {
                  if trips.isEmpty && activities.isEmpty {
                      ContentUnavailableView(
                          "No results for “\(searchText)”",
                          systemImage: "magnifyingglass",
                          description: Text("Check spelling or try a new search.")
                      )
                  }
              }
              .listStyle(.plain)
          }
      }
    • 19:42 - Capture and report errors from ActivityItemView

      var body: some View {
          HStack(alignment: .firstTextBaseline, spacing: 17) {
              Group {
                  if isEditing {
                      rowContentWhenEditing
                  } else {
                      rowContentWhenNotEditing
                  }
              }
              .transition(.opacity.animation(.snappy))
              .animation(.snappy, value: isEditing)
          }
          .onDisappear {
              do {
                  try updateGoalAchievements()
              } catch {
                  updateError = error
                  reportError(error)
              }
          }
          .alert(error: $updateError) {
              // Customize the presentation of the error
          }
      }
    • 21:04 - Update dateEdited and propagate side effects on property changes

      init(activity: Activity, isLast: Bool, isEditing: Bool) {
          activity.token = withContinuousObservation(options: .didSet) { event in
              _ = activity.name
              _ = activity.isComplete
      
              if event.matches(\Activity.name) {
                  activity.dateEdited = .now
              }
      
              if event.matches(\Activity.isComplete) {
                  activity.dateEdited = .now
                  activity.trip?.isComplete = activity.trip?.activities.isEmpty == false
                  && activity.trip?.activities.allSatisfy { $0.isComplete } == true
              }
          }
          self.activity = activity
          self.isLast = isLast
          self.isEditing = isEditing
      }
    • 0:00 - Introduction
    • An introduction to the Wishlist sample app and the three steps for adopting SwiftData: identifying relevant state, defining schemas, and defining model relationships.

    • 1:05 - Identify relevant state
    • Identify the data types and variables in Wishlist — trip collections, goal statuses, and the DataSource — that will become SwiftData models connected through a ModelContext.

    • 3:17 - Define your schemas
    • Convert Activity, Trip, and Goal into @Model types. Covers handling property observers with the @Model macro, refactoring the Goal enumeration into a class hierarchy using inheritance with TripGoal and ActivityGoal subclasses, and inlining thumbnail data.

    • 9:41 - Define model relationships
    • Declare to-many relationships between Trip and Activity using the @Relationship macro, remove the now-redundant DataSource and TripEditModel helpers, and attach the modelContainer scene modifier to complete the model layer.

    • 13:33 - Update the view layer
    • Replace environment DataSource properties with @Query macros and targeted FetchDescriptor predicates in each subview. Covers autosave, surfacing runtime errors with SwiftUI view modifiers, and re-enabling dateEdited property observers using the new withContinuousObservation API.

    • 21:47 - Next steps
    • Key takeaways: design a schema that fits your data model, balance memory and disk usage with targeted queries, and plan for interoperability and extensibility as your app evolves.

Developer Footer

  • ビデオ
  • WWDC26
  • Code Along:SwiftDataによる永続性の追加
  • メニューを開く メニューを閉じる
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    メニューを開く メニューを閉じる
    • アクセシビリティ
    • アクセサリ
    • Apple Intelligence
    • App Extension
    • App Store
    • オーディオとビデオ(英語)
    • 拡張現実
    • デザイン
    • 配信
    • 教育
    • フォント(英語)
    • ゲーム
    • ヘルスケアとフィットネス
    • アプリ内課金
    • ローカリゼーション
    • マップと位置情報
    • 機械学習とAI
    • オープンソース(英語)
    • セキュリティ
    • 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(英語)
    • Mini Apps Partner 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 © 2026 Apple Inc. All rights reserved.
    利用規約 プライバシーポリシー 契約とガイドライン