This snapshot establishes the camera-to-result recognition flow and related tests while checking in the project skill/docs assets required for the configured local tooling.
829 lines
22 KiB
Markdown
829 lines
22 KiB
Markdown
---
|
||
name: axiom-app-shortcuts-ref
|
||
description: Use when implementing App Shortcuts for instant Siri/Spotlight availability, configuring AppShortcutsProvider, adding suggested phrases, or debugging shortcuts not appearing - covers complete App Shortcuts API for iOS 16+
|
||
license: MIT
|
||
metadata:
|
||
version: "1.0.0"
|
||
---
|
||
|
||
# App Shortcuts Reference
|
||
|
||
## Overview
|
||
|
||
Comprehensive guide to App Shortcuts framework for making your app's actions instantly available in Siri, Spotlight, Action Button, Control Center, and other system experiences. App Shortcuts are pre-configured App Intents that work immediately after app install—no user setup required.
|
||
|
||
**Key distinction** App Intents are the actions; App Shortcuts are the pre-configured "surface" that makes those actions instantly discoverable system-wide.
|
||
|
||
---
|
||
|
||
## When to Use This Skill
|
||
|
||
Use this skill when:
|
||
- Implementing AppShortcutsProvider for your app
|
||
- Adding suggested phrases for Siri invocation
|
||
- Configuring instant Spotlight availability
|
||
- Creating parameterized shortcuts (skip Siri clarification)
|
||
- Using NegativeAppShortcutPhrase to prevent false positives (iOS 17+)
|
||
- Promoting shortcuts with SiriTipView
|
||
- Updating shortcuts dynamically with updateAppShortcutParameters()
|
||
- Debugging shortcuts not appearing in Shortcuts app or Spotlight
|
||
- Choosing between App Intents and App Shortcuts
|
||
|
||
Do NOT use this skill for:
|
||
- General App Intents implementation (use app-intents-ref)
|
||
- Core Spotlight indexing (use core-spotlight-ref)
|
||
- Overall discoverability strategy (use app-discoverability)
|
||
|
||
---
|
||
|
||
## Related Skills
|
||
|
||
- **app-intents-ref** — Complete App Intents implementation reference
|
||
- **app-discoverability** — Strategic guide for making apps discoverable
|
||
- **core-spotlight-ref** — Core Spotlight and NSUserActivity integration
|
||
|
||
---
|
||
|
||
## App Shortcuts vs App Intents
|
||
|
||
| Aspect | App Intent | App Shortcut |
|
||
|--------|-----------|--------------|
|
||
| **Discovery** | Must be found in Shortcuts app | Instantly available after install |
|
||
| **Configuration** | User configures in Shortcuts | Pre-configured by developer |
|
||
| **Siri activation** | Requires custom phrase setup | Works immediately with provided phrases |
|
||
| **Spotlight** | Requires donation or IndexedEntity | Appears automatically |
|
||
| **Action button** | Not directly accessible | Can be assigned immediately |
|
||
| **Setup time** | Minutes per user | Zero |
|
||
|
||
**When to use App Shortcuts** Every app should provide App Shortcuts for core functionality. They dramatically improve discoverability with zero user effort.
|
||
|
||
---
|
||
|
||
## Core Concepts
|
||
|
||
### AppShortcutsProvider Protocol
|
||
|
||
**Required conformance** Your app must have exactly one type conforming to `AppShortcutsProvider`.
|
||
|
||
```swift
|
||
struct MyAppShortcuts: AppShortcutsProvider {
|
||
// Required: Define your shortcuts
|
||
@AppShortcutsBuilder
|
||
static var appShortcuts: [AppShortcut] { get }
|
||
|
||
// Optional: Branding color
|
||
static var shortcutTileColor: ShortcutTileColor { get }
|
||
|
||
// Optional: Dynamic updates
|
||
static func updateAppShortcutParameters()
|
||
|
||
// Optional: Negative phrases (iOS 17+)
|
||
static var negativePhrases: [NegativeAppShortcutPhrase] { get }
|
||
}
|
||
```
|
||
|
||
**Platform support** iOS 16+, iPadOS 16+, macOS 13+, tvOS 16+, watchOS 9+
|
||
|
||
---
|
||
|
||
### AppShortcut Structure
|
||
|
||
Associates an `AppIntent` with spoken phrases and metadata.
|
||
|
||
```swift
|
||
AppShortcut(
|
||
intent: StartMeditationIntent(),
|
||
phrases: [
|
||
"Start meditation in \(.applicationName)",
|
||
"Begin mindfulness with \(.applicationName)"
|
||
],
|
||
shortTitle: "Meditate",
|
||
systemImageName: "figure.mind.and.body"
|
||
)
|
||
```
|
||
|
||
**Components:**
|
||
- `intent` — The App Intent to execute
|
||
- `phrases` — Spoken/typed phrases for Siri/Spotlight
|
||
- `shortTitle` — Short label for Shortcuts app tiles
|
||
- `systemImageName` — SF Symbol for visual representation
|
||
|
||
---
|
||
|
||
### AppShortcutPhrase (Suggested Phrases)
|
||
|
||
**String interpolation** Phrases use `\(.applicationName)` to dynamically include your app's name.
|
||
|
||
```swift
|
||
phrases: [
|
||
"Start meditation in \(.applicationName)",
|
||
"Meditate with \(.applicationName)"
|
||
]
|
||
```
|
||
|
||
**User sees in Siri/Spotlight:**
|
||
- "Start meditation in Calm"
|
||
- "Meditate with Calm"
|
||
|
||
**Why this matters** The system uses these exact phrases to trigger your intent via Siri and show suggestions in Spotlight.
|
||
|
||
---
|
||
|
||
### @AppShortcutsBuilder
|
||
|
||
Result builder for defining shortcuts array.
|
||
|
||
```swift
|
||
@AppShortcutsBuilder
|
||
static var appShortcuts: [AppShortcut] {
|
||
AppShortcut(intent: OrderIntent(), /* ... */)
|
||
AppShortcut(intent: ReorderIntent(), /* ... */)
|
||
|
||
if UserDefaults.standard.bool(forKey: "premiumUser") {
|
||
AppShortcut(intent: CustomizeIntent(), /* ... */)
|
||
}
|
||
}
|
||
```
|
||
|
||
**Result builder features:**
|
||
- Conditional shortcuts (if/else)
|
||
- Loop-generated shortcuts (for-in)
|
||
- Inline array construction
|
||
|
||
---
|
||
|
||
## Phrase Template Patterns
|
||
|
||
### Basic Phrases (No Parameters)
|
||
|
||
```swift
|
||
AppShortcut(
|
||
intent: StartWorkoutIntent(),
|
||
phrases: [
|
||
"Start workout in \(.applicationName)",
|
||
"Begin exercise with \(.applicationName)",
|
||
"Work out in \(.applicationName)"
|
||
],
|
||
shortTitle: "Start Workout",
|
||
systemImageName: "figure.run"
|
||
)
|
||
```
|
||
|
||
**Benefits:**
|
||
- Simple, discoverable
|
||
- Works for all users
|
||
- No parameter ambiguity
|
||
|
||
**Use when** Intent has no required parameters or parameters have defaults.
|
||
|
||
---
|
||
|
||
### Parameterized Phrases (Skip Clarification)
|
||
|
||
Pre-configure intents with specific parameter values to skip Siri's clarification step.
|
||
|
||
```swift
|
||
// Intent with parameters
|
||
struct StartMeditationIntent: AppIntent {
|
||
static var title: LocalizedStringResource = "Start Meditation"
|
||
|
||
@Parameter(title: "Type")
|
||
var meditationType: MeditationType?
|
||
|
||
@Parameter(title: "Duration")
|
||
var duration: Int?
|
||
}
|
||
|
||
// Shortcuts with different parameter combinations
|
||
@AppShortcutsBuilder
|
||
static var appShortcuts: [AppShortcut] {
|
||
// Generic version (will ask for parameters)
|
||
AppShortcut(
|
||
intent: StartMeditationIntent(),
|
||
phrases: ["Start meditation in \(.applicationName)"],
|
||
shortTitle: "Meditate",
|
||
systemImageName: "figure.mind.and.body"
|
||
)
|
||
|
||
// Specific versions (skip parameter step)
|
||
AppShortcut(
|
||
intent: StartMeditationIntent(
|
||
meditationType: .mindfulness,
|
||
duration: 10
|
||
),
|
||
phrases: [
|
||
"Start quick mindfulness in \(.applicationName)",
|
||
"10 minute mindfulness in \(.applicationName)"
|
||
],
|
||
shortTitle: "Quick Mindfulness",
|
||
systemImageName: "brain.head.profile"
|
||
)
|
||
|
||
AppShortcut(
|
||
intent: StartMeditationIntent(
|
||
meditationType: .sleep,
|
||
duration: 20
|
||
),
|
||
phrases: [
|
||
"Start sleep meditation in \(.applicationName)"
|
||
],
|
||
shortTitle: "Sleep Meditation",
|
||
systemImageName: "moon.stars.fill"
|
||
)
|
||
}
|
||
```
|
||
|
||
**Benefits:**
|
||
- One-phrase completion (no follow-up questions)
|
||
- Better user experience for common use cases
|
||
- Spotlight shows specific shortcuts
|
||
|
||
**Trade-off** More shortcuts = more visual clutter in Shortcuts app. Balance common cases (3-5 shortcuts) vs flexibility (generic shortcut with parameters).
|
||
|
||
---
|
||
|
||
## NegativeAppShortcutPhrase (iOS 17+)
|
||
|
||
Train the system to NOT invoke your app for certain phrases.
|
||
|
||
```swift
|
||
struct MeditationAppShortcuts: AppShortcutsProvider {
|
||
@AppShortcutsBuilder
|
||
static var appShortcuts: [AppShortcut] {
|
||
AppShortcut(
|
||
intent: StartMeditationIntent(),
|
||
phrases: ["Start meditation in \(.applicationName)"],
|
||
shortTitle: "Meditate",
|
||
systemImageName: "figure.mind.and.body"
|
||
)
|
||
}
|
||
|
||
// Prevent false positives
|
||
static var negativePhrases: [NegativeAppShortcutPhrase] {
|
||
NegativeAppShortcutPhrases {
|
||
"Stop meditation"
|
||
"Cancel meditation"
|
||
"End session"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**When to use:**
|
||
- Phrases that sound similar to your shortcuts but mean the opposite
|
||
- Common phrases users might say that shouldn't trigger your app
|
||
- Disambiguation when multiple apps have similar capabilities
|
||
|
||
**Platform** iOS 17.0+, iPadOS 17.0+, macOS 14.0+, tvOS 17.0+, watchOS 10.0+
|
||
|
||
---
|
||
|
||
## Discovery UI Components
|
||
|
||
### SiriTipView — Promote Shortcuts In-App
|
||
|
||
Display the spoken phrase for a shortcut directly in your app's UI.
|
||
|
||
```swift
|
||
import AppIntents
|
||
import SwiftUI
|
||
|
||
struct OrderConfirmationView: View {
|
||
@State private var showSiriTip = true
|
||
|
||
var body: some View {
|
||
VStack {
|
||
Text("Order confirmed!")
|
||
|
||
// Show Siri tip after successful order
|
||
SiriTipView(intent: ReorderIntent(), isVisible: $showSiriTip)
|
||
.siriTipViewStyle(.dark)
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**Requirements:**
|
||
- Intent must be used in an AppShortcut (otherwise shows empty view)
|
||
- isVisible binding controls display state
|
||
|
||
**Styles:**
|
||
- `.automatic` — Adapts to environment
|
||
- `.light` — Light background
|
||
- `.dark` — Dark background
|
||
|
||
**Best practice** Show after users complete actions, suggesting easier ways next time.
|
||
|
||
---
|
||
|
||
### ShortcutsLink — Link to Shortcuts App
|
||
|
||
Opens your app's page in the Shortcuts app, listing all available shortcuts.
|
||
|
||
```swift
|
||
import AppIntents
|
||
import SwiftUI
|
||
|
||
struct SettingsView: View {
|
||
var body: some View {
|
||
List {
|
||
Section("Siri & Shortcuts") {
|
||
ShortcutsLink()
|
||
// Displays "Shortcuts" with standard link styling
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**When to use:**
|
||
- Settings screen
|
||
- Help/Support section
|
||
- Onboarding flow
|
||
|
||
**Benefits** Single tap takes users to see all your app's shortcuts, with suggested phrases visible.
|
||
|
||
---
|
||
|
||
### ShortcutTileColor — Branding
|
||
|
||
Set the color for your shortcuts in the Shortcuts app.
|
||
|
||
```swift
|
||
struct CoffeeAppShortcuts: AppShortcutsProvider {
|
||
static var shortcutTileColor: ShortcutTileColor = .tangerine
|
||
|
||
@AppShortcutsBuilder
|
||
static var appShortcuts: [AppShortcut] {
|
||
// ...
|
||
}
|
||
}
|
||
```
|
||
|
||
**Available colors:**
|
||
| Color | Use Case |
|
||
|-------|----------|
|
||
| `.blue` | Default, professional |
|
||
| `.tangerine` | Energy, food/beverage |
|
||
| `.purple` | Creative, meditation |
|
||
| `.teal` | Health, wellness |
|
||
| `.red` | Urgent, important |
|
||
| `.pink` | Lifestyle, social |
|
||
| `.navy` | Business, finance |
|
||
| `.yellow` | Productivity, notes |
|
||
| `.lime` | Fitness, outdoor |
|
||
|
||
Full list: `.blue`, `.grape`, `.grayBlue`, `.grayBrown`, `.grayGreen`, `.lightBlue`, `.lime`, `.navy`, `.orange`, `.pink`, `.purple`, `.red`, `.tangerine`, `.teal`, `.yellow`
|
||
|
||
**Choose color** that matches your app icon or brand identity.
|
||
|
||
---
|
||
|
||
## Dynamic Updates
|
||
|
||
### updateAppShortcutParameters()
|
||
|
||
Call when parameter options change to refresh stored shortcuts.
|
||
|
||
```swift
|
||
struct MeditationAppShortcuts: AppShortcutsProvider {
|
||
@AppShortcutsBuilder
|
||
static var appShortcuts: [AppShortcut] {
|
||
// Shortcuts can reference dynamic data
|
||
for session in MeditationData.favoriteSessions {
|
||
AppShortcut(
|
||
intent: StartSessionIntent(session: session),
|
||
phrases: ["Start \(session.name) in \(.applicationName)"],
|
||
shortTitle: session.name,
|
||
systemImageName: session.iconName
|
||
)
|
||
}
|
||
}
|
||
|
||
static func updateAppShortcutParameters() {
|
||
// Called automatically when needed
|
||
// Override only if you need custom behavior
|
||
}
|
||
}
|
||
|
||
// In your app, when data changes
|
||
extension MeditationData {
|
||
func markAsFavorite(_ session: Session) {
|
||
favoriteSessions.append(session)
|
||
|
||
// Update App Shortcuts to reflect new data
|
||
MeditationAppShortcuts.updateAppShortcutParameters()
|
||
}
|
||
}
|
||
```
|
||
|
||
**When to call:**
|
||
- User adds/removes favorites
|
||
- Available options change
|
||
- App data structure updates
|
||
|
||
**Automatic invocation** The system calls this periodically, but you can force updates when you know data changed.
|
||
|
||
---
|
||
|
||
## Complete Implementation Example
|
||
|
||
### Step 1: Define App Intents
|
||
|
||
```swift
|
||
import 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
|
||
|
||
@Parameter(title: "Customizations")
|
||
var customizations: String?
|
||
|
||
static var parameterSummary: some ParameterSummary {
|
||
Summary("Order \(\.$size) \(\.$coffeeType)") {
|
||
\.$customizations
|
||
}
|
||
}
|
||
|
||
func perform() async throws -> some IntentResult {
|
||
let order = try await CoffeeService.shared.order(
|
||
type: coffeeType,
|
||
size: size,
|
||
customizations: customizations
|
||
)
|
||
|
||
return .result(
|
||
value: order,
|
||
dialog: "Your \(size) \(coffeeType) is ordered for pickup"
|
||
)
|
||
}
|
||
}
|
||
|
||
struct ReorderLastIntent: AppIntent {
|
||
static var title: LocalizedStringResource = "Reorder Last Coffee"
|
||
static var description = IntentDescription("Reorders your most recent coffee")
|
||
static var openAppWhenRun: Bool = false
|
||
|
||
func perform() async throws -> some IntentResult {
|
||
guard let lastOrder = try await CoffeeService.shared.lastOrder() else {
|
||
throw CoffeeError.noRecentOrders
|
||
}
|
||
|
||
try await CoffeeService.shared.reorder(lastOrder)
|
||
|
||
return .result(
|
||
dialog: "Reordering your \(lastOrder.coffeeName)"
|
||
)
|
||
}
|
||
}
|
||
|
||
enum CoffeeType: String, AppEnum {
|
||
case latte, cappuccino, americano, espresso
|
||
|
||
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Coffee"
|
||
static var caseDisplayRepresentations: [CoffeeType: DisplayRepresentation] = [
|
||
.latte: "Latte",
|
||
.cappuccino: "Cappuccino",
|
||
.americano: "Americano",
|
||
.espresso: "Espresso"
|
||
]
|
||
}
|
||
|
||
enum CoffeeSize: String, AppEnum {
|
||
case small, medium, large
|
||
|
||
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Size"
|
||
static var caseDisplayRepresentations: [CoffeeSize: DisplayRepresentation] = [
|
||
.small: "Small",
|
||
.medium: "Medium",
|
||
.large: "Large"
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Step 2: Create AppShortcutsProvider
|
||
|
||
```swift
|
||
import AppIntents
|
||
|
||
struct CoffeeAppShortcuts: AppShortcutsProvider {
|
||
|
||
@AppShortcutsBuilder
|
||
static var appShortcuts: [AppShortcut] {
|
||
// Generic order (will ask for parameters)
|
||
AppShortcut(
|
||
intent: OrderCoffeeIntent(),
|
||
phrases: [
|
||
"Order coffee in \(.applicationName)",
|
||
"Get coffee from \(.applicationName)"
|
||
],
|
||
shortTitle: "Order",
|
||
systemImageName: "cup.and.saucer.fill"
|
||
)
|
||
|
||
// Common specific orders (skip parameter step)
|
||
AppShortcut(
|
||
intent: OrderCoffeeIntent(
|
||
coffeeType: .latte,
|
||
size: .medium
|
||
),
|
||
phrases: [
|
||
"Order my usual from \(.applicationName)",
|
||
"Get my regular coffee from \(.applicationName)"
|
||
],
|
||
shortTitle: "Usual Order",
|
||
systemImageName: "star.fill"
|
||
)
|
||
|
||
// Reorder last
|
||
AppShortcut(
|
||
intent: ReorderLastIntent(),
|
||
phrases: [
|
||
"Reorder coffee from \(.applicationName)",
|
||
"Order again from \(.applicationName)"
|
||
],
|
||
shortTitle: "Reorder",
|
||
systemImageName: "arrow.clockwise"
|
||
)
|
||
}
|
||
|
||
// Branding
|
||
static var shortcutTileColor: ShortcutTileColor = .tangerine
|
||
|
||
// Prevent false positives (iOS 17+)
|
||
static var negativePhrases: [NegativeAppShortcutPhrase] {
|
||
NegativeAppShortcutPhrases {
|
||
"Cancel coffee order"
|
||
"Stop coffee"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Step 3: Promote in UI
|
||
|
||
```swift
|
||
import SwiftUI
|
||
import AppIntents
|
||
|
||
struct OrderConfirmationView: View {
|
||
@State private var showReorderTip = true
|
||
|
||
var body: some View {
|
||
VStack(spacing: 20) {
|
||
Image(systemName: "checkmark.circle.fill")
|
||
.font(.system(size: 60))
|
||
.foregroundColor(.green)
|
||
|
||
Text("Order Placed!")
|
||
.font(.title)
|
||
|
||
Text("Your coffee will be ready in 10 minutes")
|
||
.foregroundColor(.secondary)
|
||
|
||
// Promote reorder shortcut
|
||
if showReorderTip {
|
||
SiriTipView(intent: ReorderLastIntent(), isVisible: $showReorderTip)
|
||
.siriTipViewStyle(.dark)
|
||
.padding(.top)
|
||
}
|
||
|
||
// Link to see all shortcuts
|
||
Section {
|
||
ShortcutsLink()
|
||
} header: {
|
||
Text("See all available shortcuts")
|
||
.font(.caption)
|
||
}
|
||
}
|
||
.padding()
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Where App Shortcuts Appear
|
||
|
||
Once implemented, your App Shortcuts are available in:
|
||
|
||
| Location | User Experience |
|
||
|----------|-----------------|
|
||
| **Siri** | Voice activation with provided phrases |
|
||
| **Spotlight** | Search for action or phrase → Instant execution |
|
||
| **Shortcuts app** | Pre-populated shortcuts, zero configuration |
|
||
| **Action Button** (iPhone 15 Pro) | Assignable to hardware button |
|
||
| **Apple Watch Ultra** | Action Button assignment |
|
||
| **Control Center** | Add shortcuts as controls |
|
||
| **Lock Screen widgets** | Quick actions without unlocking |
|
||
| **Apple Pencil Pro** | Squeeze gesture assignment |
|
||
| **Focus Filters** | Contextual filtering |
|
||
|
||
**Instant availability** All locations work immediately after app install. No user setup required.
|
||
|
||
---
|
||
|
||
## Testing & Debugging
|
||
|
||
### Verify Shortcuts Appear in Shortcuts App
|
||
|
||
1. Build and run your app on device
|
||
2. Open Shortcuts app
|
||
3. Tap "+" to create new shortcut
|
||
4. Search for your app name
|
||
5. Verify shortcuts appear with correct titles and icons
|
||
|
||
**If shortcuts don't appear:**
|
||
- Ensure AppShortcutsProvider is in your main app target
|
||
- Check that `isDiscoverable` is true for the AppIntents (default)
|
||
- Rebuild and reinstall app
|
||
- Check console for AppShortcuts errors
|
||
|
||
---
|
||
|
||
### Test Siri Invocation
|
||
|
||
1. Invoke Siri
|
||
2. Say one of your suggested phrases
|
||
3. Verify Siri executes the intent
|
||
|
||
**Example**:
|
||
- You: "Order coffee in CoffeeApp"
|
||
- Siri: "What size and type?"
|
||
- You: "Medium latte"
|
||
- Siri: "Your medium latte is ordered for pickup"
|
||
|
||
**If Siri doesn't recognize phrase:**
|
||
- Check phrase includes `\(.applicationName)`
|
||
- Verify phrase is in appShortcuts array
|
||
- Try simpler phrases (3-6 words ideal)
|
||
- Avoid complex grammar or rare words
|
||
|
||
---
|
||
|
||
### Test Spotlight Discovery
|
||
|
||
1. Swipe down to open Spotlight
|
||
2. Type your app name or shortcut phrase
|
||
3. Verify shortcut appears in results
|
||
4. Tap to execute
|
||
|
||
**If shortcut doesn't appear in Spotlight:**
|
||
- Wait a few minutes (indexing delay)
|
||
- Restart device
|
||
- Check System Settings → Siri & Search → [Your App] → Show App in Search
|
||
|
||
---
|
||
|
||
### Debug with Console Logs
|
||
|
||
```swift
|
||
#if DEBUG
|
||
struct CoffeeAppShortcuts: AppShortcutsProvider {
|
||
@AppShortcutsBuilder
|
||
static var appShortcuts: [AppShortcut] {
|
||
let shortcuts = [
|
||
AppShortcut(/* ... */),
|
||
// ...
|
||
]
|
||
|
||
print("📱 Registered \(shortcuts.count) App Shortcuts")
|
||
shortcuts.forEach { shortcut in
|
||
print(" - \(shortcut.shortTitle)")
|
||
}
|
||
|
||
return shortcuts
|
||
}
|
||
}
|
||
#endif
|
||
```
|
||
|
||
**Check Xcode console** after app launch to verify shortcuts are registered.
|
||
|
||
---
|
||
|
||
## Best Practices
|
||
|
||
### 1. Phrase Design
|
||
|
||
#### ❌ DON'T: Long, complex phrases
|
||
```swift
|
||
phrases: [
|
||
"I would like to order a coffee from \(.applicationName) please"
|
||
]
|
||
```
|
||
|
||
#### ✅ DO: Short, natural phrases
|
||
```swift
|
||
phrases: [
|
||
"Order coffee in \(.applicationName)",
|
||
"Get coffee from \(.applicationName)"
|
||
]
|
||
```
|
||
|
||
**Guidelines:**
|
||
- 3-6 words ideal
|
||
- Start with verb (Order, Start, Get, Show)
|
||
- Include `\(.applicationName)` for disambiguation
|
||
- Use natural language users would actually say
|
||
|
||
---
|
||
|
||
### 2. Shortcut Quantity
|
||
|
||
#### ❌ DON'T: Provide 20+ shortcuts
|
||
```swift
|
||
// Bad: Overwhelming
|
||
AppShortcut for every possible combination
|
||
```
|
||
|
||
#### ✅ DO: Focus on 3-5 core actions
|
||
```swift
|
||
// Good: Focused on common tasks
|
||
AppShortcut(intent: OrderIntent(), /* ... */)
|
||
AppShortcut(intent: ReorderIntent(), /* ... */)
|
||
AppShortcut(intent: ViewOrdersIntent(), /* ... */)
|
||
```
|
||
|
||
**Why** Too many shortcuts creates clutter. Focus on high-value, frequently-used actions.
|
||
|
||
---
|
||
|
||
### 3. Parameter Combinations
|
||
|
||
#### ❌ DON'T: Parameterize every variant
|
||
```swift
|
||
// Bad: Creates 12 shortcuts (3 sizes × 4 types)
|
||
for size in CoffeeSize.allCases {
|
||
for type in CoffeeType.allCases {
|
||
AppShortcut(intent: OrderIntent(type: type, size: size), /* ... */)
|
||
}
|
||
}
|
||
```
|
||
|
||
#### ✅ DO: Provide generic + top 2-3 common cases
|
||
```swift
|
||
// Good: Generic + common specific cases
|
||
AppShortcut(intent: OrderIntent(), /* ... */) // Generic
|
||
AppShortcut(intent: OrderIntent(type: .latte, size: .medium), /* ... */) // Usual
|
||
AppShortcut(intent: OrderIntent(type: .espresso, size: .small), /* ... */) // Quick
|
||
```
|
||
|
||
---
|
||
|
||
### 4. Short Titles
|
||
|
||
#### ❌ DON'T: Verbose or redundant
|
||
```swift
|
||
shortTitle: "Order Coffee from Coffee App"
|
||
```
|
||
|
||
#### ✅ DO: Concise and clear
|
||
```swift
|
||
shortTitle: "Order"
|
||
```
|
||
|
||
**Context** App name already appears in Shortcuts app, so no need to repeat.
|
||
|
||
---
|
||
|
||
### 5. System Images
|
||
|
||
#### ❌ DON'T: Use custom images
|
||
```swift
|
||
// Not supported
|
||
shortImage: UIImage(named: "custom")
|
||
```
|
||
|
||
#### ✅ DO: Use SF Symbols
|
||
```swift
|
||
systemImageName: "cup.and.saucer.fill"
|
||
```
|
||
|
||
**Why** SF Symbols scale properly, support dark mode, and integrate with system UI.
|
||
|
||
---
|
||
|
||
## Resources
|
||
|
||
**WWDC**: 2022-10170, 2022-10169, 260
|
||
|
||
**Docs**: /appintents/appshortcutsprovider, /appintents/appshortcut, /appintents/app-shortcuts
|
||
|
||
**Skills**: axiom-app-intents-ref, axiom-app-discoverability, axiom-core-spotlight-ref
|
||
|
||
---
|
||
|
||
**Remember** App Shortcuts make your app's functionality instantly available across iOS. Define 3-5 core shortcuts with natural phrases, promote them in your UI with SiriTipView, and users can invoke them immediately via Siri, Spotlight, Action Button, and more.
|