Loading...
Loading...
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+
npx skill4agent add charleswiltgen/axiom axiom-app-shortcuts-ref| 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 |
AppShortcutsProviderstruct 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 }
}AppIntentAppShortcut(
intent: StartMeditationIntent(),
phrases: [
"Start meditation in \(.applicationName)",
"Begin mindfulness with \(.applicationName)"
],
shortTitle: "Meditate",
systemImageName: "figure.mind.and.body"
)intentphrasesshortTitlesystemImageName\(.applicationName)phrases: [
"Start meditation in \(.applicationName)",
"Meditate with \(.applicationName)"
]@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
AppShortcut(intent: OrderIntent(), /* ... */)
AppShortcut(intent: ReorderIntent(), /* ... */)
if UserDefaults.standard.bool(forKey: "premiumUser") {
AppShortcut(intent: CustomizeIntent(), /* ... */)
}
}AppShortcut(
intent: StartWorkoutIntent(),
phrases: [
"Start workout in \(.applicationName)",
"Begin exercise with \(.applicationName)",
"Work out in \(.applicationName)"
],
shortTitle: "Start Workout",
systemImageName: "figure.run"
)// 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"
)
}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"
}
}
}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)
}
}
}.automatic.light.darkimport AppIntents
import SwiftUI
struct SettingsView: View {
var body: some View {
List {
Section("Siri & Shortcuts") {
ShortcutsLink()
// Displays "Shortcuts" with standard link styling
}
}
}
}struct CoffeeAppShortcuts: AppShortcutsProvider {
static var shortcutTileColor: ShortcutTileColor = .tangerine
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
// ...
}
}| Color | Use Case |
|---|---|
| Default, professional |
| Energy, food/beverage |
| Creative, meditation |
| Health, wellness |
| Urgent, important |
| Lifestyle, social |
| Business, finance |
| Productivity, notes |
| Fitness, outdoor |
.blue.grape.grayBlue.grayBrown.grayGreen.lightBlue.lime.navy.orange.pink.purple.red.tangerine.teal.yellowstruct 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()
}
}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"
]
}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"
}
}
}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()
}
}| 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 |
isDiscoverable\(.applicationName)#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
}
}
#endifphrases: [
"I would like to order a coffee from \(.applicationName) please"
]phrases: [
"Order coffee in \(.applicationName)",
"Get coffee from \(.applicationName)"
]\(.applicationName)// Bad: Overwhelming
AppShortcut for every possible combination// Good: Focused on common tasks
AppShortcut(intent: OrderIntent(), /* ... */)
AppShortcut(intent: ReorderIntent(), /* ... */)
AppShortcut(intent: ViewOrdersIntent(), /* ... */)// 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), /* ... */)
}
}// Good: Generic + common specific cases
AppShortcut(intent: OrderIntent(), /* ... */) // Generic
AppShortcut(intent: OrderIntent(type: .latte, size: .medium), /* ... */) // Usual
AppShortcut(intent: OrderIntent(type: .espresso, size: .small), /* ... */) // QuickshortTitle: "Order Coffee from Coffee App"shortTitle: "Order"// Not supported
shortImage: UIImage(named: "custom")systemImageName: "cup.and.saucer.fill"