From ios-frameworks
Use when implementing, reviewing, or debugging App Intents, App Shortcuts, Siri, AppEntity, EntityQuery, parameter summaries, Spotlight intents, Focus filters, Control Center, or Action Button integrations.
How this skill is triggered — by the user, by Claude, or both
Slash command
/ios-frameworks:beepus-maximus-ios-app-intentsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Opinionated guide for exposing app actions to Siri, Shortcuts, Spotlight, and system surfaces. Covers AppIntent, AppEntity, AppShortcutsProvider, and the decision framework for choosing the right integration point. iOS 26+ only -- no `@available` checks needed.
Opinionated guide for exposing app actions to Siri, Shortcuts, Spotlight, and system surfaces. Covers AppIntent, AppEntity, AppShortcutsProvider, and the decision framework for choosing the right integration point. iOS 26+ only -- no @available checks needed.
references/intent-patterns.mdWhat are you exposing?
|
+-- An ACTION (verb) the user can trigger?
| |
| +-- Should it work instantly after install?
| | +-- YES -> AppShortcutsProvider + AppIntent
| | +-- NO -> AppIntent alone (user finds in Shortcuts app)
| |
| +-- Does it need dynamic parameters?
| +-- YES -> AppEntity + EntityQuery
| +-- NO -> AppEnum for fixed choices, or raw types
|
+-- APP CONTENT the user can search or reference?
| |
| +-- Want automatic "Find X where..." in Shortcuts?
| | +-- YES -> IndexedEntity + @Property with indexingKey
| |
| +-- Want content in Spotlight search?
| | +-- YES -> CSSearchableItem (batch) + NSUserActivity (current screen)
| |
| +-- Need entity as an intent parameter?
| +-- YES -> AppEntity + EntityQuery
|
+-- A SYSTEM INTEGRATION?
|
+-- Interactive widget button -> AppIntent with WidgetKit
+-- Control Center control -> AppIntent with ControlWidget
+-- Focus filter -> SetFocusFilterIntent
+-- Lock Screen / Action Button -> AppShortcutsProvider
| Surface | Requires | Availability |
|---|---|---|
| Siri voice | AppShortcutsProvider with phrases | Instant after install |
| Spotlight actions | AppShortcutsProvider | Instant after install |
| Shortcuts app | AppIntent (discoverable) | User finds manually |
| Action Button | AppShortcutsProvider | User assigns in Settings |
| Control Center | AppIntent + ControlWidget | User adds control |
| Interactive widgets | AppIntent as button action | Widget configuration |
| Focus filters | SetFocusFilterIntent | System Settings |
| Apple Pencil Pro squeeze | AppShortcutsProvider | User assigns |
| Visual Intelligence | IntentValueQuery | Camera circle gesture |
AppIntent -- An executable action with parameters and a perform() method.
struct LogWeightIntent: AppIntent {
static var title: LocalizedStringResource = "Log Weight"
static var description: IntentDescription = "Records a body weight measurement"
@Parameter(title: "Weight")
var weight: Measurement<UnitMass>
static var parameterSummary: some ParameterSummary {
Summary("Log weight of \(\.$weight)")
}
func perform() async throws -> some IntentResult & ProvidesDialog {
try await WeightService.shared.log(weight)
return .result(dialog: "Logged \(weight.formatted())")
}
}
AppEntity -- An object users can pick as an intent parameter. Always separate from your data model.
struct WorkoutEntity: AppEntity {
var id: UUID
var name: String
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Workout"
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: "\(name)")
}
static var defaultQuery = WorkoutQuery()
}
AppEnum -- A fixed set of choices for intent parameters.
enum MealType: String, AppEnum {
case breakfast, lunch, dinner, snack
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Meal"
static var caseDisplayRepresentations: [MealType: DisplayRepresentation] = [
.breakfast: "Breakfast",
.lunch: "Lunch",
.dinner: "Dinner",
.snack: "Snack",
]
}
One type per app. Makes intents available in Siri, Spotlight, and Action Button with zero user setup.
struct MyAppShortcuts: AppShortcutsProvider {
static var shortcutTileColor: ShortcutTileColor = .teal
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: LogWeightIntent(),
phrases: [
"Log weight in \(.applicationName)",
"Record weight with \(.applicationName)",
],
shortTitle: "Log Weight",
systemImageName: "scalemass.fill"
)
}
}
Rules for phrases:
\(.applicationName) for disambiguation| Mistake | Fix |
|---|---|
| Generic title ("Do Thing", "Process") | Verb-noun title ("Log Weight", "Start Workout") |
| Technical error messages | User-friendly: "Sorry, that item is unavailable" |
suggestedEntities() returns thousands | Limit to 10-20 recent/relevant items |
| MainActor access in background intent | Use await MainActor.run { } or set openAppWhenRun = true |
| Making data model conform to AppEntity | Create a separate entity type with init(from:) |
| Parameter summary with technical phrasing | Natural language: "Send (.$message) to (.$contact)" |
Forgetting defaultQuery on AppEntity | Always provide -- intent resolution depends on it |
| Mistake | Fix |
|---|---|
| 20+ shortcuts | Focus on 3-5 core actions |
| Long phrases ("I would like to...") | Short phrases ("Order coffee in...") |
Missing \(.applicationName) in phrases | Always include for Siri disambiguation |
| Parameterizing every variant | Generic shortcut + 2-3 common specific cases |
| Verbose shortTitle | Concise -- app name already shown by system |
// Show after a completed action to teach the shortcut
SiriTipView(intent: ReorderIntent(), isVisible: $showTip)
.siriTipViewStyle(.automatic)
// Link to all shortcuts in settings
ShortcutsLink()
struct ToggleDarkModeIntent: AppIntent {
static var title: LocalizedStringResource = "Toggle Dark Mode"
static var openAppWhenRun: Bool = false
func perform() async throws -> some IntentResult & ProvidesDialog {
let newValue = await SettingsService.shared.toggleDarkMode()
return .result(dialog: "Dark mode \(newValue ? "on" : "off")")
}
}
struct OpenWorkoutIntent: AppIntent {
static var title: LocalizedStringResource = "Open Workout"
static var openAppWhenRun: Bool = true
@Parameter(title: "Workout")
var workout: WorkoutEntity
func perform() async throws -> some IntentResult {
await MainActor.run {
NavigationCoordinator.shared.show(workoutID: workout.id)
}
return .result()
}
}
func perform() async throws -> some IntentResult {
try await requestConfirmation(
result: .result(dialog: "Delete '\(item.title)'?"),
confirmationActionName: .init(stringLiteral: "Delete")
)
try await service.delete(item)
return .result(dialog: "Deleted")
}
Use supportedModes for granular control over foreground/background:
| Mode | Behavior |
|---|---|
.background | Runs entirely in background |
.foreground(.immediate) | App foregrounded before perform() |
.foreground(.dynamic) | Can request foreground mid-execution |
.foreground(.deferred) | Background first, foreground before completion |
struct SmartIntent: AppIntent {
static let supportedModes: IntentModes = [.background, .foreground(.dynamic)]
func perform() async throws -> some IntentResult & ProvidesDialog {
let result = try await computeResult()
if systemContext.currentMode.canContinueInForeground {
try? await continueInForeground(alwaysConfirm: false)
await navigator.show(result)
}
return .result(dialog: "Done: \(result.summary)")
}
}
Conform errors to CustomLocalizedStringResourceConvertible so Siri speaks user-friendly messages:
enum IntentError: Error, CustomLocalizedStringResourceConvertible {
case notFound
case unauthorized
var localizedStringResource: LocalizedStringResource {
switch self {
case .notFound: "That item could not be found"
case .unauthorized: "Please open the app and sign in first"
}
}
}
For the complete API -- EntityQuery variants, IndexedEntity, Spotlight integration, Focus filters, interactive widgets, Control Center controls, assistant schemas, Visual Intelligence, and AppIntentsPackage for modular projects -- read references/intent-patterns.md.
| Rule | Value |
|---|---|
| Max App Shortcuts | 3-5 core actions |
| Phrase length | 3-6 words + \(.applicationName) |
| Entity suggestions | 10-20 items max |
| Default shortcut tile color | Match your app brand |
| Separate entities from models | Always -- never conform data model to AppEntity |
| Deployment target | iOS 26+ only -- no @available checks |
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub 4eleven7/claude-skills --plugin ios-frameworks