ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
SwiftUIの新機能
SwiftUIを活用し、あらゆるAppleプラットフォームで優れたアプリを構築する方法を学びましょう。デザインと操作感が刷新されたiPadOSのタブとドキュメントのほか、ウインドウ操作のための新しいAPIでウインドウ管理を向上させる方法や、visionOSアプリでイマーシブな空間とボリュームをより高度に制御する方法もご紹介します。充実した情報を表現できる表の作成、テキストのカスタマイズとレイアウトなど、幅広い注目の機能強化に関する情報も得られます。
関連する章
- 0:00 - Introduction
- 0:51 - Fresh apps
- 1:04 - Fresh apps: TabView
- 2:22 - Fresh apps: Presentation sizing
- 2:39 - Fresh apps: Zoom transition
- 3:02 - Fresh apps: Custom controls
- 3:38 - Fresh apps: Vectorized and function plots
- 4:10 - Fresh apps: TableColumnForEach
- 4:25 - Fresh apps: MeshGradient
- 4:51 - Fresh apps: Document launch experience
- 5:33 - Fresh apps: SF Symbols 6
- 6:37 - Harnessing the platform
- 6:52 - Harnessing the platform: Windowing
- 8:28 - Harnessing the platform: Input methods
- 10:45 - Harnessing the platform: Widgets and Live Activities
- 12:25 - Intermezzo
- 12:55 - Framework foundations
- 13:09 - Framework foundations: Custom containers
- 13:48 - Framework foundations: Ease of use
- 16:18 - Framework foundations: Scrolling enhancements
- 17:18 - Framework foundations: Swift 6 language mode
- 18:01 - Framework foundations: Improved interoperability
- 19:18 - Crafting experiences
- 19:43 - Crafting experiences: Volumes
- 20:27 - Crafting experiences: Immersive spaces
- 21:27 - Crafting experiences: TextRenderer
- 22:12 - Next steps
リソース
関連ビデオ
WWDC24
- より進化したドキュメント起動体験の実現
- システム全体にアプリのコントロールを拡張
- ボリュームとイマーシブな空間の詳細
- Apple Pencilを最大限に活用
- Apple Watchでのライブアクティビティの表示
- iPadOSのタブとサイドバーの利用体験を向上
- SF Symbols 6の新機能
- Swift 6へのアプリの移行
- Swift Charts:ベクトルプロットと関数プロット
- SwiftUIでのウインドウの操作
- SwiftUIにおけるアクセシビリティの最新情報
- SwiftUIによるカスタムビジュアルエフェクトの作成
- SwiftUIによるmacOSウインドウのカスタマイズ
- SwiftUIコンテナの解説
- UIのアニメーションとトランジションの向上
- visionOSにおけるカスタムホバーエフェクトの作成
-
ダウンロード
こんにちは ご視聴ありがとうございます Sommerです Samです 2人とも SwiftUIチームのエンジニアです 今日は SwiftUIの新機能について お話できることにワクワクしています 私たちはカラオケが大好きなので チームで定期的にカラオケパーティを 開くためのアプリを開発しています このアプリでは 強化されたSwiftUIの 機能を数多く利用しています ここで紹介できることをうれしく思います まず 皆さんのアプリを まったく新しいものに感じさせる 素晴らしい機能を紹介します プラットフォームを選ばずに アプリの操作性を高めるツールもあります フレームワークの基礎となる 構成要素が大幅に改善され イマーシブ体験を生み出す 新しいツールセットも用意されています ふう… お話しすることがたくさんあるので 早速始めましょう SwiftUIを使えば 新しいタブビュー 美しいメッシュグラデーション 洒落たコントロールなどで アプリを刷新できます Samと私はカラオケイベント プランナーアプリを開発しました 主にサイドバーで操作するアプリです iOS 18.0ではサイドバーの柔軟性が さらに高まっています ボタンを押すだけで メインビューがタブバー表示に変わり 美しいUIをより多く表示できます タブバーはコンテンツの上に浮かんで 表示されます ユーザーは自分の好みに合わせて 画面をカスタマイズし アイテムを並べ替えたり あまり使用しないオプションを 非表示にしたりできます メインビューで 新しいタブビューを使用するように 簡単に書き直すことができました 現在 SwiftUIでは TabViewで 新しいタイプセーフ構文を使用できるため ビルド時に一般的なエラーを 見つけやすくなりました そしてコンテンツが増えるにつれて タブビューをさらに柔軟にするのも簡単です 新しい.sidebarAdaptable タブビュースタイルを適用するだけで Karaoke Plannerの タブバーとサイドバービューを 切り替えられるようになります
これらの曲目リストのように 多数のコンテンツがある場合 サイドバーではタブの並べ替えや削除など その動作をカスタマイズすることができます しかも すべてプログラムでの コントロールが可能です 更新されたサイドバーは tvOSでもすっきり表示されます macOSではタブビューを サイドバーとして表示したり ツールバー上にセグメントコントロールとして 表示したりできます シート表示のサイズ調整機能が すべてのプラットフォームで統一され シンプルになりました .presentationSizingモディファイア (修飾子)を使用すれば .form、.page または カスタムサイズのシートを 作成することができます SwiftUIは新しいズームナビゲーション トランジションもサポートしており パーティーの情報を拡大して 魅力的に表示できます
新しいTabViewについて詳しくは 「Improve your tab and sidebar experience on iPad」をご覧ください 新しいアニメーションについて詳しくは 「Enhance your UI animations and transitions」をご覧ください
ボタンやトグルなど デベロッパ独自のサイズ調整可能な カスタムコントロールを 作成できるようになりました これらはコントロールセンターや ロック画面に表示され アクションボタンで アクティブにすることもできます コントロールは App Intentで簡単に作成できる 新しい種類のウィジェットです 数行のコードで ControlWidgetButtonを作成して WWDCカラオケパーティーを すぐに始めることができます 新しいパワフルな コントロールAPIを使用して 構成可能なボタンやトグルを カスタマイズする方法については 「Access your app's controls across the system」をご覧ください Samと私はカラオケパーティーの参加者を 増やそうと懸命に取り組んできました 指数関数機能では 非常に妥当な目標のように思います
Swift Chartsの関数プロットを 使用すると このLinePlotのような 美しいグラフを簡単に描けます
実際の参加者数もグラフにしてみましょう おっと 目標にはまだまだですね
Swift Chartsの改善点について 詳しくは 「Swift Charts: Vectorized and function plots」をご覧ください データ好きとしては パーティーで歌われている楽曲数も トラッキングしたいと思います TableColumnForEachを使用すると 私達が開催するパーティーの数に応じて 動的に表の列数を変更できます 参加者を増やすため パーティーの招待状を もっとカラフルにしましょう SwiftUIに カラフルなメッシュグラデーションの 便利なサポートが加わりました カラーグリッ上のポイント間を 補間することで 美しい格子を作成できます
これはカラオケの招待状を 豪華なパーティーに見合ったものに するために最適ですね
Karaoke Plannerアプリが 新しくなったところで 歌詞をカスタマイズして 少し楽しみたいと思います 私たちはお気に入りの曲の歌詞を 編集するための ドキュメントベースのアプリも作成しました 新しいDocument Launch Scene タイプを使ってアプリの個性を表現し その機能を強調する起動画面を作成しました 大きな太字のタイトルを作成し 背景をカスタマイズして 楽しげなアクセサリビューを加えて 起動画面の体験を際立たせました カスタムドキュメントアイコンや テンプレートなど ドキュメントベースのアプリで できることについて詳しくは 「Evolve your document launch experience」をご覧ください これから音符にシンボルエフェクトを使って 起動画面を仕上げたいと思います 音符シンボルが本当に揺れてますね
アプリでSF Symbolsの3つの 新しいアニメーションプリセットを 採用できるようになりました Wiggleエフェクトは シンボルを任意の方向や 角度に振動させて注目を集めます Breatheエフェクトは シンボルをスムーズに脈動させて アクティビティが 進行中であることを示します 回転エフェクトは 指定されたアンカーポイントを中心に シンボルの一部を回転させます
既存のプリセットの一部も 新しい機能で強化されています 例えば デフォルトの Replaceアニメーションは 新しいMagicReplace動作を 優先するようになります MagicReplaceを使用すると シンボルでバッジやスラッシュが スムーズにアニメーション表示されます
これらはSF Symbols 6の機能強化の ほんの一部にすぎません 詳しくは「What’s new in SF Symbols」をご覧ください
SwiftUIには Appleのあらゆるプラットフォームで アプリの操作性を向上させる 素晴らしい機能が備わっています ウインドウ表示の強化 入力コントロールの改善 一目で把握できる多数のコンテンツにより どのプラットフォームでもアプリの魅力を 最大限に発揮することができます macOSでは ウインドウのスタイルと動作を カスタマイズできるようになりました macOS用の私の歌詞編集アプリには シングルラインプレビューを表示する ウインドウを実装しました 新しい プレーンウインドウスタイルを使用して デフォルトのウインドウクロームを削除し 他のすべてのウインドウの上に 表示されるように フローティングウインドウレベルを 設定しました そしてdefaultWindowPlacement API を使用して 歌詞が隠れないように 画面の上部に配置しました ディスプレイのサイズと コンテンツのサイズを考慮して 最適な位置に配置しました また プレビューのコンテンツビューに WindowDragGestureを追加し 画面上でドラッグして 位置を調整できるようにしました
新しいシーンタイプもあります ユーティリティウインドウなどです ウインドウのスタイルと動作を カスタマイズする方法について詳しくは 「TailormacOS windows with SwiftUI」をご覧ください マルチウインドウのこの歌詞編集アプリは visionOSでもスムーズに動作します 先日カラオケ仲間のAndrewから Botanistアプリで この新しいプッシュウインドウ アクションを使用し 最も重要なコンテンツに フォーカスする方法を教わりました pushWindowを使用すれば ウインドウを開き 元のウインドウを 非表示にすることができます 歌詞編集アプリでも同じことをしたいので pushWindow環境アクションを使用して 歌詞プレビューにフォーカスします 詳しくは ビデオ「Work with windows in SwiftUI」をご覧ください SwiftUIは 各プラットフォームが提供する ユニークな入力方法を活用するための 新しいツールを数多く備えています visionOSでは ユーザーのプライバシーを保護しながら ユーザーが視線を向けたり 指を近づけたり そちらにポインタを動かしたりしたとき ビューが反応するように設定できます
新しいクロージャベースの hoverEffectモディファイア内では ビューがアクティブ状態と 非アクティブ状態の間を 移行する際の外観をコントロールできます 複数のエフェクトを調整したり エフェクトのタイミングをコントロールしたり アクセシビリティ設定に応じて 反応したりする方法について詳しくは 「Create custom hover effects in visionOS」をご覧ください iPadOS、macOS visionOSの優れたアプリでは 優れたキーボードサポートが 提供されています
macOSのメインメニューに プレビューウインドウを開くための 項目を追加しました
新しいmodifierKeyAlternate モディファイアを追加し このオプションキーを押したとき 全画面でプレビューするための セカンダリ項目が表示されるようにしました
低レベルのコントロールでは どのビューでも修飾キーの 押下状態の変化に反応できます 私は歌詞編集アプリを更新して onModifierKeysChangedを使用しました オプションキーを押し下げるとバウンドする ボールが歌詞のどこに落ちるかを示す アラインメントガイドが表示され 調整することができます ポインタ操作は 多くのデバイスで 重要な入力形式の1つです pointerStyle APIを使用すると システムポインタの外観と 表示をカスタマイズできます 歌詞をサイズ変更できるようにしたので 各サイズ変更アンカーに適切な frameResizeポインタスタイルを 適用しましょう iPadOS 17.5では 新たに Apple Pencilと Apple Pencil Proの機能が SwiftUIでサポートされるようになりました ダブルタップやスクイーズなどです
.onPencilSqueezeを使用すると ジェスチャから情報を収集して 求められているアクションを確認できます ここでは ペンシルのホバー位置の下に 歌詞の落書きパレットを表示し 楽しいイラストを 歌詞に追加できるようにします 新しいApple Pencilの APIについて詳しくは 「Squeeze the most out of Apple Pencil」をご覧ください ウィジェットにはアプリの情報や 主要な操作を 一目でわかるように表示できます さらに ライブアクティビティが watchOSにも導入され デベロッパ側で何もしなくても iOSベースのライブアクティビティが Apple Watchに 自動的に表示されるようになりました
Samと私は 既にこのライブアクティビティを利用して 外出先で歌詞を確認できるようにしています 私のApple Watchにも 歌詞が自動的に表示されます さらに 新しい.smallを .supplementalActivityFamilyで使用して watchOS向けにコンテンツを調整します これで より多くの歌詞が一度に 表示されるようになります 素晴らしい また 歌い手がダブルタップで 歌詞を進められるようにするために .handGestureShortcut モディファイアを適用します
加えて カラオケイベントで 次にいつ 唄えるかを常に把握しておきたいので Karaoke Plannerアプリに ウィジェットを追加し 新しい 参照日付フォーマットスタイルを使用して 自分の番までのカウントダウンが 表示されるようにしました
新しいフォーマットを追加すれば 日時をリアルタイムで 表示することができます ウィジェットや ライブアクティビティに最適です
これらのフォーマットには 日付参照 日付オフセット タイマーが含まれます 各フォーマットは構成要素に至るまで 詳細にカスタマイズでき コンテナのサイズに 適応させることもできます ウィジェットがよりスマートになりました 関連するコンテキストを指定して スマートスタックなどに より インテリジェントに 表示されるようにしましょう これで 指定した時刻になったとき または参加者が予定の カラオケ会場に近づいたとき カウントダウンウィジェットが 自動的に表示されます ねえSommer 歌詞編集アプリの進み具合はどう? 次のソロ曲「Cupertino Dreamin」の 歌詞を考えてるんだけど 忘れる前に書き留めなきゃと思ってるんだ 順調よ 「Smells Like Scene Spirit」の歌詞を このアプリで書いたところ すごくいい感じ いいね それをセットリストの最後に 入れるのはどうかな そうそう セットリスト! WWDCのカラオケパーティーまでに 準備できる? もちろんだよ Sommer 曲のテーマは すべてSwiftUIの 新しい魅力的なフレームワーク基盤に 関するものだよ SwiftUIには新しい 様々なAPIが追加されており これまで以上に簡単に このフレームワークをご利用いただけます 例えば SwiftUIの コアコンポーネントが機能強化され 基本的なAPIの新たな使用方法が 追加されました 使いやすさも向上しています また 独自にカスタムコンテナビューを 作成できるようになりました ForEachの新しいAPIである subviewOfを使用すると 指定したビューのサブビューの イテレーションが可能になります この例では 各サブビューを独自の カードビューにラップしています これを使ってリストやピッカーなどの SwiftUIの組み込みコンテナと 同じ機能を持つ カスタムコンテナを作成できます これには静的および動的コンテンツの ミックスセクションの サポートコンテナ固有の モディファイアの追加などが含まれます カスタムコンテナとそれに関連する SwiftUIの基礎については 「Demystify SwiftUI Containers」を ご覧ください 使いやすさが向上し SwiftUIの操作が これまで以上に簡単になりました EnvironmentKeyへの 完全な準拠や EnvironmentValuesの拡張を 記述する必要がありません 新しいEntryマクロを使用して シンプルなプロパティを記述するだけです なにより EnvironmentValuesだけでなく FocusValues、Transaction 新しいContainerValuesでも Entryマクロを使用できる点が最高です さらに SwiftUIの組み込み アクセシビリティラベルに追加情報を 付加できるようになりました これにより フレームワークから 提供されたラベルを上書きすることなく コントロールに アクセシビリティ情報を追加できます 条件付きモディファイアのサポートや App Intentベースの accessibilityActionsなど SwiftUIの新しい便利な アクセシビリティ機能について詳しくは 「Catchup on accessibility in SwiftUI」をご覧ください Xcodeプレビューでは 新しい 動的リンクアーキテクチャが採用されています プロジェクトを再ビルドしなくても プレビューと ビルド/実行を 切り替えることができるため イテレーションのスピードが向上します
プレビューのセットアップも 簡単になりました Previewableマクロを使用して プレビュー内で Stateを直接指定できます プレビューコンテンツをビューにラップする ボイラープレートは必要ありません テキストの操作や選択の管理も 新しくなります テキスト編集コントロール内の テキスト選択に対して プログラムによるアクセスと コントロールが提供されます 歌詞フィールドで選択された テキストに合わせて 選択バインディングの内容が更新されます
また 選択範囲などのプロパティを 読み取れるようになりました これを使用すれば インスペクタで選択された 単語の韻を表示することができます
.searchFocusedを使用すると 検索フィールドのフォーカス状態を プログラムで制御できます 例えば 検索フィールドにフォーカスが あるかを確認し検索フィールドに (または検索フィールドから)プログラム的に フォーカスを移動することができます
また 任意のテキストフィールドに テキストの提案を 追加できるようになりました これを使って行の終了方法を提案できます 提案は ドロップダウンメニューとして表示され オプションを選択すると 該当する完成形で テキストフィールドが更新されます SwiftUIには新しい グラフィックス機能も追加されています 色を美しく混ぜ合わせることが できるようになりました 色の新しいmixモディファイアで別の色と 特定の量で混ぜ合わせることができます またカスタムシェーダ機能を拡張し 最初に使用する前に シェーダを 事前コンパイルできるようになったため 遅延シェーダコンパイルによる フレームドロップを 避けられるようになりました さらに スクロールビューを 細かくコントロールするための 新しいAPIも用意されています ScrollViewの状態とより深く 統合できるようになりました .onScrollGeometryChangeを 使用すると コンテンツのオフセットや コンテンツサイズなどの 変化に効率的に対応できます 例えば この ボタンは スクロールビューのコンテンツの上部を スクロールすると表示されます
また スクロールによるビューの 可視性の変化も検出できるようになりました こちらの自動再生ビデオのように コンテンツが画面内に表示されたり 消えたりする状況に基づいた 素晴らしい体験を作成できます
スクロールビューをプログラムで コントロールするだけでなく 画面の上端など 新しいスクロール位置にも プログラムで スクロールさせることができます
完璧なスクロール体験を実現するため 軸に沿ったバウンスをオフにしたり スクロールをプログラムで停止したり コンテンツの配置を細かく コントロールしたりするなど 調整できるさまざまな追加設定があります 新しいSwift 6の言語モードでは コンパイル時のデータ競合の 安全性を確保しています SwiftUIでは新しい言語モードを アプリに採用しやすくするため APIの改善を行いました SwiftUIのビューは 常にメインアクターで評価されます それに合わせてビュープロトコルに メインアクター注釈が付けられました つまり Viewに準拠するすべてのタイプは デフォルトで暗黙的に メインアクターに隔離されます そのため Viewを明示的に メインアクターとしてマークした場合 動作を一切変更することなく その注釈を削除できます 新しいSwift 6の言語モードは オプトイン型のため 準備が整えばいつでも活用できます コンパイル時のチェックについて詳しくは 「Migrate your app to Swift 6」 をご覧ください SwiftUIは 新しいアプリの構築時だけでなく UIKitやAppKitで開発された 既存のアプリに 新機能を追加する際にも 利用できるように設計されています これらのフレームワークによる 優れた相互運用性は非常に重要です ジェスチャとアニメーションの統合が 大幅に向上しました 組み込みまたはカスタムの UIGestureRecognizerを SwiftUIビュー階層で 活用できるようになりました これは UIKitに直接依存していない SwiftUIビューでも機能します 相互運用性の向上は 逆方向にも進んでいます UIKitとAppKitで SwiftUIのアニメーションのパワーを 活用できるようになりました SwiftUIでは UIViewと NSAnimationContextで 新しいanimate関数が定義されており UIKitとAppKitの変更を インプロセスの SwiftUIアニメーションを使用して アニメーション表示することができます ジェスチャで動くアニメーションの速度も SwiftUIビューと同様に 自動的に保持されます UIおよびNSViewRepresentableの コンテキストでは 新しいAPIが提供され SwiftUIで開始されたアニメーションを UIKitおよびAppKitに橋渡しし フレームワークの境界を越えて アニメーションが完璧に同期して 実行されることが保証されます フレームワーク全体でアニメーションを 使用する方法について詳しくは 「Enhance your UI animations and transitions」をご覧ください こうした基本的な機能強化は 素晴らしいですね あとはカラオケの練習をしてスキルアップし Sommerと一緒に パーティーを開催するだけです SwiftUIの新しいツールで実現する 開発体験は 素晴らしい練習アプリの作成と イベントの成功の両方に役立ちます ボリューム イマーシブ空間 新しいテキストエフェクトを実現する 新しいAPIが 私たちのカラオケ体験に 命を吹きこんでくれます 歌の練習をするため ボリューム内にマイクを配置した visionOSの練習アプリを作成しました visionOS 2ではボリュームに ベースプレートを表示できます これによりボリュームの境界を把握し 新しいサイズ変更ハンドルを含む ウインドウコントロールを利用できます マイクには既に マイクスタンドベースがありますが このベースプレートはない方がよさそうです .volumeBaseplateVisibility モディファイアを使用して システム提供のベースプレートを 無効にしたいと思います いいですね
また 新しい.onVolumeViewpointChange モディファイアを使用して マイクが常に歌い手の方を 向くようにしました これはボリュームの別の側に 移動するたびに呼び出され ユーザーの視点の変化に 対応できるようになっています マイクが用意できたので 次はそれを置く場所が必要です カラオケを歌うのに最適な場所は ムーディーなカラオケラウンジでしょう 既に美しいイマーシブ空間があるので 許可するイマーシブレベルを コントロールします 最初のイマーシブレベルを50% 最低40%に設定し 歌い手がこのカラオケラウンジに 慣れることができるようにします
雰囲気を作るため プログレッシブイマーシブ空間の 周りのパススルービデオに エフェクトを適用します ビデオパススルーを暗くする preferred-surroundings-effectや カラオケ体験を特別なものにする クールなムード照明のための colorMultiplyを使用できます
おお いいですね
新しい装飾の取り付け方法や サポートされる視点の指定方法など ボリュームとイマーシブ空間の 機能強化について詳しくは 「Dive deep into volumes and immersive spaces」をご覧ください
ステージは整いましたが パーティーを始める前に 歌詞を用意する必要があります
SwiftUI Text ビューを カスタムレンダリングエフェクトや インタラクション動作で拡張することができます これを使ってカラオケの歌詞に 単語のハイライトエフェクトを 付けることができます カラオケレンダラーが元の描画の背後に テキストのコピーを作成し それをぼかして 紫色に輝いて見えるように着色します このハイライトエフェクトを 特定の単語だけに適用し 最終的な仕上げを加えることで このような見事なエフェクトを 生み出すことができます こうした素晴らしいテキストエフェクトの 作成方法について詳しくは 「Create custom visual effectswithSwiftUI」をご覧ください さてSommer これでパーティーの準備は すべて整ったね 本当にうまくいっていると思うわ SwiftUIのこれらの変更は Sommerと私がアプリを最大限に 活用するために非常に役立ちました 皆さんもぜひ ご自身のアプリで活用してください サイドバーベースの iPadアプリやtvOSアプリでは 新しいタブビューAPIを活用して 柔軟性を高めましょう ドキュメントベースのアプリでは 新しいドキュメントローンチ体験を 強化するとよいでしょう macOSとvisionOSのアプリには 新しいウインドウ表示や 入力機能を追加しましょう watchOSでの体験に調整を加えて ライブアクティビティを 最大限に活用しましょう そして visionOSでは ボリュームとイマーシブ空間の 新しい機能をぜひご活用ください 今年はSwiftUIデベロッパにとって 良い年になりそうです カラオケファンにとってもね それではSam 準備はいい? もちろんさ Sommer Hey Siri パーティーを始めて!
-
-
1:38 - TabView
import SwiftUI struct KaraokeTabView: View { @State var customization = TabViewCustomization() var body: some View { TabView { Tab("Parties", image: "party.popper") { PartiesView(parties: Party.all) } .customizationID("karaoke.tab.parties") Tab("Planning", image: "pencil.and.list.clipboard") { PlanningView() } .customizationID("karaoke.tab.planning") Tab("Attendance", image: "person.3") { AttendanceView() } .customizationID("karaoke.tab.attendance") Tab("Song List", image: "music.note.list") { SongListView() } .customizationID("karaoke.tab.songlist") } .tabViewStyle(.sidebarAdaptable) .tabViewCustomization($customization) } } struct PartiesView: View { var parties: [Party] var body: some View { Text("PartiesView") } } struct PlanningView: View { var body: some View { Text("PlanningView") } } struct AttendanceView: View { var body: some View { Text("AttendanceView") } } struct SongListView: View { var body: some View { Text("SongListView") } } struct Party { static var all: [Party] = [] } #Preview { KaraokeTabView() }
-
2:28 - Presentation sizing
import SwiftUI struct AllPartiesView: View { @State var showAddSheet: Bool = true var parties: [Party] = [] var body: some View { PartiesGridView(parties: parties, showAddSheet: $showAddSheet) .sheet(isPresented: $showAddSheet) { AddPartyView() .presentationSizing(.form) } } } struct PartiesGridView: View { var parties: [Party] @Binding var showAddSheet: Bool var body: some View { Text("PartiesGridView") } } struct AddPartyView: View { var body: some View { Text("AddPartyView") } } struct Party { static var all: [Party] = [] } #Preview { AllPartiesView() }
-
2:39 - Zoom transition
import SwiftUI struct PartyView: View { var party: Party @Namespace() var namespace var body: some View { NavigationLink { PartyDetailView(party: party) .navigationTransition(.zoom( sourceID: party.id, in: namespace)) } label: { Text("Party!") } .matchedTransitionSource(id: party.id, in: namespace) } } struct PartyDetailView: View { var party: Party var body: some View { Text("PartyDetailView") } } struct Party: Identifiable { var id = UUID() static var all: [Party] = [] } #Preview { @Previewable var party: Party = Party() NavigationStack { PartyView(party: party) } }
-
3:18 - Controls API
import WidgetKit import SwiftUI struct StartPartyControl: ControlWidget { var body: some ControlWidgetConfiguration { StaticControlConfiguration( kind: "com.apple.karaoke_start_party" ) { ControlWidgetButton(action: StartPartyIntent()) { Label("Start the Party!", systemImage: "music.mic") Text(PartyManager.shared.nextParty.name) } } } } // Model code class PartyManager { static let shared = PartyManager() var nextParty: Party = Party(name: "WWDC Karaoke") } struct Party { var name: String } // AppIntent import AppIntents struct StartPartyIntent: AppIntent { static let title: LocalizedStringResource = "Start the Party" func perform() async throws -> some IntentResult { return .result() } }
-
3:49 - Function plotting
import SwiftUI import Charts struct AttendanceView: View { var body: some View { Chart { LinePlot(x: "Parties", y: "Guests") { x in pow(x, 2) } .foregroundStyle(.purple) } .chartXScale(domain: 1...10) .chartYScale(domain: 1...100) } } #Preview { AttendanceView() .padding(40) }
-
4:18 - Dynamic table columns
import SwiftUI struct SongCountsTable: View { var body: some View { Table(Self.guestData) { // A static column for the name TableColumn("Name", value: \.name) TableColumnForEach(Self.partyData) { party in TableColumn(party.name) { guest in Text(guest.songsSung[party.id] ?? 0, format: .number) } } } } private static func randSongsSung(low: Bool = false) -> [Int : Int] { var songs: [Int : Int] = [:] for party in partyData { songs[party.id] = low ? Int.random(in: 0...3) : Int.random(in: 3...12) } return songs } private static let guestData: [GuestData] = [ GuestData(name: "Sommer", songsSung: randSongsSung()), GuestData(name: "Sam", songsSung: randSongsSung()), GuestData(name: "Max", songsSung: randSongsSung()), GuestData(name: "Kyle", songsSung: randSongsSung(low: true)), GuestData(name: "Matt", songsSung: randSongsSung(low: true)), GuestData(name: "Apollo", songsSung: randSongsSung()), GuestData(name: "Anna", songsSung: randSongsSung()), GuestData(name: "Raj", songsSung: randSongsSung()), GuestData(name: "John", songsSung: randSongsSung(low: true)), GuestData(name: "Harry", songsSung: randSongsSung()), GuestData(name: "Luca", songsSung: randSongsSung()), GuestData(name: "Curt", songsSung: randSongsSung()), GuestData(name: "Betsy", songsSung: randSongsSung()) ] private static let partyData: [PartyData] = [ PartyData(partyNumber: 1, numberGuests: 5), PartyData(partyNumber: 2, numberGuests: 6), PartyData(partyNumber: 3, numberGuests: 7), PartyData(partyNumber: 4, numberGuests: 9), PartyData(partyNumber: 5, numberGuests: 9), PartyData(partyNumber: 6, numberGuests: 10), PartyData(partyNumber: 7, numberGuests: 11), PartyData(partyNumber: 8, numberGuests: 12), PartyData(partyNumber: 9, numberGuests: 11), PartyData(partyNumber: 10, numberGuests: 13), ] } struct GuestData: Identifiable { let name: String let songsSung: [Int : Int] let id = UUID() } struct PartyData: Identifiable { let partyNumber: Int let numberGuests: Int let symbolSize = 100 var id: Int { partyNumber } var name: String { "\(partyNumber)" } } #Preview { SongCountsTable() .padding(40) }
-
4:42 - Mesh gradients
import SwiftUI struct MyMesh: View { var body: some View { MeshGradient( width: 3, height: 3, points: [ .init(0, 0), .init(0.5, 0), .init(1, 0), .init(0, 0.5), .init(0.3, 0.5), .init(1, 0.5), .init(0, 1), .init(0.5, 1), .init(1, 1) ], colors: [ .red, .purple, .indigo, .orange, .cyan, .blue, .yellow, .green, .mint ] ) } } #Preview { MyMesh() .statusBarHidden() }
-
5:14 - Document launch scene
DocumentGroupLaunchScene("Your Lyrics") { NewDocumentButton() Button("New Parody from Existing Song") { // Do something! } } background: { PinkPurpleGradient() } backgroundAccessoryView: { geometry in MusicNotesAccessoryView(geometry: geometry) .symbolEffect(.wiggle(.rotational.continuous())) } overlayAccessoryView: { geometry in MicrophoneAccessoryView(geometry: geometry) }
-
7:04 - Window styling and default placement
Window("Lyric Preview", id: "lyricPreview") { LyricPreview() } .windowStyle(.plain) .windowLevel(.floating) .defaultWindowPlacement { content, context in let displayBounds = context.defaultDisplay.visibleRect let contentSize = content.sizeThatFits(.unspecified) return topPreviewPlacement(size: contentSize, bounds: displayBounds) } }
-
7:30 - Window Drag Gesture
Text(currentLyric) .background(.thinMaterial, in: .capsule) .gesture(WindowDragGesture())
-
8:18 - Push window environment action
struct EditorView: View { @Environment(\.pushWindow) private var pushWindow var body: some View { Button("Play", systemImage: "play.fill") { pushWindow(id: "lyric-preview") } } }
-
8:47 - Hover effects
struct ProfileButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .background(.thinMaterial) .hoverEffect(.highlight) .clipShape(.capsule) .hoverEffect { effect, isActive, _ in effect.scaleEffect(isActive ? 1.05 : 1.0) } } }
-
9:14 - Modifier key alternates
Button("Preview Lyrics in Window") { // show preview in window } .modifierKeyAlternate(.option) { Button("Preview Lyrics in Full Screen") { // show preview in full screen } } .keyboardShortcut("p", modifiers: [.shift, .command])
-
9:32 - Responding to modifier keys
var body: some View { LyricLine() .overlay(alignment: .top) { if showBouncingBallAlignment { // Show bouncing ball alignment guide } } .onModifierKeysChanged(mask: .option) { showBouncingBallAlignment = !$1.isEmpty } }
-
9:55 - Pointer customization
ForEach(resizeAnchors) { anchor in ResizeHandle(anchor: anchor) .pointerStyle(.frameResize(position: anchor.position)) }
-
10:23 - Pencil squeeze gesture
@Environment(\.preferredPencilSqueezeAction) var preferredAction var body: some View { LyricsEditorView() .onPencilSqueeze { phase in if preferredAction == .showContextualPalette, case let .ended(value) = phase { if let anchorPoint = value.hoverPose?.anchor { lyricDoodlePaletteAnchor = .point(anchorPoint) } lyricDoodlePalettePresented = true } }
-
13:13 - Custom containers
struct DisplayBoard<Content: View>: View { @ViewBuilder var content: Content var body: some View { DisplayBoardCardLayout { ForEach(subviewOf: content) { subview in CardView { subview } } } .background { BoardBackgroundView() } } } DisplayBoard { Text("Scrolling in the Deep") Text("Born to Build & Run") Text("Some Body Like View") ForEach(songsFromSam) { song in Text(song.title) } }
-
13:35 - Custom containers with sectioning
DisplayBoard { Section("Matt's Favorites") { Text("Scrolling in the Deep") Text("Born to Build & Run") Text("Some Body Like View") .displayBoardCardRejected(true) } Section("Sam's Favorites") { ForEach(songsFromSam) { song in Text(song.title) } } }
-
13:52 - Entry macro
extension EnvironmentValues { @Entry var karaokePartyColor: Color = .purple } extension FocusValues { @Entry var lyricNote: String? = nil } extension Transaction { @Entry var animatePartyIcons: Bool = false } extension ContainerValues { @Entry var displayBoardCardStyle: DisplayBoardCardStyle = .bordered }
-
14:12 - Default accessibility label augmentation
SongView(song) .accessibilityElement(children: .combine) .accessibilityLabel { label in if let rating = song.rating { Text(rating) } label }
-
14:52 - Previewable
#Preview { @Previewable @State var showAllSongs = true Toggle("Show All songs", isOn: $showAllSongs) }
-
15:06 - Programatic text selection
struct LyricView: View { @State private var selection: TextSelection? var body: some View { TextField("Line \(line.number)", text: $line.text, selection: $selection) // ... } }
-
15:19 - Getting selected ranges
InspectorContent(text: line.text, ranges: selection?.ranges)
-
15:29 - Binding to search field focus state
// Binding to search field focus state struct SongSearchView: View { @FocusState private var isSearchFieldFocused: Bool @State private var searchText = "" @State private var isPresented = false var body: some View { NavigationSplitView { Text("Power Ballads") Text("Show Tunes") } detail: { // ... if !isSearchFieldFocused { Button("Find another song") { isSearchFieldFocused = true } } } .searchable(text: $searchText, isPresented: $isPresented) .searchFocused($isSearchFieldFocused) } }
-
15:41 - Text suggestions
TextField("Line \(line.number)", text: $line.text) .textInputSuggestions { ForEach(lyricCompletions) { Text($0.attributedCompletion) .textInputCompletion($0.text) } }
-
15:59 - Color mixing
Color.red.mix(with: .purple, by: 0.2) Color.red.mix(with: .purple, by: 0.5) Color.red.mix(with: .purple, by: 0.8)
-
16:13 - Custom shaders
ContentView() .task { let slimShader = ShaderLibrary.slim() try! await slimShader.compile(as: .layerEffect) }
-
16:23 - React to scroll geometry changes
struct ContentView: View { @State private var showBackButton = false ScrollView { // ... } .onScrollGeometryChange(for: Bool.self) { geometry in geometry.contentOffset.y < geometry.contentInsets.top } action: { wasScrolledToTop, isScrolledToTop in withAnimation { showBackButton = !isScrolledToTop } } }
-
16:42 - React to scroll visibility changes
struct AutoPlayingVideo: View { @State private var player: AVPlayer = makePlayer() var body: some View { VideoPlayer(player: player) .onScrollVisibilityChange(threshold: 0.2) { visible in if visible { player.play() } else { player.pause() } } } }
-
16:54 - New scroll positions
struct ContentView: View { @State private var position: ScrollPosition = .init(idType: Int.self) var body: some View { ScrollView { // ... } .scrollPosition($position) .overlay { FloatingButton("Back to Invitation") { position.scrollTo(edge: .top) } } } }
-
18:17 - Gesture interoperability
struct VideoThumbnailScrubGesture: UIGestureRecognizerRepresentable { @Binding var progress: Double func makeUIGestureRecognizer(context: Context) -> VideoThumbnailScrubGestureRecognizer { VideoThumbnailScrubGestureRecognizer() } func handleUIGestureRecognizerAction( _ recognizer: VideoThumbnailScrubGestureRecognizer, context: Context ) { progress = recognizer.progress } } struct VideoThumbnailTile: View { var body: some View { VideoThumbnail() .gesture(VideoThumbnailScrubGesture(progress: $progress)) } }
-
18:34 - SwiftUI animations in UIKit and AppKit
let animation = SwiftUI.Animation.spring(duration: 0.8) // UIKit UIView.animate(animation) { view.center = endOfBracelet } // AppKit NSAnimationContext.animate(animation) { view.center = endOfBracelet }
-
18:57 - Representable animation bridging
struct BeadBoxWrapper: UIViewRepresentable { @Binding var isOpen: Bool func updateUIView(_ box: BeadBox, context: Context) { context.animate { box.lid.center.y = isOpen ? -100 : 100 } } }
-
19:59 - Volume baseplate visibility
struct KaraokePracticeApp: App { var body: some Scene { WindowGroup { ContentView() } .windowStyle(.volumetric) .defaultWorldScaling(.trueScale) .volumeBaseplateVisibility(.hidden) } }
-
20:15 - React to volume viewpoint changes
struct MicrophoneView: View { @State var micRotation: Rotation3D = .identity var body: some View { Model3D(named: "microphone") .onVolumeViewpointChange { _, new in micRotation = rotateToFace(new) } .rotation3DEffect(micRotation) .animation(.easeInOut, value: micRotation) } }
-
20:38 - Control allowed immersion levels
struct KaraokeApp: App { @State private var immersion: ImmersionStyle = .progressive( 0.4...1.0, initialAmount: 0.5) var body: some Scene { ImmersiveSpace(id: "Karaoke") { LoungeView() } .immersionStyle(selection: $immersion, in: immersion) } }
-
21:00 - Preferred surrounding effects
struct LoungeView: View { var body: some View { StageView() .preferredSurroundingsEffect(.colorMultiply(.purple)) } }
-
21:33 - Custom text renderers
struct KaraokeRenderer: TextRenderer { func draw( layout: Text.Layout, in context: inout GraphicsContext ) { for line in layout { for run in line { var glow = context glow.addFilter(.blur(radius: 8)) glow.addFilter(purpleColorFilter) glow.draw(run) context.draw(run) } } } } struct LyricsView: View { var body: some View { Text("A Whole View World") .textRenderer(KaraokeRenderer()) } } #Preview { LyricsView() }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。