mcpField
A
axiom-app-discoverability

axiom-app-discoverability

github.com/charleswiltgen開発1.2k175 回インストール今日更新
リポジトリ

axiom-app-discoverability について

iOS開発者向けのアプリの見つけやすさに関する計画・診断スキルです。App Intents、App Shortcuts、Core Spotlight、NSUserActivityを組み合わせ、アプリの操作やコンテンツをシステム検索や提案に表示する方法を説明します。

主な機能

  1. 01複数APIによる発見性の方針策定
  2. 02App Intentsによる操作の公開
  3. 03App Shortcutsと提案フレーズの設定
  4. 04Core Spotlightによるコンテンツ索引
  5. 05NSUserActivityによる画面の登録
  6. 06Spotlight索引の問題確認

活用シーン

  1. 01SiriやSpotlightにアプリ操作を表示
  2. 02システム検索でアプリのコンテンツを発見可能にする
  3. 03重要な画面の検索と予測を有効化
  4. 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. 見つけやすさ — 見つけやすくする(ステップ 1~5)
  2. 有用性 — 使う価値を高める(優れたインテントを設計する)
  3. プロモーション — 利用可能なショートカットをユーザーに知らせる(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「コーヒーを注文」
アクションをすぐに利用可能にするAppShortcutSpotlight に即座に表示
アプリのすべてのコンテンツをインデックス化する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 に表示されない場合:

確認チェックリスト
  1. インデックス化が成功したか確認する — indexSearchableItems に完了ハンドラーのログ出力を追加する
  2. 処理を待つ — Spotlight が新しいアイテムを処理するまで 10~30 秒かかる場合があります
  3. 正確なタイトルで検索する — 最初は Spotlight が部分一致のキーワードを検出できない場合があります
  4. 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 がアプリを表示するタイミングを判断できるようにします。

コメント