ドキュメント

構成可能なウィジェットの作成

カスタムSiriKit Intentの定義をプロジェクトに追加して、ウィジェットをカスタマイズするオプションをユーザーに提供します。

最新の英語ドキュメント

Making a Configurable Widget

概要

最も適切な情報をユーザーが簡単にアクセスできるようにするため、ウィジェットはカスタマイズ可能なプロパティを提供できます。たとえば、株価ウィジェットで特定の株式を選択したり、宅配便ウィジェットで追跡番号を入力したりすることができます。ウィジェットでは、カスタムインテントの定義を使ってカスタマイズ可能なプロパティを定義します。この定義は、Siriからの提案とSiriショートカットでこれらの操作をカスタマイズするために使われるのと同じメカニズムです。

構成可能なプロパティをウィジェットに追加するには、次の手順を実行します。

  1. 構成可能なプロパティを定義するカスタムインテントの定義をXcodeプロジェクトに追加します。

  2. ウィジェットでIntentTimelineProviderを使って、ユーザーの選択をタイムラインエントリに組み込みます。

  3. プロパティが動的データに依存している場合は、Intents Extensionを実装します。

アプリでSiriからの提案またはSiriショートカットがすでにサポートされ、カスタムインテントが含まれている場合、作業の大半は完了しています。そうでない場合は、ウィジェット追加の作業を利用して、Siriからの提案や、Siriショートカットのサポートもご検討ください。Intentsを最大限に利用する方法について詳しくは、「SiriKit」を参照してください。

次のセクションでは、ゲームのキャラクターに関する情報を表示するウィジェットに構成可能なプロパティを追加する方法について説明します。

プロジェクトへのカスタムインテントの定義の追加

Xcodeプロジェクトで、「File(ファイル)」>「New File(新しいファイル)」を選択し、「SiriKit Intent Definition File(SiriKit Intentの定義ファイル)」を選択します。メッセージが表示されたら、「Next(次へ)」をクリックしてファイルを保存します。Xcodeで新しい.intentdefinitionファイルが作成され、プロジェクトに追加されます。

「SiriKit Intent Definition File(SiriKit Intentの定義ファイル)」テンプレートが選択されたXcodeの新しいファイルシートを示すスクリーンショット

Xcodeで、Intentの定義ファイルからコードが生成されます。このコードをターゲットで使うには、次の操作を実行します。

  • Intentの定義ファイルをターゲットのメンバーとして含めます。

  • ターゲットのプロパティの「Supported Intents(サポートされているIntents)」セクションにIntentsのクラス名を追加して、ターゲットに含める特定のIntentsを示します。

Intentの定義ファイルをフレームワーク内に含める場合は、収容アプリのターゲットにも含める必要があります。その場合、アプリとフレームワークでのタイプの重複を避けるために、ファイルインスペクタの「Target Membership(ターゲットメンバーシップ)」セクションでアプリターゲットに対して「No Generated Classes(クラスを生成しない)」を選択します。

ユーザーがゲームでキャラクターを選択できるようにするカスタムインテントを追加して構成するには、次の手順を実行します。

  1. プロジェクトナビゲータで、対象のIntentファイルを選択します。Xcodeに、空のインテント定義エディタが表示されます。

  2. 「Editor(エディタ)」>「New Intent(新しいIntent)」の順に選択し、「Custom Intents(カスタムインテントs)」の下のIntentを選択します。

  3. カスタムインテントの名前をSelectCharacterに変更します。属性インスペクタの「Custom Class(カスタムクラス)」フィールドに、ユーザーがコード内でIntentを参照するときに使うクラス名(この例ではSelectCharacterIntent)が表示されることに注意してください。

  4. 「Category(カテゴリ)」を「View(ビュー)」に設定し、「Intent is eligible for widgets(Intentをウィジェットで利用できる)」チェックボックスをオンにして、ウィジェットでIntentを使用できることを示します。

  5. 「Parameters(パラメータ)」の下に、characterという名前の新しいパラメータを追加します。これがウィジェットの構成可能な設定になります。

カスタムインテントとパラメータを含むXcodeのインテント定義エディタを示すスクリーンショット

パラメータを追加したら、そのパラメータの詳細を構成します。パラメータがユーザーに選択肢の静的リストを提供する場合は、「Add Enum(列挙型の追加)」メニュー項目を選択して、静的列挙型を作成します。たとえば、パラメータがキャラクターのアバターを指定し、利用可能なアバターのリストが、変化しない定数のセットである場合は、利用可能な選択肢を指定する静的列挙型をIntentの定義ファイルで使うことができます。利用可能なアバターのリストが変化する、つまり動的に生成される場合は、代わりに動的オプションを含むタイプを使います。

この例のcharacterプロパティは、アプリで利用可能なキャラクターの動的リストに依存します。動的データを提供するには、次の手順に従って新しいタイプを作成します。

  1. 「Type(タイプ)」ポップアップメニューから、「Add Type(タイプの追加)」を選択します。Xcodeのエディタの「Types(タイプ)」セクションに新しいタイプが追加されます。

  2. タイプの名前をGameCharacterに変更します。

  3. 新しいnameプロパティを追加して、「Type(タイプ)」ポップアップメニューから「String(文字列)」を選択します。

  4. SelectCharacter Intentを選択します。

  5. Intentエディタで、「Options are provided dynamically(オプションは動的に提供されます)」チェックボックスをオンにして、コードがこのパラメータにアイテムの動的リストを提供することを示します。

カスタムタイプが追加されたXcodeのインテント定義エディタを示すスクリーンショット

GameCharacterタイプは、ユーザーが選択できるキャラクターを記述します。次のセクションでは、キャラクターのリストを動的に提供するコードを追加します。

プロジェクトへのIntents Extensionの追加

キャラクターのリストを動的に提供するには、Intents Extensionをアプリに追加します。ユーザーがウィジェットを編集するときに、WidgetKitはIntents Extensionを読み込んで、動的情報を提供します。Intents Extensionを追加するには、次の手順を実行します。

  1. 「File(ファイル)」>「New(新規)」>「Target(ターゲット)」の順に選択して、Intents Extensionを選択します。

  2. 「Next(次に)」をクリックします。

  3. Intents Extensionの名前を入力して、「Starting Point(開始点)」を「None(なし)」に設定します。

  4. 「Finish(完了する)」をクリックします。Xcodeで新しいスキームのアクティブ化について確認を求められたら、「Activate(アクティブ化)」をクリックします。

  5. 新しいターゲットのプロパティの「General(一般)」タブで、「Supported Intents(サポートされているIntents)」セクションにエントリを追加して、「Class Name(クラス名)」をSelectCharacterIntentに設定します。

  6. プロジェクトナビゲータで、以前に追加したカスタムインテントの定義ファイルを選択します。

  7. ファイルインスペクタを使って、定義ファイルをIntent Extensionのターゲットに追加します。

動的な値を提供するIntentハンドラの実装

動的な値を提供するカスタムインテントを含むウィジェットをユーザーが編集する場合、システムは、それらの値を提供するオブジェクトを必要とします。システムは、Intents ExtensionにIntentのハンドラを提供するよう求めることで、このオブジェクトを識別します。XcodeでIntents Extensionが作成されたときに、IntentHandlerという名前のクラスを含むIntentHandler.swift という名前のプロジェクトにファイルが追加されます。このクラスに、ハンドラを返すメソッドが含まれています。このハンドラを拡張して、ウィジェットをカスタマイズするための値を提供します。

カスタムインテントの定義ファイルに基づいて、ハンドラが適合する必要があるSelectCharacterIntentHandlingプロトコルがXcodeで生成されます。この適合性をIntentHandlerクラスの宣言に追加します。(このプロトコル、およびXcodeで自動的に生成される他のタイプの詳細を確認するには、SelectCharacterIntentHandlingを選択して、「Navigate(ナビゲーション)」>「Jump to Definition(定義に移動)」の順に選択します。)


class IntentHandler: INExtension, SelectCharacterIntentHandling {
    ...
}

ハンドラが動的なオプションを提供する場合、ハンドラはprovide[Type]OptionalCollection(for:with:)という名前のメソッドを実装する必要があります。ここで、[Type]はIntentの定義ファイルにあるタイプの名前です。このメソッドが見つからない場合、Xcodeはビルドエラーをレポートし、プロトコルスタブを追加するfix-it(修正ツール)を提供します。プロジェクトをビルドし、fix-itを使ってこのスタブを追加します。

このメソッドは、ユーザーが呼び出すcompletionハンドラを含めて、INObjectCollection<GameCharacter>を渡します。GameCharacterタイプに注目してください。これは、Intentの定義ファイルにあるカスタムタイプです。Xcodeでは、このタイプを定義するコードが次のように生成されます。


public class GameCharacter: INObject {
    @available(iOS 13.0, macOS 11.0, watchOS 6.0, *)
    @NSManaged public var name: String?
}

nameプロパティに注目してください。これもまた、追加したカスタムタイプにつてIntentの定義ファイルから取得されたものです。

provideCharacterOptionsCollection(for:with:)メソッドを実装するために、ウィジェットでは、ゲームのプロジェクトに存在する構造体を使います。この構造体は、次に示すとおり、利用可能なキャラクターのリストとキャラクターの詳細を定義します。


struct CharacterDetail {
    let name: String
    let avatar: String
    let healthLevel: Double
    let heroType: String


    static let availableCharacters = [
        CharacterDetail(name: "Power Panda", avatar: "🐼", healthLevel: 0.14, heroType: "Forest Dweller"),
        CharacterDetail(name: "Unipony", avatar: "🦄", healthLevel: 0.67, heroType: "Free Rangers"),
        CharacterDetail(name: "Spouty", avatar: "🐳", healthLevel: 0.83, heroType: "Deep Sea Goer")
    ]
}

Intentハンドラのコードでは、availableCharacters配列が繰り返され、キャラクターごとにGameCharacterオブジェクトが作成されます。わかりやすくするために、GameCharacterのIDはキャラクターの名前になっています。ゲームキャラクターの配列がINObjectCollectionに挿入され、ハンドラがcollectionをcompletionハンドラに渡します。


class IntentHandler: INExtension, SelectCharacterIntentHandling {
    func provideCharacterOptionsCollection(for intent: SelectCharacterIntent, with completion: @escaping (INObjectCollection<GameCharacter>?, Error?) -> Void) {


        // Iterate the available characters, creating
        // a GameCharacter for each one.
        let characters: [GameCharacter] = CharacterDetail.availableCharacters.map { character in
            let gameCharacter = GameCharacter(
                identifier: character.name,
                display: character.name
            )
            gameCharacter.name = character.name
            return gameCharacter
        }


        // Create a collection with the array of characters.
        let collection = INObjectCollection(items: characters)


        // Call the completion handler, passing the collection.
        completion(collection, nil)
    }
}

Intentの定義ファイルの構成が完了し、Intents Extensionがアプリに追加されたら、ユーザーは、ウィジェットを編集して、表示する特定のキャラクターを選択できます。WidgetKitは、Intentの定義ファイルの情報を使って、ウィジェットの編集用のユーザーインターフェイスを自動的に作成します。

ユーザーがウィジェットを編集し、キャラクターを選択したら、次は、その選択をウィジェットの表示に組み込みます。

ユーザーがカスタマイズした値の処理

構成可能なプロパティをサポートするために、ウィジェットではIntentTimelineProvider構成を使います。たとえば、character-detailsウィジェットでは、構成が次のように定義されます。


struct CharacterDetailWidget: Widget {
    var body: some WidgetConfiguration {
        IntentConfiguration(
            kind: "com.mygame.character-detail",
            intent: SelectCharacterIntent.self,
            provider: CharacterDetailProvider(),
        ) { entry in
            CharacterDetailView(entry: entry)
        }
        .configurationDisplayName("Character Details")
        .description("Displays a character's health and other details")
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
    }
}

SelectCharacterIntentパラメータは、ウィジェットでユーザーがカスタマイズ可能なプロパティを決定します。構成では、CharacterDetailProviderを使って、ウィジェットのタイムラインイベントを管理します。タイムラインプロバイダについて詳しくは、「ウィジェットを最新の状態に維持」を参照してください。

ユーザーがウィジェットを編集した後、WidgetKitは、タイムラインエントリをリクエストするときに、ユーザーがカスタマイズした値をプロバイダに渡します。通常、Intentの関連する詳細を、プロバイダが生成するタイムラインエントリに含めます。この例では、プロバイダが、ヘルパメソッドでIntentのキャラクターの名前を使ってCharacterDetailを検索してから、キャラクターの詳細を含むエントリでタイムラインを作成します。


struct CharacterDetailProvider: IntentTimelineProvider {
    func getTimeline(for configuration: SelectCharacterIntent, in context: Context, completion: @escaping (Timeline<CharacterDetailEntry>) -> Void) {
        // Access the customized properties of the intent.
        let characterDetail = lookupCharacterDetail(for: configuration.character.name)


        // Construct a timeline entry for the current date, and include the character details.
        let entry = CharacterDetailEntry(date: Date(), detail: characterDetail)


        // Create the timeline and call the completion handler. The .never reload 
        // policy indicates that the containing app will use WidgetCenter methods 
        // to reload the widget's timeline when the details change.
        let timeline = Timeline(entries: [entry], policy: .never)
        completion(timeline)
    }
}

ユーザーがカスタマイズした値をタイムラインエントリに含めると、ウィジェットが適切なコンテンツを表示できるようになります。

関連項目

構成可能なウィジェット