ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
watchOS 26の新機能
watchOS 26の新機能を取り上げ、それらをwatchOSとiOSのアプリに統合する方法を学びます。ARM64アーキテクチャを紹介し、新しいデザインシステムについて詳しく説明します。また、ウィジェットのアップデートや、Apple Watchにコントロールを追加する方法も紹介します。
関連する章
- 0:00 - Introduction
- 0:56 - Update for watchOS 26
- 3:48 - Bring apps to new places
- 9:44 - Be relevant and up-to-date
リソース
- Creating controls to perform actions across the system
- Increasing the visibility of widgets in Smart Stacks
- Making a configurable widget
- MapKit
- Migrating ClockKit complications to WidgetKit
- Workouts and activity rings
関連ビデオ
WWDC25
WWDC24
WWDC23
WWDC22
-
このビデオを検索
こんにちは Anneです 本セッションでは watchOS 26の新機能と それらをアプリで活用するための ヒントをご紹介します まずwatchOS 26の新機能を説明した後 watchOSとiOSのアプリを Apple Watchのより多くの場所で 提供する方法を紹介し さらに アプリで関連性の高いコンテンツを 適時に表示する新しい方法を解説します 途中でアプリ構築のデモも行います このアプリは 近くのビーチでの ウェルネスやレクリエーションの アクティビティを表示したり 各地のビーチの海の状態を表示したり ビーチで またはビーチに想いを馳せながら ほかの場所で過ごしたマインドフルな時間を 効果的に追跡したりできるアプリです さっそく始めましょう watchOS 26は フレッシュな新しい外観から 新しいシステムアーキテクチャまで 全面的に改善されています また アプリで新しいwatchOSを 最大限に活用するために 知っておくべき情報もあります 新しいデザインシステムは watchOSの全体に適用されます 素材とコントロールの変更 アプリアイコンのアップデート 文字盤やコントロールセンターなどの システムスペースの変更などです ツールバーとコントロールのスタイルも watchOS 26で更新されました watchOS 10以降向けのアプリはすべて 新しいスタイルを自動的に使用するので アプリとシステムの他の部分の 統一性が維持されます アプリを実行して インターフェイスの 外観を確認してみましょう カスタムスタイルを使っている場合 新しい システムスタイルがインターフェイス要素の 可読性に影響しないかご確認ください アプリのデザインと アップデートの詳細については 「Meet Liquid Glass」をご覧ください
iOS/watchOS 26では アプリアイコンの外観も刷新されています
アイコンはIcon Composerで 更新できます 更新されたiOSアプリのアイコンは iPhoneからApple Watchに転送される 通知でも表示されます 更新されたWatchアプリのアイコンは アプリのグリッドとリストや Apple Watchに直接送信される 通知に表示されます Icon Composerと アプリアイコンに関する各種の新機能の 詳細については 「Create icons with Icon Composer」をご覧ください デザインシステムに加え watchOSは 新しいシステムアーキテクチャを サポートしています Apple Watch Series 9以降と Apple Watch Ultra 2の watchOS 26では ARM64アーキテクチャを採用してます Xcodeで Apple Watchをターゲットに する場合は 標準アーキテクチャの ビルド設定を使用すれば そのターゲットで すべてのApple Watch アーキテクチャ向けのビルドを行えます ARM64における型の違いには 注意が必要です 特にfloat型やint型を使用する場合や ポインタベースの計算では要注意です また 必ずシミュレータとデバイスの 両方でテストしてください Xcodeは Xcode 14以降 ARM64アーキテクチャ向けの Apple Watchアプリのビルドを サポートしています Apple Watchシミュレータは常にApple シリコンのARM64アーキテクチャを使います そして 嬉しいことに ビルドにおいて既に 標準アーキテクチャを使用している場合 ARM64向けのビルドに対応済みです また 以前からテストに シミュレータを使用している場合 ARM64向けのテストに対応済みです 新しいデザインシステムと アーキテクチャに関する刷新は watchOS 26の 最新アップデートのほんの一部です 次に アプリの新しい提供場所を いくつかご紹介しましょう コントロールは watchOS 26から Apple Watchで利用可能になりました ユーザーは コントロールセンターや スマートスタックにコントロールを配置し Apple Watch Ultraのアクションボタンで 使用できます コントロールをWidgetKitで構築すれば ユーザーはアプリを開かずに 素早くアクションを実行したり アプリを特定のビューで起動したりできます コントロールにはシンボル タイトル その他のシステム関連の 背景情報を表示し 色も設定できます サポート対象のシステムスペースに ユーザーがコントロールを追加すると コンテキストに応じた コントロールが表示されます
ユーザーは iPhoneアプリのコントロールを Watchアプリがなくても Apple Watchの システムスペースに追加できます Apple Watchでコントロールをタップすると ペアリングされているiPhoneで アクションが実行されます アクションはiPhoneで実行されるため iPhoneアプリでフォアグラウンドに アクションが表示されるコントロールは Apple Watchには表示されません Watchアプリがある場合は iOSのコントロールの構築に使ったものと 同じAPIを使って Apple Watchの コントロールを構築することもできます コントロールをタップすると Apple Watchでアクションが実行されます
スマートスタックにコントロール ウィジェット ライブアクティビティの サポートが追加され コンテンツの 表示方法が非常に多くなったので スマートスタックに何を表示すべきか 迷うかもしれません この場合 主な目的を考慮するのが有効です コントロールが適しているのは 主な目的がアクションの実行である場合です 例えば アプリの設定の変更や インターネットに接続された デバイスの電源をオンにするなどの アクションです ウィジェットが適しているのは 一日を通して情報を表示する場合です 例えば 天気情報や 近日予定のイベントの表示などです ライブアクティビティが適しているのは 開始と終了が明確なイベントの表示です スポーツイベントや航空機のフライトの 進行状況などです コントロールを使い始める方法と 詳細情報については 「Extend your app’s controls across the system」でCliffの解説をお聴きください アプリのウィジェットやコントロールを カスタマイズしたいユーザーもいます iOSでは デベロッパが行った システム設定から選択することで ユーザーがウィジェットを編集できます watchOS 26では ユーザーは ウィジェットとコントロールを同じ方法で カスタマイズできるようになりました ウィジェットでは 事前作成済みの 推奨設定を提供するのではなく ユーザーが設定できるということを 表示できます 画面の例では ウィジェットの設定を変更して アメリア島のカレンダーを表示しています ウィジェットを設定可能にするには 推奨事項に 空の配列を返し 事前設定済みの ウィジェットがないこと 文字盤かスマートスタックで ウィジェットを設定できることを示します 設定可能にしたい 既存のウィジェットがある場合は watchOS 26の可用性チェックを 追加します 空の配列を返し サポートされているバージョンで ウィジェットが 設定可能であることを示します 以前のバージョンでは App Intentの推奨事項を返し続けます コントロールも設定できます 例えば ビーチアプリで メディテーションの時間について 追加の設定をするとします いつでもビーチに行けるわけではないので メディテーション中に 海の音を流せるようにしたいですね コントロールを設定可能にするには AppIntentControlConfigurationで iOSの場合と同じようにコントロールを記述し AppIntentControlValueProviderに 値プロバイダを指定します watchOS 26の スマートスタックに加わるのは コントロールだけではありません WatchアプリでHealthKitを使って ワークアウトを記録する場合は ユーザーのルーチンに合わせて スマートスタックに表示されます タップすると素早く ワークアウトを開始できます
おすすめのワークアウトアプリが 適切に表示されるように HKWorkoutActivityTypeを 正しく指定し ワークアウトの開始と終了の時間を 正しく記録して HKWorkoutRouteBuilderを使って ワークアウト全体の場所データを追加します 新しい場所でアプリを使う場合 watchOS 26ではMapKit向けの 多くの新機能を利用できます 食料雑貨店など近くの気になる場所を検索し 自動車 徒歩 自転車など移動手段に応じ 目的地までのルートを取得して SwiftUIでマップ上のオーバーレイとして ルートを表示します iOSで使い慣れているAPIを使って すべて対応できます 近くの場所の検索や 方向の表示などの機能を持つ 独立したWatchアプリにとって 便利な仕様です お気に入りのビーチへの ルートを示しています ルートや場所の検索について詳しくは Jeffの「Meet MapKit for SwiftUI」を ご確認ください カスタムコントロールと コントロールセンター 文字盤のおすすめのワークアウトアプリ 新しいMapKit機能など watchOS 26はたくさんの方法で システム上に新たな体験を もたらします
watchOS 26のこのツアーでは コンテキストや関連性のある体験を スマートスタックに生み出す 新たな方法も紹介します 重要なタイミングで ほしい情報やアクションを表示し その情報を最新の状態に保ちます watchOS 26には 新しいフレームワークが導入されます RelevanceKitです RelevanceKitは必要なときにだけ コンテンツを表示します
RelevanceKitには関連する コンテキストタイプがたくさんあり コンテキストのキューに応じて おすすめのウィジェットを表示します 関連するコンテキストには 日付 睡眠スケジュール フィットネス情報 場所などがあります watchOS 26ではウィジェットに 興味のあるカテゴリを関連付けて 食料雑貨店 カフェ ビーチなど 場所のタイプと ウィジェットの関連付けを 示すことができます ビーチアプリでは ユーザーがビーチにいると 現在の海の状況が スマートスタックに表示されます MapKitの興味のあるカテゴリ用に 場所のRelevantContextを作成します カテゴリがサポートされていない場合 nilを返します ウィジェットに関連付けられた コンテキストはすべて WidgetRelevanceと属性で 確認できます RelevanceKitのほかにも watchOS 26には スマートスタック向けに 新しいウィジェット設定が追加されました 新しいRelevantウィジェットは RelevanceKitをベースにして ウィジェットに表示される ビューを取得します 興味や時間帯など 特定のRelevantContextに合わせて おすすめのビューが表示されます Relevantウィジェットは 自動的にスマートスタックに表示され 複数のおすすめのビューが 同時に表示されます watchOS 26では Relevantウィジェットを使った 便利な機能として カレンダーの予定を複数表示する ウィジェットを表示して 編集中のメモを素早く立ち上げる 休暇先の天気予報を表示する といったことができます Beach Eventsウィジェットには その日その場所で予定された アクティビティが表示されますが Relevantウィジェットにすることで 機能を強化できます Beach Eventsウィジェットの シンプルなカレンダーです 一部の予定が重なっています 既存のTimelineウィジェットを 構築するには エントリの単一のタイムラインが必要ですが 複数の予定が重なっています タイムラインのエントリには 複数の予定が含まれており 時間が重なっていることがわかります カレンダーとタイムラインを見比べると 9時30分時点では問題ありません 資金集めパーティーと メディテーションが 1つのビューに2列で表示されています 10時には予定が3つあります 3つの予定をすべて入れようとすると ビューが切り詰められます 表示するイベントを 2つにする必要があります Relevantウィジェットで この問題を解決できます 10時頃のように 複数の予定が関連付けられると スマートスタックには 複数のカードが表示されます 予定ごとに1枚です ビーチでの予定のRelevantウィジェットを 作成する方法を説明します まずウィジェットの構造を簡単にまとめます 基本的な構成要素はエントリです ウィジェットのビューのレンダリングに 必要なデータがすべて含まれます エントリプロバイダがエントリを作成し ウィジェットの表示を 更新するタイミングを WidgetKitに通知します 設定によりエントリプロバイダが作成され エントリなどの情報で SwiftUI viewが生成されます タイムラインベースのウィジェットを 作成する場合は TimelineEntryと AppIntentTimelineProviderと AppIntentConfigurationです relevantウィジェットの作成方法は タイムラインウィジェットと同様です Relevantウィジェットの 土台となるのがRelevanceEntryです
RelevanceEntriesProviderが RelevanceEntryを作成して RelevanceConfigurationに 提供します RelevanceEntryからスタートして relevantウィジェット構築の手順を 順を追って紹介します ビーチアプリの場合は RelevanceEntryを作成します ビーチイベントがここに格納され イベントの場所 タイトル 日付など ウィジェットビュー構成に必要な 情報を網羅します 次に RelevanceEntriesProviderを 実装します relevancesメソッドは ウィジェットと 関連事象の発生をシステムに通知します ここでWidgetRelevanceAttributesの 配列を作成して ビーチでのイベントに関する WidgetConfigurationIntentsを 情報が重要になるタイミングを示す キューと合わせて RelevantContextに関連付けます こちらは ビーチで発生した イベントの日付です これらの属性情報込みで relevanceの値を返します 関連事象が発生すると システムはエントリを呼び出し 関連する WidgetConfigurationIntentと ウィジェットの表示サイズや プレビュー表示の有無などの コンテキスト情報をアプリに提供します プレビューは 設定アプリまたは スマートスタック編集時に表示される ウィジェットの提案での設定に従って 表示されます 設定ビュー構成用のデータ込みで プレビューエントリを返します プラヤリンダでのサーフィンは プレビュー表示の良い例ですね その他 お気に入りのビーチイベントなど 構成からの情報を使って ウィジェットビューの構成に必要な エントリを作成します ウィジェットはロードされたのに データが表示されない場合 システムはプロバイダに プレースホルダエントリを要求します 例えば ウィジェット表示用に 新しいデータのダウンロードを要する場合 エントリメソッドによっては リターンが遅くなったりします このとき 情報が古くなっていたり 読み込み中であることを示すため プレースホルダエントリを返します ウィジェットは 読み込みインジケータを ビューに表示させるための エントリを返します 最後に ウィジェットの作成です ウィジェットのbodyでプロバイダを使って RelevanceConfigurationを返し 現在のRelevanceEntryを使って ウィジェットのビューを作成するための クロージャを作成します 複数のイベント予定がある場合は ビーチイベントの候補をいくつか スマートスタックが提案してくれます ビーチでのイベントがタイムライン込みで ウィジェットに表示され 誰でも スマートスタックに追加可能になります 誰かが私のタイムラインウィジェットを 自分のスマートスタックに入れた場合 同じイベントのカードが2枚表示されます 1枚は 追加された タイムラインウィジェットからのカード もう1枚は Relevantウィジェットからの カードです こうした重複を防ぐには RelevanceConfigurationを タイムラインウィジェットの WidgetConfigurationに関連付けます 候補が提示されると システムは タイムラインウィジェットを 関連するウィジェットカードに 置き換えます これで イベント1つにつき1枚だけ カードが表示されるようになります ウィジェットを関連付けするには associatedKindモディファイアを RelevanceConfigurationに追加して タイムラインウィジェットのkindを渡します これでRelevantウィジェットができました ウィジェットの見栄えを良くするヒントを もう1つ紹介しますプレビューを使用しましょう 関連条件のシミュレーションなしでも プレビューなら スマートスタックでの Relevantウィジェットの見た目を 確認できます ユースケースに合わせた ウィジェットのプレビュー方法を3つほど 手短に紹介します ウィジェットのビューを開発している間 relevanceEntriesを使ったプレビューで 様々なディスプレイの 様々な表示サイズで ウィジェットの見た目を 手早くチェックして微調整できます 文字数が異なるビューの レイアウト例として 2つのイベントを使ってみます エントリメソッドの開発時には WidgetConfigurationIntentsを 使えば エントリ作成をプレビューで 手早く確認できます サンプル用の構成でrelevanceを作成して エントリとビューが正しく作成できたか 検証してみます
最終チェックは RelevanceProviderを 使ったプレビューで行います プレビューイベントストアには イベントが5つ入っています 表示サイズを変えても どのイベントもきれいに表示されるか プレビューで確認できます 重要な情報を 必要なタイミングで 表示できたら最高ですね 最新の情報であるかも重要です ウィジェットで最新のデータを提供する 新しいツールが登場します watchOS 26以降では APNSを使った プッシュ通知による更新が可能になります WidgetKit対応の すべてのAppleプラットフォームで すべてのウィジェットが プッシュ通知による更新に対応します ウィジェットのプッシュ更新対応の 詳細については Tannerが「What’s new in widgets」で紹介します コンプリケーションのプッシュ更新が 必要という理由で ウィジェットへの ClockKitコンプリケーション移行が まだの場合は 今がチャンスです 「Go further with Complications in WidgetKit」では Augustが 移行をスムーズに行うための ヒントを紹介します watchOSの新機能を どうぞ楽しんでください システムとしっくり馴染むルックに仕上げ ARM64アーキテクチャでも完璧に 動作するよう アプリをwatchOS 26で ビルドし実行することをお勧めします 独立したアプリを提供する場合は iOSコントロールのテストを Apple Watchで実行して Watchアプリに コントロールを追加するのが有効です アプリの要所要所で Relevantウィジェットをビルドして プッシュ通知を活用して ウィジェットを 最新の状態に保ちましょう Apple Watchに皆さんのアプリを入れて 散歩や旅行やビーチなど 色々な場所で使うのが 毎日の楽しみです これからも探索を続け アプリとともに 新しい世界への旅を続けましょう
-
-
6:53 - Make a widget configurable
// In the AppIntentTimelineProvider func recommendations() -> [AppIntentRecommendation<BeachConfigurationIntent>] { return [] }
-
7:06 - Support earlier versions of watchOS with a configurable widget
// In the AppIntentTimelineProvider func recommendations() -> [AppIntentRecommendation<BeachConfigurationIntent>] { if #available(watchOS 26, *) { // Return an empty array to allow configuration of the widget in watchOS 12+ return [] } else { // Return array of recommendations for preconfigured widgets before watchOS 12 return recommendedBeaches } }
-
7:46 - Use AppIntentControlConfiguration to make a control configurable
struct ConfigurableMeditationControl: ControlWidget { var body: some ControlWidgetConfiguration { AppIntentControlConfiguration( kind: WidgetKinds.configurableMeditationControl, provider: Provider() ) { value in // Provide the control's content } .displayName("Ocean Meditation") .description("Meditation with optional ocean sounds.") .promptsForUserConfiguration() } }
-
7:56 - Use AppIntentControlValueProvider for a configurable control
extension ConfigurableMeditationControl { struct Provider: AppIntentControlValueProvider { func previewValue(configuration: TimerConfiguration) -> Value { // Return the value to show in the add sheet } func currentValue(configuration: TimerConfiguration) async throws -> Value { // Return the control's value } } }
-
10:53 - Relevance for a point-of-interest category
func relevance() async -> WidgetRelevance<Void> { guard let context = RelevantContext.location(category: .beach) else { return WidgetRelevance<Void>([]) } return WidgetRelevance([WidgetRelevanceAttribute(context: context)]) }
-
14:37 - Implement the relevance method in the RelevanceEntriesProvider
struct BeachEventRelevanceProvider: RelevanceEntriesProvider { let store: BeachEventStore func relevance() async -> WidgetRelevance<BeachEventConfigurationIntent> { // Associate configuration intents with RelevantContexts let attributes = events.map { event in WidgetRelevanceAttribute( configuration: BeachEventConfigurationIntent(event: event), context: .date(interval: event.date, kind: .default) ) } return WidgetRelevance(attributes) } }
-
15:09 - Create a RelevanceEntry when the widget is relevant
struct BeachEventRelevanceProvider: RelevanceEntriesProvider { func relevance() async -> WidgetRelevance<BeachEventConfigurationIntent> { // Return relevance information for the widget } func entry( configuration: BeachEventConfigurationIntent, context: Context ) async throws -> BeachEventRelevanceEntry { if context.isPreview { return .previewEntry } return BeachEventRelevanceEntry( event: configuration.event ) } }
-
15:55 - Create a placeholder entry to display when the widget is loading
struct BeachEventRelevanceProvider: RelevanceEntriesProvider { func relevance() async -> WidgetRelevance<BeachEventConfigurationIntent> { // Return relevance information for the widget } func entry( configuration: BeachEventConfigurationIntent, context: Context ) async throws -> BeachEventRelevanceEntry { // Return the entry for the configuration } func placeholder(context: Context) -> BeachEventRelevanceEntry { BeachEventRelevanceEntry.placeholderEntry } }
-
16:27 - Use a RelevanceConfiguration to create a relevant widget
struct BeachEventWidget: Widget { private let model = BeachEventStore.shared var body: some WidgetConfiguration { RelevanceConfiguration kind: "BeachWidget provider: BeachEventRelevanceProvider(store: model) ) { entry in BeachWidgetView(entry: entry) } .configurationDisplayName("Beach Events") .description("Events at the beach") } }
-
17:31 - Use associatedKind to relate the relevant widget to the timeline widget
struct BeachEventWidget: Widget { private let model = BeachEventStore.shared var body: some WidgetConfiguration { RelevanceConfiguration kind: "BeachWidget provider: BeachEventRelevanceProvider(store: model) ) { entry in BeachWidgetView(entry: entry) } .configurationDisplayName("Beach Events") .description("Events at the beach") .associatedKind(WidgetKinds.beachEventsTimeline) } }
-
18:06 - Create a Preview with relevanceEntries
#Preview("Entries") { BeachEventWidget() } relevanceEntries: { BeachEventRelevanceEntry.previewShorebirds BeachEventRelevanceEntry.previewMeditation }
-
18:26 - Create a Preview with relevance
#Preview("Provider and Relevance") { BeachEventWidget() } relevanceProvider: { BeachEventRelevanceProvider(store: .preview) } relevance: { let configurations: [BeachEventConfigurationIntent] = [ .previewSurfing, .previewMeditation, .previewWalk ] let attributes = configurations.map { WidgetRelevanceAttribute( configuration: $0, context: .date($0.event.startDate, kind: .default) ) } return WidgetRelevance(attributes) }
-
18:47 - Create a Preview with a relevanceProvider
#Preview("Provider") { BeachEventWidget() } relevanceProvider: { BeachEventRelevanceProvider(store: .preview) }
-