-
SwiftUIの新機能
SwiftUIに追加された最新の機能と、それらによってアプリをどのように改善できるかを確認しましょう。直接のディスクアクセスやスナップショットベースの差分処理により高性能なアプリの構築を可能にする新しいDocumentプロトコル、リスト/グリッド/セクションのコンテンツを並べ替えるための新しいAPI、表示の優先度設定や自動最小化の挙動などを実現するツールバーの機能強化を紹介します。拡張されたプレゼンテーションのためのAPI(任意のビューでのスワイプアクションなどの機能を追加)、AsyncImageのキャッシュの改善、Observable型の遅延状態初期化についても取り上げます。
関連する章
- 0:00 - Introduction
- 2:12 - Refreshed look and feel
- 8:06 - Document-based apps
- 15:18 - Presentation and interaction
- 19:58 - Data flow and performance
- 27:25 - Next steps
リソース
- TN3211: Resolving SwiftUI source incompatibilities for State and ContentBuilder
- State()
- ContentBuilder
- Swift Collections on GitHub
関連ビデオ
WWDC26
-
このビデオを検索
こんにちは、Stevenです UI Frameworksチームに所属しています。 Juliaです。私もUI Frameworks エンジニアです。 SwiftUIの新機能について ご紹介できることをうれしく思います。 SwiftUIには大きなアップグレードが 加わりました。 洗練された外観と操作感から パフォーマンスの向上、 アプリとの新しいインタラクション方法、 強力な新しいドキュメントAPIまで、 多くの素晴らしい新機能を ご紹介します。 まず最初に… 私はステッカーが 大好きです。 ラップトップをステッカーで 埋め尽くしています。 私もステッカーが大好きです。 でも現実の世界で貼れる場所は 限られています。 どこにでも貼れるわけでは ありません。 そこで、無限のステッカーの 可能性を解き放つ方法を考えました。 アプリです。 ステッカーアプリをご紹介します。 まず写真を選びます。 Apple Parkでの StevenとのこのPhoto、いいですね。 ペットを職場に 連れてこられたらいいのですが。 きっと喜んでくれるでしょう。 愛犬Pretzelのステッカーを シーンにドラッグして… 彼女の個性に合わせた サイズに変更します。 そして愛猫のKishkaも追加します。 Kishkaはさらに大きな 個性の持ち主です。 これが理想の勤務日というものですね。 一日中これをやっていたいですが 紹介することがたくさんあるので、 スクリプトに 集中しましょう。 このアプリはSwiftUIの多くの 新機能を活用しています。 優れた外観と高いパフォーマンスを持つ 一流のユーザー体験を実現しています。 まず、アプリが2027リリースで 得る美しい新しい外観と、 サイズ変更に最適化された ツールバーコンテンツについてご説明します。 次にJuliaが、 アプリに強力なドキュメント機能を 追加する新しいAPIと、 プレゼンテーションと インタラクションの改善についてお話しします。 最後に、アプリをスムーズに動作させる ための方法として、 パフォーマンスと データフローの強化についてお話しします。
まず、2027リリースでのアプリの 刷新された外観と操作感から始めましょう。 アプリをビルドして実行すると Liquid Glassデザインが 自動的に更新された 外観になります。 コードを1行も変更することなく この外観が適用されます。 Liquid Glassは洗練された 外観を持ち、 新しいLiquid Glassスライダーに 自動的に対応してティントを調整します。 iOSと同様に、macOSでも Liquid Glassのカスタム要素を 「インタラクティブ」としてマークすると ユーザーのクリックにより流動的に反応します。 これはマウスポインターで うまく動作するように最適化されており、 Macでも自然に感じられます。
Macと同様に、iPadアプリも 非アクティブ時には独特の外観になり、 アイコンとテキストが薄くなって どのウィンドウがアクティブかが わかりやすくなります。 たとえば、アプリとFilesアプリを タップして切り替えるときのように。 アプリでのこれらの改善の 見た目が気に入っています。 コードを変更することなく アプリの外観が刷新された点が 特に素晴らしいです。 ただし、アプリの外観を 細かく調整する方法もあります。 細部まで整えることができます。 サイドバーのカスタムアカウントボタンは 他のタブラベルと同様に薄くなります。 appearsActive環境変数を使って 条件付きで ウィンドウが非アクティブのときに ボタンの不透明度を下げています。 iPadとMacのメニューバーには デフォルトで最小限のアイコンが表示され、 主要なアクション専用に なりました。 ただし、labelStyle titleAndIcon モディファイアをStoreメニュー項目に適用すると アイコンが表示されて 目立つようになります。 ステッカーが増えるにつれ MacとiPadでのアプリの サイズ変更機能をとても ありがたく感じています。 iOS 27では、iPhoneアプリも サイズ変更可能になります。 Xcode 27では、ライブプレビューに リサイズハンドルが追加されました。 インタラクティブなサイズ変更への アプリの対応をテストできます。 これにより、即座にプレビューできます。 iPhone Mirroringを使用した場合の アプリの動作や、 iPadでiPhoneアプリとして 実行した場合の動作を確認できます。 これはすでにうまく動作しています。 iPadとMacでの アプリのサイズ変更に 対応しているためです。 SwiftUIで構築されたアプリは この機能の多くを自動的に取得しますが、 UIKitとSwiftUIの両方を 使用している場合は、 追加で考慮すべき点が あるかもしれません。 たとえば、画面のジオメトリを 正しく決定する方法、 ビューのサイズ設定にidiomではなく サイズクラスを使用すること、 インターフェース向きの変更への 対応などがあります。 UIKitとSwiftUIの両方を使用する アプリのサイズ変更対応について 詳しく学ぶには、 「Modernize your UIKit app」を ご覧ください。
アプリには完全な ストア体験もあります。 新しいステッカーパックを ダウンロードできます。 私のお気に入りは WWDC26のステッカーパックです。 ストアビューにはいくつかのタブがあり ショッピングカート用のタブも含まれます。 ショッピングカートのタブは画面の ボトムトレーリングエッジに表示され、 ストアコンテンツを含む 他のタブと区別されます。 この特別なタブ配置を有効にするために、 新しいprominentタブロールを使って 目立つようにしています。 アプリのすべての機能を考えると ツールバーは最も重要なアクションへの クイックアクセスを 提供する優れた方法です。 機能を追加するにつれて ツールバー項目のリストは さらに増えるでしょう。 これはアプリのサイズ変更時に 特に重要になります。 アプリウィンドウのサイズを変更すると ツールバー項目はシステムによって 自動的に調整されます。 収まりきらない項目は 非表示になります。 iPhoneでは水平方向の スペースがさらに限られているため、 すべてのツールバーボタンを 表示する余裕がありません。 Undo、Redo、Shareなどの重要なアクションが オーバーフローメニューに隠れてしまいます。 そこで、新しいツールバーAPIの 出番です。 ツールバースペースが限られているときに どのボタンを表示したままにするかを 指定できます。 最も重要なToolbarItemGroupには UndoとRedoという 編集ボタンが含まれています。 しかし現在は非表示になっています。 これらを表示し続けることが 重要だとシステムに伝えたいです。 新しいvisibilityPriority モディファイアを追加することで 優先度をhighに設定することができます。 これでUndoとRedoボタンが 表示されるようになります。
一部のアクションは オーバーフローメニューに残しておきたいです。 頻繁には使わないためです。 写真を入れ替えるボタンや、 ページを画像としてエクスポートする ボタン、ステッカーをクリアするボタンなど。 これらのボタンを常に オーバーフローメニューに配置するために、 新しいToolbarOverflowMenu コンテナにグループ化します。 必要なときはメニューから アクセスできます。 最後に、Shareボタンについては 常に表示させておきたいです。 このステッカーページを 全員に送り忘れないようにするために。 新しいtopBarPinnedTrailing 配置を使うと、Shareボタンを 常にトレーリング位置に 表示し続けることができます。 これでツールバーは完璧に設定され アプリのすべての重要な機能に ウィンドウやスクリーンサイズに 関わらずアクセスできます。 もう1つツールバーの 強化機能をご紹介します。 コレクションにはたくさんの ステッカーがあるので、スクロール中は できるだけ多くのスペースを 確保したいです。 そこで新しいtoolbarMinimizeBehavior モディファイアを追加し、「onScrollDown」に設定します。 navigationBar配置に 適用します。 これで、スクロール時にシステムが 自動的にナビゲーションバーを 移動させてくれます。 アプリの見た目と操作感は 素晴らしいと思いますが、 内部ではさらに多くのことが 行われています。 アプリはステッカーページを 開いたり保存したりでき、 ページを画像として エクスポートする機能もあります。 これはすべてJuliaが紹介する 強力な新機能のおかげです。
アプリにはこれらの機能が さらに多く備わっています。 新しいSwiftUI Document APIの おかげです。 まず、新しいDocument APIの 概要と、 アプリ構築の基盤として どう機能するかをご説明します。 SwiftUIは長らくドキュメントベースの アプリをFILE_DOCUMENTと REFERENCE_FILE_DOCUMENTプロトコルで サポートしてきました。 2027リリースでは、その基盤を拡張した APIを追加できることをうれしく思います。 PixelMator PROや Pages、私たちが毎日使っている Xcodeなど、 ドキュメントベースのアプリは 多くの機能をすぐに使えます。 新規ドキュメント作成のCommand+Nや キーボードショートカットなど、 ドキュメントを開くCommand+Oも 含まれます。 ドキュメントに変更があることを 伝える編集済みインジケーター、 スマートな自動保存の仕組み、 その他多くの機能があります。 Document APIは、アプリが 内部と UIの両方で 行える数多くの 改善を可能にします。 そのうち3つを取り上げます。 ドキュメント作成コンテキスト、 ディスクの読み書き パフォーマンスの改善、 そしてドキュメントURLへの ダイレクトアクセスの本格的なサポートです。 アプリでのドキュメント作成方法を 見てみましょう。 デフォルトでは、空白の ステッカーページから始められます。 でも、もっとすぐに 始められるようにしたいです。 そこで、写真からページを 作成するボタンも追加しました。 新しいDocumentCreationSource APIを使って blankとphotoという2つのソースを宣言し、 それぞれのNewDocumentButtonを ローンチシーンに追加します。 これらのボタンのいずれかを選択すると SwiftUIはソースをドキュメントの 作成クロージャにcontextパラメーターで 渡します。 初期化子でcontextを確認し ソースが「photo」の場合は、 ドキュメントが開いたときに フォトピッカーが表示されます。 これで、1タップで写真に ステッカーを貼れるようになります。
ドキュメントベースのアプリは 大量のデータを読み書きします。 頻繁に更新が必要な 複雑なUIを持つこともあります。 新しいAPIはこれらの処理を 最適化する優れた方法を提供し、 アプリをスムーズに 動作させ続けます。 アプリのbodyの最初のSceneとして DocumentGroupを宣言することで ドキュメントアーキテクチャに 対応します。 StickerDocumentクラスが ドキュメントタイプを定義します。 ビューへのデータ提供と ディスクからのデータ読み取り方法、 書き込み方法を定義します。 Document APIはモダンな Observationフレームワークと連携するため、 Observableマクロを 使用しています。 これだけでパフォーマンスが向上します。 ビューは依存するプロパティが 変更されたときのみ 更新されるようになります。 読み書きをできる限り速く効率的に 行うことが目標です。 新しいAPIの最適化ポイントを 見ていきましょう。 書き込みについては、ドキュメントを writable documentプロトコルに準拠させます。 要件は3つあります。 まず、アプリが書き込める フォーマットのリストです。 このアプリは写真とステッカーを含むカスタム パッケージフォーマットをサポートしています。 次に、書き込み用の現在のドキュメント コンテンツを返すsnapshotメソッドです。 コンテンツの表現には カスタムPageSnapshot構造体を使います。 書き込みに必要なすべてを 含んでいます。 背景画像、 ステッカーの座標、 そしてステッカー自体です。 ある時点でのドキュメントの スナップショットとして機能します。 3つ目の要件を満たすために Writerを提供します。 WriterはDocumentWriterプロトコルに 準拠し、 指定されたフォーマットでドキュメントを ディスクに書き込む方法を知っています。 リクエストされたコンテンツタイプを渡します。 このアプリでは「stickerDocument」です。 DocumentWriterプロトコルには Snapshotという概念があります。 PageSnapshot型が ここにぴったり当てはまります。 DocumentWriterの唯一の 要件は書き込みメソッドです。 パフォーマンスを最適化する 複数の機会を提供します。 まず、writeメソッドは nonisolatedかつ非同期です。 これにより、高コストなディスク書き込み 操作をバックグラウンドで実行でき、 アプリのレスポンスを 維持できます。
実際に更新が必要な部分だけを 書き込みます。 現在のスナップショットと 前のスナップショットを比較することで。 最適化を行っても、ディスク操作には 目に見える時間がかかる場合があるため、 SwiftUIはprogressパラメーターを提供し Foundation Subprogress APIを使って 書き込みの進捗を 報告できます。
ドキュメントタイプに ディスクからの読み込み方法を教えるため、 StickerDocumentクラスを ReadableDocumentプロトコルに準拠させます。 ReadableDocumentは WritableDocumentの対になるものです。 比較してみましょう。 各プロトコルはサポートするコンテンツタイプ のリストを要求します。 WritableDocumentはスナップショットを 提供し、 ReadableDocumentはそれを 適用する方法を知っています。 DocumentWriterというフレンドプロトコルが WritableDocumentにあります。 ReadableDocumentのフレンドは DocumentReaderで、 ディスク関連の 重い処理をすべて担当します。 これでStickerアプリは このページのようなファイルを 読み書きできるようになりました。 なかなか立派な海賊に なれましたね。
もう1つ追加する機能があります。 ページを画像として保存する機能です。 アプリを持っていない人とも 共有できるようにするために。 Document APIを使えば Writerを拡張して ページを別のフォーマットで 保存できます。 Core GraphicsでPNGとして 保存するなど。 書き込み可能なコンテンツタイプの 定義に戻り、.PNGをリストに追加します。 追加フォーマットをサポートするために、 先ほど実装した writeメソッドを修正します。 アプリが複数のフォーマットを扱えるように なったので、アプリがサポートする 各タイプのコンテンツタイプ チェックを追加します。 PNGについては、Core Graphicsを使って ステッカーと背景画像を 1枚の画像に合成して URLに書き込みます。 さらに多くのタイプを追加するには 任意のフォーマットでドキュメントを書き込めます。 任意のフレームワークを使って コンテンツタイプを追加するだけです。 これがアプリのドキュメント保存の 設定です。
次は、ステッカーコレクションの ご紹介です。 プレゼンテーションとインタラクションに 素晴らしい改善があります。 大量のステッカーコレクションは 少しカオスに感じることがあります。 そこで、reorderableコンテナAPIの 出番です。 アプリのインスペクターで ステッカーコレクションを 閲覧する2つの方法があります。 1つ目は、各ステッカーを 名前とともに表示する「List」です。 ドラッグして並び替えることで ステッカーを整理したいです。 そこでReorderable APIを 使います。
ForEachにReorderableモディファイアを 追加し、 Listにreorder Containerモディファイアを 追加します。 クロージャ内で、自分で書いた ヘルパー関数difference.applyを呼び出して ステッカーの配列を 更新します。 内部では、apply関数がオープンソースの swift-collectionsパッケージを使って 並び替えの変更を コミットします。 詳細はswift.orgを ご覧ください。
SwiftUIがドラッグのインタラクションと アニメーションを自動的に処理します。 ステッカーをグリッドで 整理することもでき、 一度により多くのステッカーを 閲覧しやすくなります。 reorderable APIはListに限らず あらゆるコンテナで機能します。 つまり、リストのコードを 流用して LazyVGridに変更できます。 並び替えのコードは まったく同じです。 まったく異なるコンテナで 同じインタラクティブな並び替え動作を 同じコードで 実現できます。 同じコードで。 これらのAPIにより、watchOSでも 初めて並び替え機能が使えるようになりました。
並び替えに関しては これはまだ始まりにすぎません。 reorderableコンテナのすべての 機能を深く掘り下げるには、 「Build powerful drag and drop in SwiftUI」の Code Alongセッションもご覧ください。
外出中でも、iPhoneで ステッカーページをカスタマイズするのが好きです。 UIの下部のシートにはすべてのステッカーが すぐに手の届く場所にあります。 リストからステッカーを削除するための スワイプアクションも設定しました。 swipeActionsモディファイアをリスト項目に このDeleteボタンと一緒に追加することで。
でも、リストをカスタマイズする 柔軟性をもっと持たせたくて、 LazyVStackを 使うことにしました。 SwiftUIはListだけでなくあらゆるビューの スワイプアクションをサポートします。 ForEachをListから 更新した項目スタイルのLazyVStackに移動し、 swipeActionsContainerモディファイアを 追加します。 このスクロールビュー内の項目間の スワイプアクションを調整します。
先ほど言ったように 私はステッカーが大好きです。 なので、写真の デコレーションを始めると、 つい夢中になりすぎて しまうことを認めます。 そのため、いくつかのステッカーを 削除してスペースを作る必要があることも。 どれも大好きなのですが。
写真上の各ステッカーに コンテキストメニューを追加し、 このDeleteボタンで すばやく削除できるようにしました。
ボタンをタップすると stickerToDelete State変数が 現在のステッカーに設定されます。
また、confirmationDialog モディファイアも追加しました。 確認ダイアログは、シートと同じ item-bindingパターンをサポートするようになりました。 stickerToDeleteバインディングを モディファイアに渡します。 Deleteボタンをタップして stickerToDeleteに値を設定すると、 確認ダイアログが表示されます。 alertでも同様に機能します。
プレゼンテーションとインタラクションの 素晴らしい改善をご紹介しましたが、 それだけではありません。 2027リリースのSwiftUIには さらに多くの改善があります。 これらの改善の多くは すでに使用しているAPIを アプリの変更なしに より良くします。
高いパフォーマンスはアプリをレスポンシブで 洗練されたものにするために重要です。 データのフローを丁寧に考えることが アプリのパフォーマンスを 最高の状態に保つ 最良の方法の1つです。 Stevenが、データフローとパフォーマンスの 大きな改善についてお話しします。 ありがとう、Julia。 アプリに追加した ステッカーストアはとてもクールです。 ステッカーパックがダウンロード可能なのが 素晴らしくて、 AsyncImageの 改善を活用できます。 AsyncImageは画像アセットを インターネットから読み込む優れた方法で、 画面上に表示されると 読み込まれます。 スクロールして表示するのに とても効果的です。 KishkaとPretzelの かわいいステッカーたちを次々と。 しかしこれまでAsyncImageは 画像をメモリに保持していませんでした。 スクロールバックすると画面に再表示 されたときに再読み込みされていました。 一番上にスクロールバックしたときに これらの画像が すぐに表示されることを 望んでいました。 2027リリースではそうなります。 AsyncImageは標準のHTTPキャッシングを サポートするようになり、デフォルトでキャッシュされます。 サーバーのキャッシュヘッダーを尊重し コードの変更は不要です。 すべてのアプリで 自動的に有効になります。 Xcode 27でビルドされたアプリは 新しいAPIを活用して ダウンロードのカスタマイズが できます。 画像のダウンロード方法をより細かく 制御するために、独自のURLRequestを 構築してAsyncImageに 渡すことができます。 これにより、リクエストごとの 様々なカスタマイズが可能になります。 例えば、キャッシュポリシーの 指定などです。 より大きなキャッシュなど、より長期の 設定が必要な場合は、 独自のカスタムURLSessionを インスタンス化できます。 必要な容量のURLCacheを 設定して asyncImageURLSessionモディファイアに 渡すことでセッションを使用します。
スクロールバックすると 画像は自動的に キャッシュから読み込まれます。
SwiftUIはアプリのデータをモデル化して 保存し、ビューに渡す様々な方法を提供します。 データをビューに渡す方法も 含まれます。 アプリのデータを保存する優れた方法の1つが Observableクラスの使用です。 ここではStickerStoreクラスで 行っているように。 StickerStoreViewが初期化されると StickerStoreクラスの新しいインスタンスが 作成され State変数に割り当てられます。 このインスタンスはビューの ライフタイム中保持されます。 しかし、親ビューが更新されて StickerStoreViewが 再度初期化されるとどうなるでしょうか。 以前のリリースでは、 初期化のたびにStickerStoreの 新しいインスタンスが作成されていました。 しかし元のインスタンスが State変数に保持されているものです。 新しいインスタンスは 破棄されていました。 ビューの再初期化のたびに これが繰り返されていました。 クラスの主要な保存インスタンスは 安定したままであっても。
2027リリースでは初めて、 Stateプロパティで初期化・保存される クラスが lazyになりました。 つまり一度だけ初期化されます。 これはStateが Dynamic Propertyから マクロに変換されたおかげです。
StickerStoreViewが 初めて初期化されると、 StickerStoreクラスの新しいインスタンスが 以前と同様に作成されますが、 以降の初期化では 新しいクラスインスタンスは作成されません。 この動作は @Observableが初めて導入されたリリースに バックポートされています。 iOS 17、macOS 14、 および対応リリースから適用されます。
場合によっては、 Stateマクロの導入が ソース破壊的な変更に なる可能性があります。 例えば、@State変数に デフォルト値を指定しており、 さらにinit内で同じ@State変数に 値を代入している場合、 Xcodeは初期化前の使用に関する エラーを表示します。 このエラーを解決するには 不要なデフォルト値の代入を削除します。 Stateマクロがコードに与える 影響についての詳細は ドキュメントをご確認ください。 優れたランタイムパフォーマンスの維持は アプリをスムーズに動作させるために 不可欠です。 しかし、別のタイプの パフォーマンスもあります。 アプリ開発の体験に 大きな影響を与えることがあります。 複雑に入れ子になったビューを 持つアプリでは、 このエラーに遭遇した かもしれません。 「コンパイラは妥当な時間内に この式の型チェックができません」 これはなぜ起こるのでしょうか。 このビューにはSection、Group、 そしてForEachでコンテンツを囲んでいます。 この式の型チェックのために、 まずコンパイラはどのSectionの オーバーロードを使うか選択する必要があります。 SectionはViewを生成するビルダーか、 TableRowContentを生成するビルダーで 初期化できます。 どちらを使うかを知るためにコンパイラは 両方のオプションを試す必要があります。 コードではSectionビルダーが Groupを返します。 コンパイラはSectionが生成する コンテンツのタイプを知ることができません。 ネストされたGroupのコンテンツの タイプを解決するまでは。 今度はさらに多くのオプションがあり、 ネストされたForEachについても コンパイラはそれぞれ試す必要があります。 さらに、ForEachのビルダーにも 独自のオプションセットがあり、 それもチェックが必要です。 まだコンテンツ自体に 到達していません。 これらのパスをそれぞれ試すことで 型チェックのコストが急増します。 しかし、コードの中のSection、Group、 ForEachがビューを構築していることはわかっています。 この決定木には実際には 1つの有効なパスしかありません。 この複雑な選択の代わりに これらのビルダーが生成するタイプに 制約されず、 単にコンテンツを組み立てるだけに なるとしたらどうでしょう。 2027リリースでは SwiftUIはまさにそれをします。 最も一般的なビルダーセットが 単一の初期化子を共有するようになり、 シンプルで明確な 1つのパスだけになります。 複数の異なるビルダータイプが 単一のビルダーに統合されたため これが可能になりました。 ContentBuilderです。 これはSwiftUIのすべてのAPIで 統一されたビルダーを実現するための一歩です。 ContentBuilderはあらゆる 最小デプロイメントターゲットで使用できます。 内部では既存の ViewBuilderの進化形だからです。 ContentBuilderはXcode 27を使った SwiftUIの型チェックパフォーマンスを 大幅に向上させます。 2027リリースをターゲットにしている場合も 以前のリリースも同様です。 また、Xcode 27に含まれる新しいエージェント スキルも導入できることを嬉しく思います。 2027リリースの新機能を アプリに導入し、 パフォーマンスとコードの 正確性を向上させます。 SwiftUI Specialist Skillはアプリでの SwiftUIのベストプラクティス遵守を支援します。 What's New In SwiftUI Skillは 新しいAPIの導入をガイドします。 2027リリースのものです。 これらのスキルはXcode 27の Coding Assistantからアクセスできます。 他のツールでこれらのスキルを 使用するには、 「xcrun agent skills export」 コマンドでエクスポートできます。 ワークフローにインポートできる Markdownファイルが作成されます。
SwiftUIの素晴らしい 新しい機能強化をご紹介しました。 次はあなたの番です。 まず、2027リリース向けに Xcode 27でプロジェクトをビルドして、 アプリの更新された外観と 操作感を確認しましょう。 ドキュメントベースのアプリがあれば 新しいDocument APIがどう活用できるか 検討してみてください。 そして、Xcode 27の SwiftUIエージェントスキルを試して 新しいAPIとベストプラクティスを 採用しましょう。 なかなか離れがたいですが スケジュールを守らなくてはなりません。 皆さんがこれらの改善を アプリに取り入れることを 楽しんでいただければ幸いです。 ご視聴ありがとうございました。
-
-
3:20 - appearsActive environment value
struct SidebarFooterView: View { @Environment(\.appearsActive) private var appearsActive var body: some View { MyAccountView() .opacity(appearsActive ? 1 : 0.5) } } -
3:34 - Menu icon visibility
CommandMenu("Stickers") { Button { openStore() } label: { Label("Store", systemImage: "bag.fill") .labelStyle(.titleAndIcon) } } // Other menu items } -
5:12 - Prominent tab role
TabView { Tab { EventsTab() } Tab { HolidaysTab() } Tab { FunTab() } Tab(role: .prominent) { CartTab() } } -
6:15 - Toolbar item visibility and overflow menu
// Toolbar item visibility priority StickerPageView() .toolbar { ToolbarItemGroup { UndoButton() RedoButton() } .visibilityPriority(.high) ToolbarOverflowMenu { ChoosePhotoButton() ExportAsImageButton() ClearAllStickersButton() } ToolbarItem(placement: .topBarPinnedTrailing) { ShareButton() } } -
7:37 - Minimize toolbar on scroll with toolbarMinimizeBehavior
// Minimize toolbar when scrolling ScrollView { StickerListView() } .toolbarMinimizeBehavior(.onScrollDown, for: .navigationBar) -
9:47 - Document creation sources with context parameter
// Use the context to create a document @main struct Stickers: App { var body: some Scene { DocumentGroupLaunchScene("Create a Sticker Page") { NewDocumentButton("New Sticker Page", source: .blank) NewDocumentButton("Sticker Page from Photo…", source: .photo) } DocumentGroup { /* ... */ } } } extension DocumentCreationSource { static let blank = Self(id: "blank") static let photo = Self(id: "photo") } -
10:01 - Use the context to create a document
@main struct Stickers: App { var body: some Scene { DocumentGroupLaunchScene("Create a Sticker Page") { NewDocumentButton("New Sticker Page", source: .blank) NewDocumentButton("Sticker Page from Photo…", source: .photo) } DocumentGroup { document in StickerPageDocumentView(document) } { configuration, context in StickerPageDocument(configuration: configuration, context: context) } } } -
10:43 - Document app declaration
@main struct Stickers: App { var body: some Scene { DocumentGroup { /* ... */ } WindowGroup { /* ... */ } } } -
11:25 - Implement document writing
@Observable final class StickerDocument { // ... } -
11:34 - Implement document writing: list writable formats
@Observable final class StickerDocument { static let writableDocumentTypes: [UTType] = [.stickerDocument] // ... } import UniformTypeIdentifiers extension UTType { static let stickerDocument = UTType(exportedAs: "stickerdocument") } -
11:45 - Implement document writing: provide snapshot
@Observable final class StickerDocument { static let writableDocumentTypes: [UTType] = [.stickerDocument] @MainActor func snapshot(contentType: UTType) async throws -> sending PageSnapshot { /* ... */ } // ... } -
11:54 - Implement document writing: represent the snapshot
struct PageSnapshot { var background: Image var metadata: StickerPlacements var stickers: [Image] } struct StickerPlacements { /* ... */ } -
12:13 - Implement document writing: provide a DocumentWriter
@Observable final class StickerDocument { static let writableDocumentTypes: [UTType] = [.stickerDocument] @MainActor func snapshot(contentType: UTType) async throws -> sending PageSnapshot { makeSnapshot() } func writer(configuration: sending WriteConfiguration) -> sending Writer { Writer(contentType: configuration.contentType) } } -
12:33 - DocumentWriter: Snapshot
struct Writer<Snapshot>: DocumentWriter { typealias Snapshot = PageSnapshot // ... } -
12:36 - DocumentWriter: PageSnapshot as Snapshot
struct Writer<Snapshot>: DocumentWriter { typealias Snapshot = PageSnapshot let contentType: UTType // ... } -
12:42 - DocumentWriter protocol implementation
struct Writer<Snapshot>: DocumentWriter { typealias Snapshot = PageSnapshot let contentType: UTType nonisolated func write( snapshot: sending PageSnapshot, to destination: URL, previous: sending PageSnapshot?, progress: consuming Subprogress ) async throws { // write .stickerDocument } } -
13:18 - Progress reporting during writing
struct Writer<Snapshot>: DocumentWriter { typealias Snapshot = PageSnapshot let contentType: UTType nonisolated func write( snapshot: sending PageSnapshot, to destination: URL, previous: sending PageSnapshot?, progress: consuming Subprogress ) async throws { // report progress… // write .stickerDocument } } -
13:27 - Implement document reading with ReadableDocument protocol
extension StickerDocument: ReadableDocument { } -
14:35 - Add PNG to supported formats list
@Observable final class StickerDocument: WritableDocument { static let writableContentTypes: [UTType] = [.stickerDocument, .png] } -
14:48 - Add content type checks
struct Writer<Snapshot>: DocumentWriter { typealias Snapshot = PageSnapshot let contentType: UTType nonisolated func write( snapshot: sending PageSnapshot, to destination: URL, previous: sending PageSnapshot?, progress: consuming Subprogress ) async throws { if contentType.conforms(to: .stickerDocument) { // write .stickerDocument } else if contentType.conforms(to: .png) } } -
14:56 - Writing multiple formats including PNG
struct Writer<Snapshot>: DocumentWriter { typealias Snapshot = PageSnapshot let contentType: UTType nonisolated func write( snapshot: sending PageSnapshot, to destination: URL, previous: sending PageSnapshot?, progress: consuming Subprogress ) async throws { if contentType.conforms(to: .stickerDocument) { // write .stickerDocument } else if contentType.conforms(to: .png) { let context = CGContext(/* ... */) context.draw(/* ... */) } } } -
15:58 - Reorderable list with reorderContainer
List { ForEach(stickers) { sticker in StickerListItemView(sticker: sticker) } .reorderable() } .reorderContainer(for: Sticker.self) { difference in difference.apply(to: &stickers) } -
16:14 - Apply changes to a reorderable list's data source
import OrderedCollections // from https://github.com/apple/swift-collections extension ReorderDifference where CollectionID == ReorderableSingleCollectionIdentifier { func apply(to values: inout [some Identifiable<ItemID>]) { var dictionary = OrderedDictionary(uniqueKeys: values.map { $0.id }, values: values) let destinationOffset: Int? = switch destination.position { case .before(let destination): dictionary.keys.firstIndex(of: destination) case .end: nil } dictionary.move(keys: sources, to: destinationOffset ?? values.endIndex) values = dictionary.values.elements } } -
16:48 - Reorderable grid with LazyVGrid
LazyVGrid { ForEach(stickers) { sticker in StickerListItemView(sticker: sticker) } .reorderable() } .reorderContainer(for: Sticker.self) { difference in difference.apply(to: &stickers) } -
18:12 - Swipe actions on List
List { ForEach(stickers) { sticker in StickerListItemView(sticker: sticker) .swipeActions { DeleteButton(sticker: sticker) } } } -
18:15 - Swipe actions on any view
ScrollView { LazyVStack { ForEach(stickers) { sticker in StickerListItemView(sticker: sticker) .swipeActions { DeleteButton(sticker: sticker) } } } } .swipeActionsContainer() -
18:54 - Confirmation dialog with item binding
struct StickerCanvasView: View { var stickers: [Sticker] @State private var stickerToDelete: Sticker? var body: some View { ZStack { ForEach(stickers) { sticker in PlacedStickerView(sticker: sticker) .contextMenu { // ... } } } .confirmationDialog( "Delete?", item: $stickerToDelete ) { sticker in DeleteStickerButton(sticker) } } } -
19:35 - Alert with item binding
struct StickerCanvasView: View { var stickers: [Sticker] @State private var stickerToDelete: Sticker? var body: some View { ZStack { ForEach(stickers) { sticker in PlacedStickerView(sticker: sticker) .contextMenu { // ... } } } .alert( "Delete?", item: $stickerToDelete ) { sticker in DeleteStickerButton(sticker) } } } -
21:18 - AsyncImage with URLRequest and custom URLSession
@Observable class StickerStore { static let imageSession: URLSession = { let config = URLSessionConfiguration.default config.urlCache = URLCache( memoryCapacity: 64 * 1024 * 1024, diskCapacity: 256 * 1024 * 1024) return URLSession(configuration: config) }() } ForEach(pets) { pet in AsyncImage(request: URLRequest( url: pet.imageURL, cachePolicy: .returnCacheDataElseLoad) ) } .asyncImageURLSession(StickerStore.imageSession) -
23:08 - @State converted to macro for lazy initialization
@Observable class StickerStore { } struct StickerStoreView: View { // store is now lazily initialized, only // created once for the lifetime of the view @State private var store = StickerStore() var body: some View { // ... } } -
23:48 - @State macro init assignment error
struct StickerPageView: View { @State private var page = StickerPage() let title: String init(title: String) { self.page = StickerPage(title: title) // Variable 'self.title' used before being initialized self.title = title } var body: some View { // ... } } -
24:02 - Fixed @State macro init assignment error
struct StickerPageView: View { @State private var page: StickerPage // Removed default value to fix error let title: String init(title: String) { self.page = StickerPage(title: title) self.title = title } var body: some View { // ... } } -
26:07 - @ContentBuilder
@ContentBuilder func stickerLibraryView() -> some View { // ... }
-
-
- 0:00 - Introduction
This video covers the refreshed Liquid Glass look and feel, new document-based app APIs, presentation and interaction improvements, and data flow and performance enhancements.
- 2:12 - Refreshed look and feel
Apps automatically adopt the updated Liquid Glass appearance on 2027 OS releases without code changes. Covers interactive Liquid Glass elements, the inactive window appearance on iPadOS, toolbar customization with overflow menus and pinned placements, minimize-on-scroll behavior, and guidance for building resizable apps using size classes.
- 8:06 - Document-based apps
Expanded document APIs for SwiftUI apps, including the new DocumentCreationSource API for custom new-document flows, performance improvements for reading and writing large documents, and first-class support for direct document URL access via the FileDocument and ReferenceFileDocument protocols.
- 15:18 - Presentation and interaction
New reorderable container APIs let users drag to reorder items in any container — List, LazyVGrid, and on watchOS for the first time. Also covers swipe actions on arbitrary views beyond List, and item binding-driven confirmation dialogs.
- 19:58 - Data flow and performance
AsyncImage now supports standard HTTP caching by default, with new APIs for custom URLRequest and URLSession configurations. The @State property wrapper is converted to a macro, making class initialization lazy to prevent redundant allocations — back-ported to iOS 17, macOS 14, and aligned releases.
- 27:25 - Next steps
Key takeaways and recommended next steps: build in Xcode 27 to see the updated Liquid Glass look in your app, adopt the new Document APIs for document-based apps, and explore the SwiftUI agent skills.