Loading...
Loading...
Use when making app surface in Spotlight search, Siri suggestions, or system experiences - covers the 6-step strategy combining App Intents, App Shortcuts, Core Spotlight, and NSUserActivity to feed the system metadata for iOS 16+
npx skill4agent add charleswiltgen/axiom axiom-app-discoverabilitystruct 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")
}
}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
}suggestedPhraseimport 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)")
}
}
}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
}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"// Promote your shortcuts in-app
SiriTipView(intent: OrderCoffeeIntent(), isVisible: $showTip)
.siriTipViewStyle(.dark)┌─ 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 │
└──────────────────────────────────────────────────────────────────┘| Use Case | API | Example |
|---|---|---|
| Expose action to Siri/Shortcuts | | "Order coffee" |
| Make action available instantly | | Appear in Spotlight immediately |
| Index all app content | | All coffee orders searchable |
| Mark current screen important | | User viewing order detail |
| Auto-generate Find actions | | "Find orders where..." |
| Link screen to App Intent | | Deep link to specific order |
// Your app's most valuable actions
struct OrderCoffeeIntent: AppIntent { /* ... */ }
struct ReorderLastIntent: AppIntent { /* ... */ }
struct ViewOrdersIntent: AppIntent { /* ... */ }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
}
}// 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)
}// In your detail view controllers/views
let activity = NSUserActivity(activityType: "com.app.viewOrder")
activity.isEligibleForSearch = true
activity.becomeCurrent()
self.userActivity = activity// ❌ 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)// ❌ 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")SiriTipView// Show tip after user places order
SiriTipView(intent: ReorderLastIntent(), isVisible: $showTip)// ❌ BAD: Settings screen marked for prediction
activity.isEligibleForPrediction = true // Don't predict Settings!// ✅ GOOD: Mark content screens only
if order != nil {
activity.isEligibleForPrediction = true
}appEntityIdentifier// ✅ GOOD: Connect activity to App Intent entity
activity.appEntityIdentifier = order.id.uuidStringisDiscoverable = true\(.applicationName)becomeCurrent()resignCurrent()appEntityIdentifiercontentAttributeSet