View in English

  • メニューを開く メニューを閉じる
  • Apple Developer
検索
検索を終了
  • Apple Developer
  • ニュース
  • 見つける
  • デザイン
  • 開発
  • 配信
  • サポート
  • アカウント
次の内容に検索結果を絞り込む

クイックリンク

5 クイックリンク

ビデオ

メニューを開く メニューを閉じる
  • コレクション
  • トピック
  • すべてのビデオ
  • 利用方法

その他のビデオ

ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。

  • 概要
  • トランスクリプト
  • コード
  • システム全体にアプリのコントロールを拡張

    アプリのコントロールをコントロールセンターやロック画面などに配置する方法を確認しましょう。WidgetKitを使用して、アプリのコントロールをシステムの利用体験に拡張する方法を解説します。コントロールを作成し、デザインをカスタマイズしたり、構成を変更したりする方法も取り上げます。

    関連する章

    • 0:00 - Introduction
    • 0:37 - Learn about controls
    • 3:04 - Build a control
    • 6:39 - Update toggle states
    • 12:25 - Make controls configurable
    • 14:40 - Add refinements

    リソース

    • Adding refinements and configuration to controls
    • Creating a camera experience for the Lock Screen
    • Creating controls to perform actions across the system
    • Forum: App & System Services
    • Human Interface Guidelines: Controls
    • Updating controls locally and remotely
      • HDビデオ
      • SDビデオ

    関連ビデオ

    WWDC24

    • ロック画面での優れたカメラキャプチャ体験の構築
  • ダウンロード

    こんにちは Cliffです System Experienceチームで エンジニアをしています 今日は iOS 18の 新しいウィジェットタイプである コントロールの作成方法について お話しします まず コントロールの概要と ユーザーがどのように使用するかを説明します 次に コントロールの作成方法を紹介し アクションを実行して その状態を維持する方法 構成をサポートする方法 システムとの統合を改善する方法を 説明します

    コントロールは アプリの機能を システムスペースに拡張する新しい方法です システムスペースには コントロールセンター ロック画面 アクションボタンなどがあります これらのコントロールは WidgetKitを用いて作成されます iOS 14でWidgetKitが導入され 視覚に訴える カスタムスタイルのコンテンツを 詳細情報とともに アプリに表示できるようになりました ウィジェットを利用すれば 天気や次のカレンダーイベントを 効果的に表示できます iOS 18ではコントロールが追加され WidgetKitをさらに拡張しています コントロールはアプリからのアクションに 迅速にアクセスできる優れた方法です これらのコントロールはアクションと 簡潔な情報にフォーカスしており フラッシュライトをオンにしたり 時計アプリにディープリンクするなどの ユースケースに適しています ウィジェットの作成方法を知っていれば コントロールも同じように作成できます 基盤となるアーキテクチャは 同じものだからです コントロールには2種類あります ボタンとトグルです ボタンは アプリの起動など 個別のアクションを実行します トグルはブール値の状態を変化させます 何かのオンとオフを切り替える場合などです

    インタラクティブなウィジェットのように コントロールはApp Intentを使って アクションを実行します

    基本的に コントロールは システムスペースに 目にみえる形で現れるアクションで アプリから提供される情報が使用されます

    アプリは シンボル タイトル 色合い 追加のコンテンツを システムに提供します サポートされているシステムスペースに ユーザーがコントロールを追加すると そのコントロールが状況に応じて システムスペースに表示されます

    コントロールセンターでは コントロールが 3つの異なるサイズで表示されるため タイトルや値のテキストが 常に表示されるとは限りません 私は仕事中に 集中力を維持しながら 休憩を取りたい時に よくタイマーを使います 今日は 作業と休憩を区切って実行できる 生産性タイマーコントロールを作成します タイマーが作動中 ライブアクティビティで 残り時間が表示されます

    ロック画面からタイマーを開始し

    コントロールセンターで停止し

    さらにアクションボタンを使って 開始または停止することができます

    生産性を高めるこのタイマーコントロールを ゼロから構築してみます まずは基本的なトグルから始めて その後 コントロールの様々な機能を 活用していきます

    既存の 生産性ウィジェット用の WidgetBundleが既にあるので まず このWidgetBundleに TimerToggle()エントリを追加します 次に この同じWidget Extensionで TimerToggle()コントロールを定義します ControlWidgetに準拠した TimerToggleタイプを追加します コントロールを定義するため コントロールに表示する情報と 実行するアクションを提供します

    まずは StaticControlConfiguration から始めます このコントロールは構成可能ではありません 構成は後で追加します

    このコントロールは 一意の識別子としてkindを取り コントロールのタイプの定義として ControlWidgetToggleを取ります

    次に コントロールにタイトルを与えて その状態を提供します

    コントロールが操作されたときに 実行するアクションも記述します インタラクティブなウィジェットと同様 このコントロールもApp Intentを 使用してアクションを実行します

    最後に コントロールを定義する シンボル画像を指定します

    必要な情報がすべて揃ったので このコントロールがシステムに 表示されるようになります

    コントロールセンターにも配置して タイトル シンボル オン/オフ状態を表示できます

    このタイマーコントロールは タイマーが作動中または停止中に 異なるシンボルを表示することで さらに改善できます

    そのためには クロージャのisOn引数を使用し コントロールがオンのときは 砂時計シンボルを表示して 時間がカウントダウンされていることを示します

    素晴らしいですね! コントロールが作動中のときのみ 砂時計が表示されるようになりました

    また この状態の値のテキストも さらに改善したいと思います 現在はオンとオフが表示されています

    これはトグルのデフォルトの値のテキストです しかし通常 タイマーでは オン/オフではなく 作動中/停止を使用します

    コントロールの値のテキストは 画像をラベルに変更してカスタマイズできます ラベルには値textと systemImageの両方が含まれます これでデフォルトのオン/オフではなく コントロールの状態を表す適切で関連性のある 値のテキストが表示されるようになりました

    この値のテキストは コントロールがロック画面上にあるときや コントロールセンターの小さなサイズ内では 表示されないことに注意してください その場合は シンボルのみが表示されます

    このコントロールの方向性は 気に入っていますが カラーについては オン状態ではシンボルが デフォルトのsystemBlueの色合いなので 私の生産性アプリの ブランドイメージに合いません

    デフォルトのsystemBlueではなく 特徴的な色をコントロールで使用するには 色合いを指定します

    生産性アプリの色合いである パープルを使用します パープルは生産性を高めます 色合いはトグルがオンのときに シンボルに使用されます

    こちらは ロック画面上で動作する スタイルが適用されたコントロールと アクションボタンの例です コントロールセンターで動作させるものと 同じコードを使用しています コントロールがオンのとき シンボルと任意の値のテキストが 指定した色合いになります トグルがどのように状態を表示し 管理するかを調べてみましょう ここまで TimerManagerクラスを使って 現在のタイマーの状態を コントロールに提供してきています この例では TimerManagerは 私の 生産性アプリと 同じデータにアクセスする 共有グループコンテナでデータを確認し 実行状態を同期的に取得します コントロールの状態やコンテンツが 変更されたときに システムがコントロールをどのように リロードするのかを見ていきましょう

    システムがコントロールを リロードする必要があるとき Widget Extensionプロセス内で コントロールの本体を実行して 現在の値を取得し コントロールのコンテンツを生成します コントロールの値とコンテンツは システムに渡され コントロールの表示に使用されます つまり Widget Extensionは コントロールの現在の状態と その状態のコンテンツを提供します

    システムがコントロールをリロードする 原因となるイベントは3種類あります コントロールアクションが実行された時

    アプリがオンデマンドで コントロールのリロードを要求した時 プッシュ通知がコントロールを 無効にした時です 最初のイベントは コントロールアクションの実行時です ユーザーがコントロールを操作するたびに コントロールのApp Intentの perform()関数が返される時点で コントロールが自動的にリロードされます リターンされる前に すべての更新を完了させてください

    このタイマーコントロールでは アクションはToggleTimerIntent()です

    このインテントは タイマーの「作動中」状態を設定し ライブアクティビティを開始または停止します

    このApp Intentはタイマーの「作動中」状態を システムが提供する値に設定するため SetValueIntentになります タイマーのライブアクティビティを変更するため LiveActivityIntentでもあります perform関数が完了すると システムは新しい状態で コントロールを更新します タイマーコントロールを操作することが 状態を変更する 唯一の方法ではありません 生産性アプリを開き そこでタイマーを 開始/停止することもできます コントロールを最新の状態に 維持したいのですが

    そのためには タイマーの状態が変化したとき ControlCenter APIを使って コントロールをリフレッシュします タイマーコントロールの種類を指定して リロードするコントロールを指定するのです これで アプリでタイマーを開始すると コントロールの状態が常に 最新の状態に保たれます

    コントロールの状態やコンテンツを リフレッシュする必要がある場合は アプリからシステムに コントロールのリロードを要求できます ウィジェットやライブアクティビティに利用できる リフレッシュツールは コントロールにも利用できます コントロールを開発し その状態を頻繁にリフレッシュする際は で を有効にして システムポリシーを コントロールから削除します このデバイスでは 生産性タイマーが うまく作動しています これを複数のデバイスで作動させ すべてのデバイスが サーバ上の同じタイマー状態に アクセスできるようにしたいと思います

    このコントロールは デバイスで利用できない サーバの状態を反映するため タイマーの状態を非同期で 取得する必要があります そのためには ValueProviderを使用できます

    ControlValueProviderには 2つの要件があります currentValue()とpreviewValueです

    currentValue()は非同期であり 必要なデータを取得する際は データベースやサーバから データを取得できます この場合 TimerManagerは サーバから非同期でタイマー状態をクエリします

    状態を処理できなかった場合は それを知らせるエラーをスローし 後でコントロールをリロードする 必要があることを通知できます

    previewValueでは コントロールが追加される前 ユーザーがプレビューしたときに 表示する値を選択します 例えば コントロールギャラリー ロック画面のカスタマイズ時 アクションボタンの設定などで使用します previewValueは事前に決定しておき 極めて短時間で戻る必要があります また コントロールのオフ状態に 対応する値を使用する必要があります

    ValueProviderを取る 別のコントロールイニシャライザで このValueProviderを使用でき 提供された値がトグルを定義する クロージャに渡されます ここでは その値を コントロールのisOn状態として使用します この例では 単純なブールを値として 使用していますが 値にさらに多くの 情報を含めることもできます その例は後で紹介します システムがValueProviderを使用して コントロールをリロードするとき 最初にValueProviderを実行して 現在の値を取得し その値をコントロールクロージャに渡して コンテンツを生成します これらすべては Widget Extensionプロセスで行われます

    これで 生産性タイマーの状態が サーバに保存され 様々なデバイスから変更できます 例えば iPadから タイマーを開始または停止した場合 このデバイス外の状態変更が 他のデバイス上のコントロールの リロードをトリガするようにしたいとします その場合は Push Notification APIを使用して コントロールのpushハンドラを定義し 外部状態変更の プッシュ通知を受信したときに コントロールがリロードされるように 構成します プッシュ通知の取り扱いに関する ドキュメントには これを行う方法とベストプラクティスの 詳細が記載されています

    これで iPadでタイマーを停止すると iPhone上のコントロールも 停止するようになりました

    私の 生産性タイマーは 順調に動作していますが 仕事とプライベートで 別々のタイマーを用意したいと思います 例えば バイオリンの練習などです アプリでそれぞれを個別に 追跡できるようにしたいと思います それぞれのタイマー用に 異なるコントロールを用意し 両方をコントロールセンターに 配置できるといいですね WidgetKitを使用して ウィジェットと 同じようにコントロールを作成できるため コントロールを構成可能にすることで これを実現できます コントロールセンターに タイマーコントロールを1つ追加した後 仕事用またはプライベート用の どちらを開始/停止するかを 選択できるようにしましょう それで コントロールセンターに仕事用と プライベート用のタイマーコントロールを それぞれ配置できます まず ValueProviderを新しいプロトコルに 準拠するように更新できます AppIntentControlValueProviderです これで 値がインテントの設定に 依存するようになります 構成を決めるApp Intentは SelectTimerIntentです コントロールを操作するタイマーを ユーザーが選択できます 構成の特定のタイマーの 作動状態が確実に取得されるようにして 返す値はタイマーとその作動状態を含む カスタム構造体になります

    ConfigurableValueProviderを使用して AppIntentControlConfiguration()で コントロールを構成可能にします このクロージャに渡される値は timerState構造体です トグルを完了するために そのタイマーと作動状態を使用します 特定のタイマーの名前をコントロールの タイトルとして表示したいと思います また トグルタイマーのApp Intentは その特定のタイマーに対して動作します

    次に ユーザーがコントロールセンターを カスタマイズするとき このコントロールで どのタイマーを操作するか 選べるようにします ここでは コントロールセンターに 並べて配置した 仕事用とプライベート用の タイマーコントロールが 各タイマーをコントロールしています

    コントロールを機能させるために 構成が必要な場合 promptsForUserConfiguration() モディファイアを使用して システムがコントロールを システムスペースに追加した際に 構成を促すプロンプトを 自動的に表示するようにできます

    コントロールをさらに洗練させて システムのデフォルト値が ユースケースに合わない場合に 最も理解しやすく関連性のある コンテンツを提供できます 例えば アクションが実行される前に ユーザーがアクションボタンを操作すると アクションのヒントが表示されます 現在のアクションヒントは とです

    なぜそうなっているのかを 詳しく見てみましょう

    コントロールの値ラベルをか にカスタマイズする前は デフォルトのオン/オフの 値のテキストの生成と同じように と という アクションヒントが生成されていました 値のテキストをカスタマイズした際 それがアクションヒントの生成にも使われました システムでと が生成されました これらは確実に改善できます そこで これをユースケースに合わせて カスタマイズします

    controlWidgetActionHintモディファイアを 使用して アクションボタンのヒントテキストを カスタマイズし アクションヒントを選びます これは動詞で始める必要があります 提供されたヒントは 特定の状態に遷移するアクションです タイマーがオン状態での アクションヒントは つまり タイマーを開始するヒントは です オフ状態のアクションヒントは つまり タイマーを停止するヒントは です 素晴らしいですね タイマーとして自然に感じられます

    controlWidgetStatusモディファイアを使用して アクションが実行されたとき コントロールセンターに 一時的なステータスを表示できます コントロールのアクションに関する追加情報 その状態 または その状態の有効期間を伝える場合 ステータスの追加をお勧めします ステータステキストは慎重に使用し 注意を引くために コントロールでまだ伝えていない 関連情報にのみ使用してください

    コントロールギャラリーから コントロールを追加すると 現在の名前は これは私のアプリの名前です

    コントロールのアプリ名が コントロールのデフォルト表示名です

    コントロールの表示名を にカスタマイズします 各コントロールに必ず特定の displayNameを選択してください 機能に応じて異なります 最後の仕上げとして 説明も追加します コントロールの構成中に 表示されるものです わずか数ステップで コントロールセンター ロック画面 アクションボタンに配置でき すべてのデバイスで同期する 生産性タイマーコントロールを作成しました コントロールはパワフルな機能です iOSおよびiPadOS 18の アプリに組み込むことで システムスペースでアプリの主要アクションに 素早くアクセスすることができます ここで説明したモディファイアを使用して コントロールのスタイルを アクションに合わせて調整し コントロールに特徴的なシンボルが 備わっていることを確認してください

    カメラでコンテンツをキャプチャできる コントロールを作成する場合は キャプチャ拡張の作成に関するセッション 「Build a great Lock Screen camera capture experience」をご覧ください

    ご視聴ありがとうございました

    • 3:13 - Add the control to the Widget Bundle

      @main
      struct ProductivityExtensionBundle: WidgetBundle {
          
          var body: some Widget {
              ChecklistWidget()
              TaskCounterWidget()
              TimerToggle()
          }
          
      }
    • 3:29 - Complete the control

      struct TimerToggle: ControlWidget {
          var body: some ControlWidgetConfiguration {
              StaticControlConfiguration(
                  kind: "com.apple.Productivity.TimerToggle"
              ) {
                  ControlWidgetToggle(
                      "Work Timer",
                      isOn: TimerManager.shared.isRunning,
                      action: ToggleTimerIntent()
                  ) { _ in
                      Image(systemName:
                            "hourglass.bottomhalf.filled")
                  }
              }
          }
      }
    • 4:41 - Specify different symbols when on and off​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​

      struct TimerToggle: ControlWidget {
          var body: some ControlWidgetConfiguration {
              StaticControlConfiguration(
                  kind: "com.apple.Productivity.TimerToggle"
              ) {
                  ControlWidgetToggle(
                      "Work Timer",
                      isOn: TimerManager.shared.isRunning,
                      action: ToggleTimerIntent()
                  ) { isOn in
                      Image(systemName: isOn
                            ? "hourglass"
                            : "hourglass.bottomhalf.filled")
                  }
              }
          }
      }
    • 5:21 - Specify custom value text​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ and add a custom tint color

      struct TimerToggle: ControlWidget {
          var body: some ControlWidgetConfiguration {
              StaticControlConfiguration(
                  kind: "com.apple.Productivity.TimerToggle"
              ) {
                  ControlWidgetToggle(
                      "Work Timer",
                      isOn: TimerManager.shared.isRunning,
                      action: ToggleTimerIntent()
                  ) { isOn in
                      Label(isOn ? "Running" : "Stopped",
                            systemImage: isOn
                            ? "hourglass"
                            : "hourglass.bottomhalf.filled")
                  }
                  .tint(.purple)
              }
          }
      }
    • 8:14 - Implement timer toggling

      struct ToggleTimerIntent: SetValueIntent, LiveActivityIntent {
          static let title: LocalizedStringResource = "Productivity Timer"
          
          @Parameter(title: "Running")
          var value: Bool  // The timer’s running state
          
          func perform() throws -> some IntentResult {
              TimerManager.shared.setTimerRunning(value)
              return .result()
          }
      }
    • 8:54 - Refresh the control from within the app

      func timerManager(_ manager: TimerManager,
                        timerDidChange timer: ProductivityTimer) {
          ControlCenter.shared.reloadControls(
              ofKind: "com.apple.Productivity.TimerToggle"
          )
      }
    • 10:03 - Define a Value Provider

      struct TimerValueProvider: ControlValueProvider {
          
          func currentValue() async throws -> Bool {
              try await TimerManager.shared.fetchRunningState()
          }
          
          let previewValue: Bool = false
      }
    • 11:00 - Provide asynchronously fetched state with a Value Provider

      struct TimerToggle: ControlWidget {
          var body: some ControlWidgetConfiguration {
              StaticControlConfiguration(
                  kind: "com.apple.Productivity.TimerToggle",
                  provider: TimerValueProvider()
              ) { isRunning in
                  ControlWidgetToggle(
                      "Work Timer",
                      isOn: isRunning,
                      action: ToggleTimerIntent()
                  ) { isOn in
                      Label(isOn ? "Running" : "Stopped",
                            systemImage: isOn
                            ? "hourglass"
                            : "hourglass.bottomhalf.filled")
                  }
                  .tint(.purple)
              }
          }
      }
    • 13:06 - Make the Value Provider configurable

      struct ConfigurableTimerValueProvider: AppIntentControlValueProvider {
          func currentValue(configuration: SelectTimerIntent) async throws -> TimerState {
              let timer = configuration.timer
              let isRunning = try await TimerManager.shared.fetchTimerRunning(timer: timer)
              return TimerState(timer: timer, isRunning: isRunning)
          }
          
          func previewValue(configuration: SelectTimerIntent) -> TimerState {
              return TimerState(timer: configuration.timer, isRunning: false)
          }
      }
    • 13:40 - Make the timer configurable

      struct TimerToggle: ControlWidget {
          var body: some ControlWidgetConfiguration {
              AppIntentControlConfiguration(
                  kind: "com.apple.Productivity.TimerToggle",
                  provider: ConfigurableTimerValueProvider()
              ) { timerState in
                  ControlWidgetToggle(
                      timerState.timer.name,
                      isOn: timerState.isRunning,
                      action: ToggleTimerIntent(timer: timerState.timer)
                  ) { isOn in
                      Label(isOn ? "Running" : "Stopped",
                            systemImage: isOn
                            ? "hourglass"
                            : "hourglass.bottomhalf.filled")
                  }
                  .tint(.purple)
              }
          }
      }
    • 14:26 - Prompt for user configuration automatically

      struct SomeControl: ControlWidget {
          var body: some ControlWidgetConfiguration {
              AppIntentControlConfiguration(
                  // ...
              )
              .promptsForUserConfiguration()
          }
      }
    • 15:42 - Custom action hint -> hint treated as verb phrase

      struct TimerToggle: ControlWidget {
          var body: some ControlWidgetConfiguration {
              AppIntentControlConfiguration(
                  kind: "com.apple.Productivity.TimerToggle",
                  provider: ConfigurableTimerValueProvider()
              ) { timerState in
                  ControlWidgetToggle(
                      timerState.timer.name,
                      isOn: timerState.isRunning,
                      action: ToggleTimerIntent(timer: timerState.timer)
                  ) { isOn in
                      Label(isOn ? "Running" : "Stopped",
                            systemImage: isOn
                            ? "hourglass"
                            : "hourglass.bottomhalf.filled")
                      .controlWidgetActionHint(isOn ?
                                               "Start" : "Stop")
                  }
                  .tint(.purple)
              }
          }
      }
    • 16:56 - Specify a display name and add a description

      struct TimerToggle: ControlWidget {
          var body: some ControlWidgetConfiguration {
              AppIntentControlConfiguration(
                  kind: "com.apple.Productivity.TimerToggle",
                  provider: ConfigurableTimerValueProvider()
              ) { timerState in
                  ControlWidgetToggle(
                      timerState.timer.name,
                      isOn: timerState.isRunning,
                      action: ToggleTimerIntent(timer: timerState.timer)
                  ) { isOn in
                      Label(isOn ? "Running" : "Stopped",
                            systemImage: isOn
                            ? "hourglass"
                            : "hourglass.bottomhalf.filled")
                      .controlWidgetActionHint(isOn ?
                                               "Start" : "Stop")
                  }
                  .tint(.purple)
              }
              .displayName("Productivity Timer")
              .description("Start and stop a productivity timer.")
          }
      }
  • 特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。

    クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。

Developer Footer

  • ビデオ
  • WWDC24
  • システム全体にアプリのコントロールを拡張
  • メニューを開く メニューを閉じる
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    メニューを開く メニューを閉じる
    • アクセシビリティ
    • アクセサリ
    • App Extension
    • App Store
    • オーディオとビデオ(英語)
    • 拡張現実
    • デザイン
    • 配信
    • 教育
    • フォント(英語)
    • ゲーム
    • ヘルスケアとフィットネス
    • アプリ内課金
    • ローカリゼーション
    • マップと位置情報
    • 機械学習
    • オープンソース(英語)
    • セキュリティ
    • SafariとWeb(英語)
    メニューを開く メニューを閉じる
    • 英語ドキュメント(完全版)
    • 日本語ドキュメント(一部トピック)
    • チュートリアル
    • ダウンロード(英語)
    • フォーラム(英語)
    • ビデオ
    Open Menu Close Menu
    • サポートドキュメント
    • お問い合わせ
    • バグ報告
    • システム状況(英語)
    メニューを開く メニューを閉じる
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles(英語)
    • フィードバックアシスタント
    メニューを開く メニューを閉じる
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program(英語)
    • News Partner Program(英語)
    • Video Partner Program(英語)
    • セキュリティ報奨金プログラム(英語)
    • Security Research Device Program(英語)
    Open Menu Close Menu
    • Appleに相談
    • Apple Developer Center
    • App Store Awards(英語)
    • Apple Design Awards
    • Apple Developer Academy(英語)
    • WWDC
    Apple Developerアプリを入手する
    Copyright © 2025 Apple Inc. All rights reserved.
    利用規約 プライバシーポリシー 契約とガイドライン