ハイライト
watchOS 26 により、Controls、設定可能な widget、RelevanceKit の 3 つが Apple Watch に登場し、Watch app を開発しなくても iPhone 上の機能を手首からトリガーできるようになりました。
主要内容
watchOS には以前からある課題がありました。ユーザーの手首には Smart Stack がありますが、開発者がそこに提示できる内容には限界がありました。Timeline widget は一度に 1 つのタイムライン上のコンテンツしか表示できず、「9 時の瞑想と 10 時の 3 つのビーチアクティビティ」のようなシナリオでは、3 つのイベントが 1 枚のカードに押し込まれて切り捨てられてしまいます。iOS 16 から存在する Controls というショートカット操作機能は、Apple Watch ではずっと利用できませんでした。その結果、開発者は独立した Watch app を開発するコストを払うか、手首というプレースメントを諦めるかの選択を迫られていました。
watchOS 26 はこの課題に 3 つのレイヤーで応えます。第 1 のレイヤーは、iOS 上の Controls をそのまま Apple Watch に持ってくることです。ユーザーは iPhone app の control を Watch の Control Center、Smart Stack、または Action Button に配置でき、タップするだけでアクションが iPhone 上で実行されます。Watch 側のコードを追加で書く必要はありません(講演者の Anne が 04:44 で明言しています)。第 2 のレイヤーは、widget と control の両方が Watch 上で設定可能になり、iOS と同じ AppIntent フローを使用するようになったことです。第 3 のレイヤーは、新しいフレームワークである RelevanceKit と Relevant Widget です。「ビーチのような POI に存在するとき」「あるイベントの時刻付近」のように widget が関連する条件を宣言すると、システムが Smart Stack に複数のカードを同時に提案し、各カードが各イベントに対応するようになります。これにより Timeline widget の単一タイムライン制限を完全に回避できます。
さらに、下層にはもう一つ重要な変更があります。Apple Watch Series 9 と Ultra 2 が watchOS 26 で正式に arm64 に移行しました。Xcode 14 からすでにサポートされており、シミュレーターも常に arm64 だったため、ほとんどの app は Standard Architectures を使うだけで直接ビルドできます。注意が必要なのは、Float/Int の変換やポインタ演算のような低レベルコードだけです。
詳細
古い Timeline widget を watchOS 26 で設定可能にする(06:53)
// 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
}
}
ポイント:
recommendations()はAppIntentTimelineProviderのメソッドです。watchOS 26 以前は事前設定項目の配列を返す必要があり、ユーザーはパラメータを変更できませんでした。- watchOS 26 以降、空の配列
[]を返すことで、システムに「この widget はユーザーが文字盤や Smart Stack で自分で設定するもの」と伝えます。その後、ユーザーが widget を編集すると AppIntent 設定ページが表示されます。 if #available(watchOS 26, *)分岐を追加することで、同じコードで旧システムと互換性を保ち、古いユーザーが設定できない widget を見ないようにできます。
Control も設定に対応させる(07:46)
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()
}
}
ポイント:
AppIntentControlConfigurationは iOS 上と完全に同じ名前で、Watch 側で API を変える必要はありません。providerはAppIntentControlValueProviderに準拠する必要があり、previewValue(configuration:)とcurrentValue(configuration:)を提供します。前者は「control を追加」パネル上のプレビュー用、後者は実際の実行時の値取得用です。.promptsForUserConfiguration()を使うと、ユーザーが初めてこの control を追加するときに設定 UI が表示されます。例えば「瞑想時に波の音を再生するかどうか」をユーザーに選択させることができます。
RelevanceKit で widget に POI カテゴリをタグ付けする(10:53)
func relevance() async -> WidgetRelevance<Void> {
guard let context = RelevantContext.location(category: .beach) else {
return WidgetRelevance<Void>([])
}
return WidgetRelevance([WidgetRelevanceAttribute(context: context)])
}
ポイント:
RelevantContext.location(category:)は MapKit の POI カテゴリを受け取ります。サポートされていないカテゴリを渡すと nil が返されるので、先にguardでチェックする必要があります。- 返された
WidgetRelevanceにWidgetRelevanceAttribute(context:)を含めることで、システムに「ユーザーがこの種類の場所にいるとき、この widget を Smart Stack の一番上に推してください」と伝えます。 - カテゴリには grocery store、cafe、beach などが含まれており、自分で GeoFence を書く必要はありません。
Relevant Widget:Smart Stack に複数のカードを一度に提供する(14:37)
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)
}
}
ポイント:
RelevanceEntriesProviderはAppIntentTimelineProviderに代わる新しいプロトコルで、Relevant Widget 専用です。relevance()は各BeachEventConfigurationIntent設定と、それに対応するRelevantContext(ここではイベント発生の時間区间)をWidgetRelevanceAttributeとしてパッケージ化します。- 同時に複数の attribute がヒットすると、システムは Smart Stack に同時に複数のカードを提案します。イベントごとに 1 枚のカードになり、Timeline widget の単一カード切り捨て問題を回避します(12:48 の 3 イベントが 1 枚のカードに押し込まれる比較デモを参照)。
RelevanceConfiguration で widget を組み立て、重複表示を回避する(17:31)
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)
}
}
ポイント:
RelevanceConfigurationは新しい widget 設定タイプで、AppIntentConfigurationと同等の位置づけですが、RelevanceEntriesProviderによって駆動される点が異なります。- クロージャ内で
entryを取得した後、SwiftUI ビューをレンダリングします。Timeline widget の書き方と同じです。 .associatedKind(WidgetKinds.beachEventsTimeline)が重要です。ユーザーがすでに同名の Timeline widget を Smart Stack に追加している場合、relevant widget が提案されたときにシステムはrelevant カードで timeline カードを置換し、1 つのイベントに対して 2 枚のカードが表示されるのを防ぎます。
重要ポイント
1. iPhone Control を優先的に Apple Watch に公開する: watchOS 26 以降、iPhone app の control はデフォルトでユーザーが Watch の Control Center、Smart Stack、Action Button に追加でき、Watch app なしでリモートトリガーできます。なぜやる価値があるか:投資はほぼゼロ、カバー範囲は広い——control の action が iPhone app を強制的にフォアグラウンドにしない限り(05:04)、自動的に表示されます。どう始めるか:既存の iOS controls を監査し、フォアグラウンド app のものを分離するか、バックグラウンド AppIntent に書き直します。
2. widget を watchOS 26 で設定可能にする: if #available(watchOS 26, *) 分岐を使い、recommendations() が空の配列を返すようにすると、ユーザーによるパラメータカスタマイズが可能になります。なぜやる価値があるか:Smart Stack の利用可能スロット数は限られているため、設定可能な widget により、ユーザーは 1 つのスロットを確保しながら複数のコンテンツ(例えば異なる場所の天気)をカバーできます。どう始めるか:既存の AppIntentTimelineProvider を見つけ、recommendations() に #available 分岐を追加するだけです。
3. RelevantContext で精准な提案をする: widget の関連性を POI カテゴリ、時間区间、フィットネス/睡眠状態に紐付け、適切なタイミングで Smart Stack のトップにプッシュします。なぜやる価値があるか:ユーザーは widget の順序を能動的に調整する必要がなく、関連するコンテンツが自動的に浮かび上がるため、リテンションとアクティブ度が向上します。どう始めるか:relevance() メソッドで WidgetRelevanceAttribute の配列を返し、まずは RelevantContext.location(category:) から試してみます。
4. 複数イベントシナリオを Relevant Widget に改造する: カレンダー、注文、行程のような「同時点で競合する可能性がある」コンテンツは、RelevanceConfiguration + RelevanceEntriesProvider で Timeline widget に代えます。なぜやる価値があるか:Smart Stack は複数のカードを同時に提案し、イベントごとに 1 枚のカードになり、単一カードの切り捨てを回避します。どう始めるか:まず RelevanceEntriesProvider を書き、relevance() で各イベントに WidgetRelevanceAttribute を割り当て、.associatedKind(_) で古い Timeline widget に関連付けて重複を防ぎます。
5. ClockKit complication の最後の移行ウィンドウ: watchOS 26 の widget push updates は APNs 経由でプッシュされます。「complication だけが push 可能で widget は不可」という理由で ClockKit にとどまっていた app は、安心して移行できます(19:14 と「Go further with Complications in WidgetKit」を参照)。なぜやる価値があるか:ClockKit は永遠に存在するわけではなく、早めに移行するほど楽になります。どう始めるか:WidgetKit で複雑な機能ページを書き直し、APNs widget push チャネルと接続します。
関連セッション
- Meet Liquid Glass — watchOS 26 全 app の新しいデザイン言語。toolbar と control スタイルの自動移行。
- What’s new in widgets — widget push updates、Live Activities などクロスプラットフォーム widget 更新の総合エントリーポイント。
- Go further with MapKit — watchOS 26 上の MapKit 経路とローカル検索の機能はここから提供されます。
- Create icons with Icon Composer — Icon Composer を使って iOS/watchOS 両方の新しいアイコンを一度に生成。
コメント
GitHub Issues · utterances