← すべての記事

iOS 26のウィジェット面——ひとつのApp Intentが、いくつもの場所で生きる

長らくウィジェットはスナップショットでした。システムがタイムライン上にビューを描画し、ユーザーがそれを眺め、タップすればアプリが開く——ただそれだけです。そのモデルはもう過去のものです。iOS 17以降、ウィジェットは何も起動せずにコードを実行するボタンやトグルを備えられるようになり、そのコードの正体がApp Intentなのです1。同じことがコントロールセンターのコントロールにも、Live Activity内のボタンにも当てはまります。これに気づいた瞬間、iOS 26のウィジェット面は3つの別々のAPIには見えなくなり、ひとつのものとして立ち現れます。すなわち、システムがApp Intentを描画してくれる場所の集まりであり、それぞれが異なる表示領域を持ちながら、下回りの仕組みは共通しているのです。

この捉え直しこそが、この記事の核心です。App Intentを一度覚えれば、ホーム画面、ロック画面、コントロールセンター、アクションボタン、Dynamic Island、そして(同じintentがそのまま)SiriやApple Intelligenceまで一気に点灯します。逆に、つながりのない3つの機能を別々に書けば、4倍の手間をかけて、より劣った結果に終わるだけです。

要点

  • インタラクティブウィジェット(iOS 17以降): ウィジェットのビューにButton(intent:)Toggle(isOn:intent:)を置けば、システムがApp Intentのperform()をウィジェット拡張内で——アプリの外で——実行し、その後タイムラインを再読み込みします1
  • コントロールセンターのコントロール(iOS 18以降): ControlWidgetButtonControlWidgetToggleから組み立てたControlWidgetが、同じApp Intentを土台として、コントロールセンター、ロック画面、アクションボタンに姿を現します2
  • Live Activity(ActivityKit): ActivityAttributesActivityConfigurationがロック画面とDynamic Islandを駆動します。サーバー側からの更新はプッシュで届き、その中のボタンもまたApp Intentです3
  • iOS 26が足したのは描画であって、モデルではありません: ウィジェットはLiquid Glassの見せ方とアクセント付き描画モードを取り入れますが、「表示面としてのApp Intents」というアーキテクチャ自体はiOS 18から変わっていません4
  • すべてを束ねる事実——ウィジェットのボタン、コントロール、Siriのフレーズ、Apple Intelligenceのアクションは、同じApp Intentであり得ます。能力は一度だけ設計すればよいのです。

インタラクティブウィジェット——アプリを開かないボタン

仕組みは小さく、正確に述べる価値があります。ウィジェットのビューにSwiftUIのButtonまたはToggleを追加しますが、アクションのクロージャの代わりにApp Intentを渡します1

struct WaterWidgetView: View {
    let logged: Int

    var body: some View {
        VStack {
            Text("\(logged) glasses")
            Button(intent: LogWaterIntent()) {
                Label("Add", systemImage: "plus")
            }
        }
    }
}

ユーザーがタップすると、システムはウィジェット拡張内でLogWaterIntent.perform()を実行します。アプリは起動しません。intentは自分の仕事をこなし(共有ストアへの書き込み、カウントの更新など)、ウィジェットのタイムラインが再読み込みされて結果が表示されます。

Toggleも同じようにSetValueIntentを通して動きます。これはユーザーが今まさに設定した新しいオン/オフの値を受け取ります。

struct ToggleReminderIntent: SetValueIntent {
    static let title: LocalizedStringResource = "Toggle Reminder"

    @Parameter(title: "Enabled")
    var value: Bool

    func perform() async throws -> some IntentResult {
        ReminderStore.shared.enabled = value
        return .result()
    }
}

ウィジェットはToggle(isOn: store.enabled, intent: ToggleReminderIntent())をバインドし、システムがvalueを新しい状態に設定してperform()を実行し、再読み込みします。ボタンと同じ形に、パラメータがひとつ増えるだけです1

ここでできることを定めるのは2つの制約であり、どちらも破ってしまいがちです。第一に、perform()は実行予算の厳しいウィジェット拡張環境で動きます。素早い状態変更のためのものであって、ネットワークアップロードや重い計算のためのものではありません。第二に、ウィジェットは状態を映すものであって、任意のUIをアニメーションさせるものではありません。データを変えればタイムラインが再描画される——カスタムのトランジションを走らせるわけではないのです。やりとりを「値を切り替え、新しい値を見せる」として設計すれば、うまくいきます。それ以上を求めれば、システムが抵抗してきます。

コントロールセンターのコントロール——同じintent、別の部屋

iOS 18はコントロールセンター、ロック画面、アクションボタンをサードパーティのコントロールに開放しました。そのAPIは意図的にウィジェットと並行する作りになっています2。コントロールはControlWidgetであり、その本体はApp Intentに結線されたControlWidgetButtonまたはControlWidgetToggleです。

struct LogWaterControl: ControlWidget {
    var body: some ControlWidgetConfiguration {
        StaticControlConfiguration(kind: "com.example.logWater") {
            ControlWidgetButton(action: LogWaterIntent()) {
                Label("Log Water", systemImage: "drop.fill")
            }
        }
    }
}

LogWaterIntentがすでにインタラクティブウィジェットを動かしているなら、コントロールはほぼタダで手に入ります。同じintentに、新しいラッパーをかぶせるだけです。これこそアーキテクチャの見返りです。コントロールは機能の2つ目の実装ではなく、すでに書いた機能の2つ目の取り付け口なのです。トグル型のコントロールはウィジェットのトグルと寸分違わずSetValueIntentを使うので、一度作った「ミュート」「セッション開始」「ダークモード切り替え」といった能力が、新しいロジックなしにコントロールセンター、ロック画面、アクションボタンに現れます2

Live Activity——自ら更新していく表示面

Live Activityは3つ目の場所であり、可動部が最も多い場所でもあります。ActivityAttributes型が静的・動的なコンテンツを定義し、ActivityConfigurationがロック画面の見せ方とDynamic Islandの各領域を描画し、ActivityKitがアクティビティを開始・更新・終了します3。Live Activity内のボタンも、やはりApp Intentです。ですからDynamic Islandの「一時停止」や「次へ」のコントロールは、ウィジェットのボタンと同じ種類のintentを実行します。

Live Activityを独自の作法たらしめているのが、更新の経路です。ローカルで更新するタイマーなら単純です。しかしサーバー上で起きる出来事(配達の移動、試合のスコア変化)に駆動されるアクティビティは、プッシュ経由で更新されます。pushTokenUpdatesに登録し、バックエンドからActivityKitのプッシュペイロードを送れば、アプリがまったく動いていなくてもシステムがロック画面とDynamic Islandを更新します3。これは本当に強力であると同時に、本当に誤りやすいものでもあります。アクティビティの正しさが、ローカルのコードだけでなく、サーバー側の契約、プッシュの信頼性、古さ(stale-date)のポリシーに依存することになるからです。

iOS 26が実際に変えたもの

iOS 26のウィジェットの目玉は、アーキテクチャではなく見せ方です。ウィジェットはLiquid Glassの素材を取り入れ、アクセント付きの描画モードを得ます。これによりシステムの新しいデザイン言語に調和し、OSがコンテンツを色付け直す文脈で正しく着色されます4。これはホーム画面、ロック画面、スタンバイ表示にまたがるウィジェットの見え方にとって重要であり、デザインの見直しに値します。とはいえ、インタラクティブ性の仕組みが変わるわけではありません。iOS 18でインタラクティブなウィジェットやコントロールを作っていたなら、iOS 26の対応は視覚的な刷新であって、書き直しではありません。iOS 26がインタラクティブウィジェットを「導入した」という主張は疑ってかかるべきです。インタラクティブ性はiOS 17で、コントロールはiOS 18で出荷されており、iOS 26はそれらをシステムの他の部分と同じ見た目に整えただけなのです。

ただし、Liquid Glassがもたらすデザイン上の含意は本物です。ウィジェットはシステムが壁紙の上に合成し、ガラスのクロームを通して屈折させるコンテンツです。したがってアプリにおけるLiquid Glassを律するのと同じルールが当てはまります。機能の層とコンテンツの層の区別を尊重し、ガラスが読み取れないような重たいカスタム背景で素材と争わないことです。

アーキテクチャを、率直に言えば

胸に留めておく価値のあるモデルはこうです。App Intentとは、名前を持ち、型付けされ、説明された能力です。iOSはその能力を取り付ける場所を、次々と増やして与えてくれます。

  • ウィジェット内のButtonToggle
  • コントロールセンターロック画面アクションボタンに置くControlWidget
  • Live ActivityとそのDynamic Island内のボタン。
  • Siriのフレーズと、Shortcutsのアクション。
  • Apple Intelligenceがユーザーに代わって呼び出せるアクション5

これらはどれも、perform()メソッドを持つ同じAppIntent型です。仕事はその能力をよく設計することにあります。明快な名前、型付けされたパラメータ、速くて冪等なperform()、そして筋の通った結果——これを一度こなせば、表示面はラッパーにすぎません。これは私がApp Intentsをアプリへの本当のAPIとして論じ、iOSアプリがいま見せる3つの面について述べてきたのと同じ主張です。ウィジェット面はあなたが作る機能ではありません。能力がいったん存在すれば、それが姿を現す場所なのです。

手を出さないほうがよいとき

ウィジェット面は、一目で分かる本物の状態と素早いアクションを持つアプリに報いてくれます。トラッカー、タイマー、トグル、再生中のコントロールなどです。すべてに報いるわけではありません。

  • 一目で見せる状態がないなら、ウィジェットも不要です。 開かなければ見せる価値のあるものが何もないアプリにとって、ウィジェットは保守の手間と余計な拡張ターゲットを払わせるだけの装飾です。
  • 重い・遅いアクションは、ここのperform()には属しません。 ウィジェットとコントロールの実行環境は制約されています。アクションに本物の時間や本物の計算が必要なら、ボタンは拡張内で仕事をするふりをするのではなく、準備済みの状態でアプリを開くべきです。
  • 「ライブ」でないものにLive Activityを使うこと。 Live Activityは時間に区切られ、活発に変化していくイベントのためのものです。これを常駐の状態バッジとして使うのは表示面の読み違いであり、アクティビティ予算をめぐってシステムの「お叱りリスト」に載る近道です。

身につけるべき技術は、3つのAPIを覚えることではありません。取り付けるに値するApp Intentを設計し、それをユーザーがすでにいる場所に取り付けること——彼らが確認するホーム画面、スワイプして開くコントロールセンター、すでに何かを見せているDynamic Island——です。能力を一度、美意識をもって作れば、iOSが表示面をタダで手渡してくれます。



  1. Apple Developer, “Adding interactivity to widgets and Live Activities”. インタラクティブウィジェット(iOS 17以降)は、AppIntent(トグルの場合はSetValueIntent)を土台とするSwiftUIのButton(intent:)Toggle(isOn:intent:)を使います。intentのperform()はアプリを起動せずにウィジェット拡張内で実行され、結果を反映するためにタイムラインが再読み込みされます。 

  2. Apple Developer, “Creating controls to perform actions across the system” およびControlWidgetプロトコル。コントロール(iOS 18以降)は、App Intentに結線されたControlWidgetButtonControlWidgetToggleから組み立てられ、コントロールセンター、ロック画面、アクションボタンに姿を現します。 

  3. Apple Developer, “ActivityKit” および“Displaying live data with Live Activities”. ActivityAttributesActivityConfigurationがロック画面とDynamic Islandを定義・描画し、ActivityKitがアクティビティを開始・更新・終了します。サーバー側からの更新はpushTokenUpdatesを用いたプッシュを使います。 

  4. iOS 26のウィジェット描画——ウィジェットはLiquid Glassの素材とアクセント付き描画モードを取り入れます。これらはWidgetRenderingMode\.widgetRenderingMode環境値で制御されます。インタラクティブ性のモデル(ウィジェットとコントロールにおけるApp Intents)は、iOS 17(ウィジェット)とiOS 18(コントロール)から変わっていません。Apple, “WWDC 2025: the new software design” およびWidgetKitのドキュメント。 

  5. Apple Developer, “App Intents”. 同じAppIntent型こそ、システムがSiri、Shortcuts、Spotlight、ウィジェットとコントロールの表示面、そしてApple Intelligenceを通して公開する単位です。クロスサーフェスのモデルに関する筆者の分析:App Intents Are Apple’s New API to Your AppApp Intents 2とiOS 26での追加、そしてiOSアプリの3つの面。 

関連記事

Live Activitiesはバッジではなくステートマシンである

ReturnのLive Activityはロック画面のカウントダウンに見えますが、実態は3つの解除パスを持つ5状態のライフサイクルマシンです。本番コードとバグの記録。

4 分で読める

Apple's Translation Framework: Free, On-Device, and Sharper Than It Looks

Apple's Translation framework: free on-device translation via translationPresentation and TranslationSession, plus the o…

8 分で読める

メンテナーが攻撃者になるとき:jqwik 1.10.0

jqwik 1.10.0 は、Maven 出力に破壊的なプロンプトインジェクション文字列を出します。ANSI エスケープで人間からは隠されます。メンテナーは意図的に追加しました。

3 分で読める