struct IntentConfiguration
struct WidgetInfo
最も適切な情報をユーザーが簡単にアクセスできるようにするため、ウィジェットはカスタマイズ可能なプロパティを提供できます。たとえば、株価ウィジェットで特定の株式を選択したり、宅配便ウィジェットで追跡番号を入力したりすることができます。ウィジェットでは、カスタムインテントの定義を使ってカスタマイズ可能なプロパティを定義します。この定義は、Siriからの提案とSiriショートカットでこれらの操作をカスタマイズするために使われるのと同じメカニズムです。
構成可能なプロパティをウィジェットに追加するには、次の手順を実行します。
構成可能なプロパティを定義するカスタムインテントの定義をXcodeプロジェクトに追加します。
ウィジェットでIntent
を使って、ユーザーの選択をタイムラインエントリに組み込みます。
プロパティが動的データに依存している場合は、Intents Extensionを実装します。
アプリでSiriからの提案またはSiriショートカットがすでにサポートされ、カスタムインテントが含まれている場合、作業の大半は完了しています。そうでない場合は、ウィジェット追加の作業を利用して、Siriからの提案や、Siriショートカットのサポートもご検討ください。Intentsを最大限に利用する方法について詳しくは、「SiriKit」を参照してください。
次のセクションでは、ゲームのキャラクターに関する情報を表示するウィジェットに構成可能なプロパティを追加する方法について説明します。
Xcodeプロジェクトで、「File(ファイル)」>「New File(新しいファイル)」を選択し、「SiriKit Intent Definition File(SiriKit Intentの定義ファイル)」を選択します。メッセージが表示されたら、「Next(次へ)」をクリックしてファイルを保存します。Xcodeで新しい.intentdefinition
ファイルが作成され、プロジェクトに追加されます。
Xcodeで、Intentの定義ファイルからコードが生成されます。このコードをターゲットで使うには、次の操作を実行します。
Intentの定義ファイルをターゲットのメンバーとして含めます。
ターゲットのプロパティの「Supported Intents(サポートされているIntents)」セクションにIntentsのクラス名を追加して、ターゲットに含める特定のIntentsを示します。
Intentの定義ファイルをフレームワーク内に含める場合は、収容アプリのターゲットにも含める必要があります。その場合、アプリとフレームワークでのタイプの重複を避けるために、ファイルインスペクタの「Target Membership(ターゲットメンバーシップ)」セクションでアプリターゲットに対して「No Generated Classes(クラスを生成しない)」を選択します。
ユーザーがゲームでキャラクターを選択できるようにするカスタムインテントを追加して構成するには、次の手順を実行します。
プロジェクトナビゲータで、対象のIntentファイルを選択します。Xcodeに、空のインテント定義エディタが表示されます。
「Editor(エディタ)」>「New Intent(新しいIntent)」の順に選択し、「Custom Intents(カスタムインテントs)」の下のIntentを選択します。
カスタムインテントの名前をSelect
に変更します。属性インスペクタの「Custom Class(カスタムクラス)」フィールドに、ユーザーがコード内でIntentを参照するときに使うクラス名(この例ではSelect
)が表示されることに注意してください。
「Category(カテゴリ)」を「View(ビュー)」に設定し、「Intent is eligible for widgets(Intentをウィジェットで利用できる)」チェックボックスをオンにして、ウィジェットでIntentを使用できることを示します。
「Parameters(パラメータ)」の下に、character
という名前の新しいパラメータを追加します。これがウィジェットの構成可能な設定になります。
パラメータを追加したら、そのパラメータの詳細を構成します。パラメータがユーザーに選択肢の静的リストを提供する場合は、「Add Enum(列挙型の追加)」メニュー項目を選択して、静的列挙型を作成します。たとえば、パラメータがキャラクターのアバターを指定し、利用可能なアバターのリストが、変化しない定数のセットである場合は、利用可能な選択肢を指定する静的列挙型をIntentの定義ファイルで使うことができます。利用可能なアバターのリストが変化する、つまり動的に生成される場合は、代わりに動的オプションを含むタイプを使います。
この例のcharacter
プロパティは、アプリで利用可能なキャラクターの動的リストに依存します。動的データを提供するには、次の手順に従って新しいタイプを作成します。
「Type(タイプ)」ポップアップメニューから、「Add Type(タイプの追加)」を選択します。Xcodeのエディタの「Types(タイプ)」セクションに新しいタイプが追加されます。
タイプの名前をGame
に変更します。
新しいname
プロパティを追加して、「Type(タイプ)」ポップアップメニューから「String(文字列)」を選択します。
Select
Intentを選択します。
Intentエディタで、「Options are provided dynamically(オプションは動的に提供されます)」チェックボックスをオンにして、コードがこのパラメータにアイテムの動的リストを提供することを示します。
Game
タイプは、ユーザーが選択できるキャラクターを記述します。次のセクションでは、キャラクターのリストを動的に提供するコードを追加します。
注意
Intent内のパラメータの順序によって、ユーザーがウィジェットを編集するときに表示されるパラメータの順序が決まります。リスト内のアイテムはドラッグして並べ替えることができます。
キャラクターのリストを動的に提供するには、Intents Extensionをアプリに追加します。ユーザーがウィジェットを編集するときに、WidgetKitはIntents Extensionを読み込んで、動的情報を提供します。Intents Extensionを追加するには、次の手順を実行します。
「File(ファイル)」>「New(新規)」>「Target(ターゲット)」の順に選択して、Intents Extensionを選択します。
「Next(次に)」をクリックします。
Intents Extensionの名前を入力して、「Starting Point(開始点)」を「None(なし)」に設定します。
「Finish(完了する)」をクリックします。Xcodeで新しいスキームのアクティブ化について確認を求められたら、「Activate(アクティブ化)」をクリックします。
新しいターゲットのプロパティの「General(一般)」タブで、「Supported Intents(サポートされているIntents)」セクションにエントリを追加して、「Class Name(クラス名)」をSelect
に設定します。
プロジェクトナビゲータで、以前に追加したカスタムインテントの定義ファイルを選択します。
ファイルインスペクタを使って、定義ファイルをIntent Extensionのターゲットに追加します。
重要
ファイルインスペクタで、収容アプリ、Widget Extension、およびIntent Extensionがすべて、このIntentの定義ファイルを収容されていることを確認してください。
動的な値を提供するカスタムインテントを含むウィジェットをユーザーが編集する場合、システムは、それらの値を提供するオブジェクトを必要とします。システムは、Intents ExtensionにIntentのハンドラを提供するよう求めることで、このオブジェクトを識別します。XcodeでIntents Extensionが作成されたときに、Intent
という名前のクラスを含むIntent
という名前のプロジェクトにファイルが追加されます。このクラスに、ハンドラを返すメソッドが含まれています。このハンドラを拡張して、ウィジェットをカスタマイズするための値を提供します。
カスタムインテントの定義ファイルに基づいて、ハンドラが適合する必要があるSelect
プロトコルがXcodeで生成されます。この適合性をIntentHandlerクラスの宣言に追加します。(このプロトコル、およびXcodeで自動的に生成される他のタイプの詳細を確認するには、Select
を選択して、「Navigate(ナビゲーション)」>「Jump to Definition(定義に移動)」の順に選択します。)
class IntentHandler: INExtension, SelectCharacterIntentHandling {
...
}
ハンドラが動的なオプションを提供する場合、ハンドラはprovide[Type]Optional
という名前のメソッドを実装する必要があります。ここで、[Type]
はIntentの定義ファイルにあるタイプの名前です。このメソッドが見つからない場合、Xcodeはビルドエラーをレポートし、プロトコルスタブを追加するfix-it(修正ツール)を提供します。プロジェクトをビルドし、fix-itを使ってこのスタブを追加します。
このメソッドは、ユーザーが呼び出すcompletionハンドラを含めて、INObject
を渡します。Game
タイプに注目してください。これは、Intentの定義ファイルにあるカスタムタイプです。Xcodeでは、このタイプを定義するコードが次のように生成されます。
public class GameCharacter: INObject {
@available(iOS 13.0, macOS 11.0, watchOS 6.0, *)
@NSManaged public var name: String?
}
name
プロパティに注目してください。これもまた、追加したカスタムタイプにつてIntentの定義ファイルから取得されたものです。
provide
メソッドを実装するために、ウィジェットでは、ゲームのプロジェクトに存在する構造体を使います。この構造体は、次に示すとおり、利用可能なキャラクターのリストとキャラクターの詳細を定義します。
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ハンドラのコードでは、available
配列が繰り返され、キャラクターごとにGame
オブジェクトが作成されます。わかりやすくするために、Game
のIDはキャラクターの名前になっています。ゲームキャラクターの配列がINObject
に挿入され、ハンドラが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の定義ファイルの情報を使って、ウィジェットの編集用のユーザーインターフェイスを自動的に作成します。
ユーザーがウィジェットを編集し、キャラクターを選択したら、次は、その選択をウィジェットの表示に組み込みます。
構成可能なプロパティをサポートするために、ウィジェットではIntent
構成を使います。たとえば、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])
}
}
Select
パラメータは、ウィジェットでユーザーがカスタマイズ可能なプロパティを決定します。構成では、Character
を使って、ウィジェットのタイムラインイベントを管理します。タイムラインプロバイダについて詳しくは、「ウィジェットを最新の状態に維持」を参照してください。
ユーザーがウィジェットを編集した後、WidgetKitは、タイムラインエントリをリクエストするときに、ユーザーがカスタマイズした値をプロバイダに渡します。通常、Intentの関連する詳細を、プロバイダが生成するタイムラインエントリに含めます。この例では、プロバイダが、ヘルパメソッドでIntentのキャラクターの名前を使ってCharacter
を検索してから、キャラクターの詳細を含むエントリでタイムラインを作成します。
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)
}
}
ユーザーがカスタマイズした値をタイムラインエントリに含めると、ウィジェットが適切なコンテンツを表示できるようになります。
struct IntentConfiguration
struct WidgetInfo