axiom-app-discoverability について
iOS開発者向けのアプリの見つけやすさに関する計画・診断スキルです。App Intents、App Shortcuts、Core Spotlight、NSUserActivityを組み合わせ、アプリの操作やコンテンツをシステム検索や提案に表示する方法を説明します。
主な機能
- 01複数APIによる発見性の方針策定
- 02App Intentsによる操作の公開
- 03App Shortcutsと提案フレーズの設定
- 04Core Spotlightによるコンテンツ索引
- 05NSUserActivityによる画面の登録
- 06Spotlight索引の問題確認
活用シーン
- 01SiriやSpotlightにアプリ操作を表示
- 02システム検索でアプリのコンテンツを発見可能にする
- 03重要な画面の検索と予測を有効化
- 04Spotlightの結果欠落や古さを調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ charleswiltgen/ axiom --skill axiom-app-discoverabilityインストール後、Claude Code で/axiom-app-discoverabilityを入力して読み込まれたか確認します。
詳細
アプリの見つけやすさ
概要
基本原則 複数の API を通じてシステムにメタデータを提供し、アプリを表示するタイミングはシステムに判断させます。
iOS は、App Intents、App Shortcuts、Core Spotlight、NSUserActivity を通じて提供するメタデータに基づき、Spotlight、Siri の候補、システムの各種機能でアプリを表示します。システムは実際の利用状況から学習し、頻繁に使われるアクションを優先します。単一の API だけでは不十分です。見つけやすさを包括的に実現するには、複数の API を活用する必要があります。
重要なポイント iOS は、ユーザーが実際に利用するショートカットやアクティビティを優先します。インテントが誰にも使われなければ、システムはそれを表示しなくなります。明確でアクション指向のメタデータを提供すれば、あとはシステムが処理します。
このスキルを使うタイミング
次のような場合にこのスキルを使います:
- アプリを Spotlight の検索結果に表示させる
- 関連する状況で Siri がアプリを提案できるようにする
- アクションボタン(iPhone/Apple Watch Ultra)にアプリのアクションを追加する
- アプリのコンテンツをシステム全体で見つけられるようにする
- 実装前に見つけやすさのアーキテクチャを計画する
- 「アプリが提案されないのはなぜ?」という問題を調査する
次のような場合はこのスキルを使わないでください:
- 詳細な API リファレンスが必要な場合(app-intents-ref、axiom-app-shortcuts-ref、axiom-core-spotlight-ref を使ってください)
- 特定の API を実装する場合(リファレンススキルを使ってください)
- App Intent を 1 つだけ追加したい場合(app-intents-ref を使ってください)
見つけやすさを実現する 6 ステップ戦略
これは、複数の本番アプリで見つけやすさを実装した開発者による、実績のある戦略です。実装時間:最小限の実用的な見つけやすさなら一晩で実装できます。
ステップ 1:App Intents を追加する
App Intents は Spotlight 検索、Siri のリクエスト、ショートカットの候補を支えます。AppIntents がなければ、アプリが効果的に表示されることはありません。
struct OrderCoffeeIntent: AppIntent {
static var title: LocalizedStringResource = "Order Coffee"
static var description = IntentDescription("Orders coffee for pickup")
@Parameter(title: "Coffee Type")
var coffeeType: CoffeeType
@Parameter(title: "Size")
var size: CoffeeSize
func perform() async throws -> some IntentResult {
try await CoffeeService.shared.order(type: coffeeType, size: size)
return .result(dialog: "Your \(size) \(coffeeType) is ordered")
}
}
重要な理由 App Intents は基盤です。それ以外のすべては App Intents を基に構築されます。
参照:完全な API リファレンスについては app-intents-ref をご覧ください
ステップ 2:提案フレーズを設定した App Shortcuts を追加する
App Shortcuts を使うと、インストール後すぐにインテントを利用できるようになります。設定は不要です。
struct CoffeeAppShortcuts: AppShortcutsProvider {
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: OrderCoffeeIntent(),
phrases: [
"Order coffee in \(.applicationName)",
"Get my usual coffee from \(.applicationName)"
],
shortTitle: "Order Coffee",
systemImageName: "cup.and.saucer.fill"
)
}
static var shortcutTileColor: ShortcutTileColor = .tangerine
}
重要な理由 App Shortcuts がない場合、ユーザーはショートカットを手動で設定する必要があります。追加すれば、アクションがすぐに Siri、Spotlight、アクションボタン、コントロールセンターに表示されます。
重要 suggestedPhrase のパターンを使ってください。これにより、Spotlight のアクション候補や Siri のカルーセルでシステムに提案される可能性が高まります。
参照:フレーズのパターンとベストプラクティスについては app-shortcuts-ref をご覧ください
ステップ 3:Core Spotlight を通じて検索可能なコンテンツを公開する
重要なコンテンツをインデックス化します。ユーザーの検索クエリに一致する項目がシステムに表示されます。
import CoreSpotlight
import UniformTypeIdentifiers
func indexOrder(_ order: Order) {
let attributes = CSSearchableItemAttributeSet(contentType: .item)
attributes.title = order.coffeeName
attributes.contentDescription = "Order from \(order.date.formatted())"
attributes.keywords = ["coffee", "order", order.coffeeName]
let item = CSSearchableItem(
uniqueIdentifier: order.id.uuidString,
domainIdentifier: "orders",
attributeSet: attributes
)
CSSearchableIndex.default().indexSearchableItems([item]) { error in
if let error = error {
print("Indexing error: \(error)")
}
}
}
重要な理由 Core Spotlight を使うと、アプリのコンテンツを検索できるようになります。ユーザーが Spotlight で「ラテ」を検索すると、アプリの注文が表示されます。
重要なものだけをインデックス化する すべてをインデックス化しないでください。ユーザー向けのコンテンツ(注文、書類、メモなど)に絞りましょう。
参照:バッチ処理、削除のパターン、ベストプラクティスについては core-spotlight-ref をご覧ください
ステップ 4:価値の高い画面に NSUserActivity を使う
重要な画面を、検索と予測の対象として設定します。
func viewOrder(_ order: Order) {
let activity = NSUserActivity(activityType: "com.coffeeapp.viewOrder")
activity.title = order.coffeeName
activity.isEligibleForSearch = true
activity.isEligibleForPrediction = true
activity.persistentIdentifier = order.id.uuidString
// Connect to App Intents
activity.appEntityIdentifier = order.id.uuidString
// Provide rich metadata
let attributes = CSSearchableItemAttributeSet(contentType: .item)
attributes.contentDescription = "Your \(order.coffeeName) order"
attributes.thumbnailData = order.imageData
activity.contentAttributeSet = attributes
activity.becomeCurrent()
// In your view controller or SwiftUI view
self.userActivity = activity
}
重要な理由 システムは、ユーザーが頻繁に訪れる画面を学習し、先回りして提案します。ロック画面のウィジェット、Siri の候補、Spotlight のすべてがその恩恵を受けます。
重要 ユーザーが再び開きたいと思う画面だけを設定してください。設定画面、オンボーディング画面、エラー画面は対象にしないでください。
参照:対象設定のパターンとアクティビティの継続については core-spotlight-ref をご覧ください
ステップ 5:インテントのメタデータを適切に設定する
明確な説明とタイトルが重要です。Spotlight にそのまま表示されるためです。
❌ 避ける:一般的すぎる、または不明瞭な表現
static var title: LocalizedStringResource = "Do Thing"
static var description = IntentDescription("Performs action")
✅ 推奨:具体的でアクション指向の表現
static var title: LocalizedStringResource = "Order Coffee"
static var description = IntentDescription("Orders coffee for pickup")
パラメーターの要約は自然な言葉で記述してください:
static var parameterSummary: some ParameterSummary {
Summary("Order \(\.$size) \(\.$coffeeType)")
}
// Siri: "Order large latte"
重要な理由 メタデータが不十分だと、ユーザーはインテントの機能を理解できません。明確なメタデータは、利用の増加につながり、システムによる優先表示を促します。
ステップ 6:使用状況に基づく優先度の向上
システムは、ユーザーが実際に呼び出すショートカットやアクティビティの優先度を高めます。誰も使わないインテントは、システムによって非表示になります。
これは自動的に行われるため、制御できません。制御できるのは次の点です。
- 見つけやすさ — 見つけやすくする(ステップ 1~5)
- 有用性 — 使う価値を高める(優れたインテントを設計する)
- プロモーション — 利用可能なショートカットをユーザーに知らせる(SiriTipView)
// Promote your shortcuts in-app
SiriTipView(intent: OrderCoffeeIntent(), isVisible: $showTip)
.siriTipViewStyle(.dark)
重要な理由 メタデータが完璧でも、ユーザーがショートカットの存在を知らなければ役に立ちません。アプリの UI でユーザーに使い方を案内してください。
参照:SiriTipView と ShortcutsLink のパターンについては app-shortcuts-ref をご覧ください
判断ツリー:ユースケースに応じた API の選択
┌─ Need to expose app functionality? ────────────────────────────────┐
│ │
│ ┌─ YES → App Intents (AppIntent protocol) │
│ │ └─ Want instant availability without user setup? │
│ │ └─ YES → App Shortcuts (AppShortcutsProvider) │
│ │ │
│ └─ NO → Exposing app CONTENT (not actions)? │
│ │ │
│ ├─ User-initiated activity (viewing screen)? │
│ │ └─ YES → NSUserActivity with isEligibleForSearch │
│ │ │
│ └─ Indexing all content (documents, orders, notes)? │
│ └─ YES → Core Spotlight (CSSearchableItem) │
│ │
│ ┌─ Already using App Intents? │
│ │ └─ Want automatic Spotlight search for entities? │
│ │ └─ YES → IndexedEntity protocol │
│ │ │
│ └─ Want to connect screen to App Intent entity? │
│ └─ YES → NSUserActivity.appEntityIdentifier │
└──────────────────────────────────────────────────────────────────┘
クイックリファレンステーブル
| ユースケース | API | 例 |
|---|---|---|
| Siri/ショートカットにアクションを公開する | AppIntent | 「コーヒーを注文」 |
| アクションをすぐに利用可能にする | AppShortcut | Spotlight に即座に表示 |
| アプリのすべてのコンテンツをインデックス化する | CSSearchableItem | すべてのコーヒー注文を検索可能にする |
| 現在の画面を重要な画面としてマークする | NSUserActivity | ユーザーが注文の詳細を表示中 |
| 「検索」アクションを自動生成する | IndexedEntity | 「次の条件に一致する注文を検索…」 |
| 画面を App Intent にリンクする | appEntityIdentifier | 特定の注文へのディープリンク |
クイック実装パターン(「一晩で実装」する方法)
最小限の実用的な見つけやすさを実現するには:
1. 主要な App Intent を 1~3 個定義する(30 分)
// Your app's most valuable actions
struct OrderCoffeeIntent: AppIntent { /* ... */ }
struct ReorderLastIntent: AppIntent { /* ... */ }
struct ViewOrdersIntent: AppIntent { /* ... */ }
2. AppShortcutsProvider を作成する(15 分)
struct CoffeeAppShortcuts: AppShortcutsProvider {
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: OrderCoffeeIntent(),
phrases: ["Order coffee in \(.applicationName)"],
shortTitle: "Order",
systemImageName: "cup.and.saucer.fill"
)
// Add 2-3 more shortcuts
}
}
3. 最上位のコンテンツをインデックス化する(30 分)
// Index most recent/important content only
func indexRecentOrders() {
let recentOrders = try await OrderService.shared.recent(limit: 20)
let items = recentOrders.map { createSearchableItem(from: $0) }
CSSearchableIndex.default().indexSearchableItems(items)
}
4. 詳細画面に NSUserActivity を追加する(30 分)
// In your detail view controllers/views
let activity = NSUserActivity(activityType: "com.app.viewOrder")
activity.isEligibleForSearch = true
activity.becomeCurrent()
self.userActivity = activity
5. Spotlight とショートカットでテストする(15 分)
- ショートカットアプリを開く → アプリを検索する → ショートカットが表示されることを確認する
- Spotlight で検索する → コンテンツを検索する → 検索結果を確認する
- Siri に話しかける → 「[YourApp] でコーヒーを注文」→ 動作することを確認する
合計時間:約 2 時間で基本的な見つけやすさを実現できます
大規模なコンテンツライブラリ向けのバッチインデックス化
1,000 件以上のアイテムをインデックス化する場合は、起動時の速度低下を避けるため、バッチに分けてインデックス化してください。
func indexAllContent() async {
let allItems = try await ContentService.shared.all()
let batchSize = 100
for batch in stride(from: 0, to: allItems.count, by: batchSize) {
let slice = Array(allItems[batch..<min(batch + batchSize, allItems.count)])
let searchableItems = slice.map { createSearchableItem(from: $0) }
CSSearchableIndex.default().indexSearchableItems(searchableItems) { error in
if let error { print("Batch index error: \(error)") }
}
// Yield between batches to avoid blocking
try? await Task.sleep(for: .milliseconds(50))
}
}
ベストプラクティス:
- 起動時ではなく、バックグラウンド処理中に 100 件ずつインデックス化する
domainIdentifierを使ってコンテンツをグループ化し、一括削除を効率化する- コンテンツが変更されたら、差分をインデックス化する(すべてを再インデックス化しない)
- 50,000 件以上の場合は、
CSSearchableIndex.beginBatch()/endBatch()を使ってアトミックに更新する
Spotlight のデバッグ
インデックス化したコンテンツが Spotlight に表示されない場合:
確認チェックリスト
- インデックス化が成功したか確認する —
indexSearchableItemsに完了ハンドラーのログ出力を追加する - 処理を待つ — Spotlight が新しいアイテムを処理するまで 10~30 秒かかる場合があります
- 正確なタイトルで検索する — 最初は Spotlight が部分一致のキーワードを検出できない場合があります
contentTypeを確認する — 一般的なコンテンツには.itemを使用します。誤った種類を指定すると、ランキングに影響する場合があります
よくあるインデックス化のミス
| 問題 | 原因 | 修正方法 |
|---|---|---|
| コンテンツが表示されない | title 属性がない | 必ず attributeSet.title を設定する |
| 順位が低い | キーワードがない | 関連する keywords 配列を追加する |
| 古い検索結果が表示される | 削除済みのアイテムを削除していない | deleteSearchableItems(withIdentifiers:) を呼び出す |
| 重複した検索結果が表示される | 一意の識別子が安定していない | 永続 ID(UUID、データベースの主キー)を使用する |
| クォータを超過する | アイテムをインデックス化しすぎている | ユーザーに関連するコンテンツ(最近のアイテム、お気に入り)に限定する |
Spotlight のインデックス化をテストする
// Verify items are indexed
CSSearchableIndex.default().fetchLastClientState { state, error in
print("Last client state: \(String(describing: state))")
}
// Search programmatically to verify
let query = CSSearchQuery(queryString: "title == 'My Item'*", attributes: ["title"])
query.foundItemsHandler = { items in
print("Found \(items.count) items")
}
query.start()
アンチパターン(避けるべきこと)
❌ アンチパターン 1:App Shortcuts を実装せず、App Intents だけを実装する
問題 ユーザーがショートカットを手動で設定する必要があります。アプリは Spotlight/Siri に自動的に表示されません。
修正方法 常に、候補フレーズを指定した AppShortcutsProvider を作成してください。
❌ アンチパターン 2:Core Spotlight ですべてをインデックス化する
問題 数千件のアイテムをインデックス化すると、パフォーマンスが低下し、クォータの問題が発生します。ユーザーが情報過多になることもあります。
// ❌ BAD: Index all 10,000 orders
let allOrders = try await OrderService.shared.all()
修正方法 最近のアイテム、お気に入り、頻繁にアクセスされるアイテムなどを選択してインデックス化してください。
// ✅ GOOD: Index recent orders only
let recentOrders = try await OrderService.shared.recent(limit: 50)
❌ アンチパターン 3:汎用的なインテントのタイトルと説明
問題 Spotlight にこれらがそのまま表示されます。汎用的なテキストはユーザーを混乱させます。
// ❌ BAD
static var title: LocalizedStringResource = "Action"
static var description = IntentDescription("Does something")
修正方法 具体的で、行動を促す表現を使います。
// ✅ GOOD
static var title: LocalizedStringResource = "Order Coffee"
static var description = IntentDescription("Orders your favorite coffee for pickup")
❌ アンチパターン 4:ショートカットについてユーザーに知らせない
問題 完璧に実装しても、ユーザーが存在を知らなければ意味がありません。
修正方法 SiriTipView を使って、アプリの UI でショートカットを紹介します。
// Show tip after user places order
SiriTipView(intent: ReorderLastIntent(), isVisible: $showTip)
❌ アンチパターン 5:すべての画面を検索対象にする
問題 システムが何を重要視すべきか判断できなくなり、質の低い候補が表示されます。
// ❌ BAD: Settings screen marked for prediction
activity.isEligibleForPrediction = true // Don't predict Settings!
修正方法 ユーザーが再びアクセスしたいと思う画面(コンテンツであり、装飾的な UI ではないもの)のみを対象にします。
// ✅ GOOD: Mark content screens only
if order != nil {
activity.isEligibleForPrediction = true
}
❌ アンチパターン 6:NSUserActivity と App Intents の連携を忘れる
問題 NSUserActivity と App Intents が分断されたままになり、連携の機会を逃します。
修正方法 appEntityIdentifier を使って両者を連携させます。
// ✅ GOOD: Connect activity to App Intent entity
activity.appEntityIdentifier = order.id.uuidString
コードレビューのチェックリスト
見つけやすさの実装をレビューする際は、次の点を確認してください。
App Intents:
- インテントのタイトルが明確で、行動を促すものになっている
- 説明からインテントの動作がわかる
- パラメーターの要約が自然な言い回しになっている
- 公開インテントで
isDiscoverable = trueになっている
App Shortcuts:
- AppShortcutsProvider が実装されている
- 提案するフレーズに
\(.applicationName)が含まれている - フレーズが短く、行動を促すものになっている
- ShortcutTileColor がアプリのブランドに合っている
- 中核となるショートカットが 3~5 個定義されている(多すぎない)
Core Spotlight:
- 価値のあるコンテンツのみがインデックス化されている(すべてではない)
- 一意の識別子が安定しており、永続的である
- ドメイン識別子で関連コンテンツがグループ化されている
- 属性にタイトル、説明、キーワードが含まれている
- コンテンツを削除した際の削除処理がある
NSUserActivity:
- 価値の高い画面のみが対象になっている
- 画面の表示時に
becomeCurrent()が呼び出される - 画面の非表示時に
resignCurrent()が呼び出される -
appEntityIdentifierが App Intent エンティティと連携している -
contentAttributeSetで豊富なメタデータを提供している
ユーザーへの案内:
- SiriTipView を使ってショートカットを紹介している
- 設定またはヘルプで ShortcutsLink を利用できる
- オンボーディングで Siri/Spotlight のサポートに触れている
テスト:
- ショートカットがショートカットアプリに表示される
- Siri が提案したフレーズを認識する
- Spotlight にアプリのコンテンツが表示される
- アクティビティの継続が動作する(Spotlight の結果をタップ)
関連スキル
- app-intents-ref — App Intents API の完全なリファレンス
- app-shortcuts-ref — App Shortcuts の実装ガイド
- core-spotlight-ref — Core Spotlight と NSUserActivity のリファレンス
リソース
WWDC:260、275、2022-10170
ドキュメント:/appintents/making-your-app-s-functionality-available-to-siri、/corespotlight
スキル:axiom-app-intents-ref、axiom-app-shortcuts-ref、axiom-core-spotlight-ref
覚えておいてください 見つけやすさは単一の API ではなく、戦略です。App Intents、App Shortcuts、Core Spotlight、NSUserActivity を通じて、システムにメタデータを提供します。コンテキストやユーザーの行動に基づいて iOS がアプリを表示するタイミングを判断できるようにします。