見出し画像

ライブアクティビティにインタラクティブな機能を実装する方法

ライブアクティビティにインタラクティブな機能を実装する方法についてまとめる。


iOS 17から、App Intentを使用することで、ライブアクティビティにインタラクティブな機能を実装することができるようになった。

App Intent

App Intentとは

Appがシステムに公開する単一で分離された機能の単位です。 例えば、インテントには新しいカレンダーイベントを作成、特定の画面を開く、注文するなどがあります。インテントはショートカットの実行やSiriでの検索など、ユーザーのリクエストに応じて実行することも、集中モードフィルタやショートカットオートメーションを使って自動的に実行することも可能です。

Dive into App Intents - WWDC22

簡単に例えると、App Intent は実行したい処理が記述されている構造体。この構造体で定義されている処理は、アプリを起動せずに実行することができる。
さらにApp Intent は、ライブアクティビティだけではなく、ショートカットやSiri検索でも使用することができる。
一度、App Intentを設計すれば、あらゆる場面で使用することができる。

サンプルコード

struct IncrementIntent: AppIntent {
    static var title: LocalizedStringResource = "Increment Count"
    
    func perform() async throws -> some IntentResult {
        Counter.incrementCount()
        return .result()
    }
}

App Intent プロトコルに準拠した構造体を定義する。 perform() 内にインテント実行時に行いたい処理を記述し、返り値は.result() とする。
インテント実行後にダイアログを表示したい時は、.result() の引数でダイアログに表示したい内容を指定することができるが、ライブアクティビティやウィジェットでは、インテント実行後にダイアログを表示することはできない。
ライブアクティビティやウィジェット経由でインテントを実行する時は、title を使用することはないが、App Intent プロトコルに準拠するために定義しておく必要がある。

インタラクティブな機能を実現することができるUI

iOS 17から利用可能な新しいイニシャライザが、Button とToggle に追加された。

イニシャライザの引数に、AppIntent プロトコルに準拠したインスタンスを指定する。生成された各UIパーツにアクションが加わると、初期化時に指定したApp Intentが実行される。
この新しいイニシャライザで各UIパーツのインスタンスを生成することで、ライブアクティビティにインタラクティブな機能を実装することができる。

また、WWDC23で実施されたセッションによると、現時点(2023/11/4)ではインタラクティブな機能を実現するUIパーツはこの2つのみとなっている。

Buttonの実装例

App Intent

struct IncrementIntent: AppIntent {
    static var title: LocalizedStringResource = "Increment Count"
    
    func perform() async throws -> some IntentResult {
        Counter.incrementCount()
        
        let activity = Activity<CounterAttributes>.activities.first
        if let activity {
            let updatedState = CounterAttributes.ContentState(
                isShow: Counter.currentDisplay(),
                count: Counter.currentCount())
            
            Task { @MainActor in
                await activity.update(
                    .init(
                        state: updatedState,
                        staleDate: .distantFuture)
                )
            }
        }
        return .result()
    }
}

View

Button(intent: IncrementIntent()) {
    Image(systemName: "plus")
        .frame(width: 15, height:15)
}

Toggleの実装例

App Intent

struct DisplayIntent: AppIntent {
    static var title: LocalizedStringResource = "Display Count"
    
    func perform() async throws -> some IntentResult {
        Counter.toggleDisplay()
        
        let activity = Activity<CounterAttributes>.activities.first
        if let activity {
            let updatedState = CounterAttributes.ContentState(
                isShow: Counter.currentDisplay(),
                count: Counter.currentCount())
            
            Task { @MainActor in
                await activity.update(
                    .init(
                        state: updatedState,
                        staleDate: .distantFuture)
                )
            }
        }
        return .result()
    }
}

View

Toggle(isOn: context.state.isShow, intent: DisplayIntent()) {
    if context.state.isShow {
        Text("Hide")
    } else {
        Text("Show")
    }
}

インタラクティブ処理の流れ

ライブアクティビティでインタラクティブな処理を行う流れは以下のようになる。

  1. Button やToggle がユーザーアクションを検知

  2. 初期化時に指定したインテントを実行開始

  3. インテント内でライブアクティビティ更新処理を実施

  4. インテントの実行完了

  5. 更新内容に基づいてライブアクティビティの再構成処理を実施

  6. ライブアクティビティの表示が更新される

ここから先は

7,193字 / 1画像

¥ 200

最後まで記事をお読みいただきありがとうございます! 記事が参考になればフォロー・♥いただけると凄く励みになります🥳