When I first tried to add Siri integration to one of my iOS apps, the old SiriKit approach stopped me cold. Between the Intent Definition file, the generated code Xcode would overwrite on rebuild, and the separate App Extension target, the boilerplate alone took half a day before I'd written a single line of my own logic.
App Intents — introduced in iOS 16 and significantly enhanced in iOS 17 — changed everything. And combined with Antigravity, it's become one of the smoothest parts of my iOS development workflow. This guide walks through everything I've learned implementing App Intents with AI assistance, including the specific failure modes to watch for in Antigravity-generated Swift code.
Why App Intents Works So Well with Antigravity
The reason App Intents pairs nicely with Antigravity comes down to structure. SiriKit required you to manage three things at once: an .intentdefinition GUI file, Xcode-generated code you couldn't directly edit, and a separate App Extension. When you asked Antigravity to modify that setup, the generated files and hand-written code would frequently conflict.
App Intents eliminates all of that. Everything is pure Swift code in a single file, in a single target. When you tell Antigravity "add an intent to create a task," it produces a complete, self-contained implementation with no hidden dependencies to manage.
The difference in practical terms:
- SiriKit (old): Intent Definition file + generated classes + App Extension target + rebuild cycle
- App Intents (current): One Swift file, one target, zero code generation, editable directly
This structural simplicity is why Antigravity handles App Intents well. The entire intent is visible as code, so Antigravity can reason about it holistically rather than reconstructing intent from scattered files.
Setting Up Your First App Intent with Antigravity
Open your project in Antigravity and send this prompt. Specifying the iOS target version matters — the API differs meaningfully between iOS 16 and 17, and leaving it unspecified often leads Antigravity to use the older patterns:
Add App Intents support to this iOS app.
Target is iOS 17 and above. Create a single basic App Intent
to add a task. No EntityQuery needed for now.
Antigravity will inspect your project structure and generate something like this:
import AppIntents
struct AddTaskIntent: AppIntent {
static var title: LocalizedStringResource = "Add Task"
static var description = IntentDescription("Adds a new task to your app")
@Parameter(title: "Task Name")
var taskName: String
@Parameter(title: "Due Date", default: nil)
var dueDate: Date?
func perform() async throws -> some IntentResult & ProvidesDialog {
// Replace TaskRepository with your app's actual data layer
let task = await TaskRepository.shared.add(name: taskName, dueDate: dueDate)
return .result(dialog: "Added \(task.name) to your tasks")
}
}Build and run this on a device, open the Shortcuts app, and "Add Task" will appear immediately. No extension target, no rebuild dance, no .intentdefinition file to hunt down. The first time I saw this work I genuinely checked twice to make sure I hadn't missed a step.
Adding Enums for Option Pickers
The type safety of App Intents parameters is where the framework really shines. Using AppEnum, you can give Siri and the Shortcuts app a proper picker interface instead of free-form text input. This is especially useful for parameters with a fixed set of valid values like status, category, or priority.
Tell Antigravity: "Add a priority parameter with three options: high, medium, and low."
enum TaskPriority: String, AppEnum {
case high = "high"
case medium = "medium"
case low = "low"
static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Priority")
static var caseDisplayRepresentations: [TaskPriority: DisplayRepresentation] = [
.high: "High",
.medium: "Medium",
.low: "Low",
]
}
struct AddTaskIntent: AppIntent {
static var title: LocalizedStringResource = "Add Task"
static var description = IntentDescription("Adds a new task to your app")
@Parameter(title: "Task Name")
var taskName: String
@Parameter(title: "Priority", default: .medium)
var priority: TaskPriority
func perform() async throws -> some IntentResult & ProvidesDialog {
let task = await TaskRepository.shared.add(
name: taskName,
priority: priority.rawValue
)
return .result(dialog: "Added \(task.name) with \(priority) priority")
}
}Say "Add report deadline with high priority" to Siri and the parameters map automatically. No manual string parsing, no regex matching — just type-safe intent parameters. When you have five or six intents in your app, this kind of structure makes maintenance dramatically more predictable.
Registering Siri Phrases with AppShortcutsProvider
Creating an AppIntent struct only makes your action visible as an action in the Shortcuts app. To let Siri invoke it by voice — without requiring the user to manually configure a shortcut first — you need AppShortcutsProvider:
struct TaskAppShortcuts: AppShortcutsProvider {
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: AddTaskIntent(),
phrases: [
// \(.applicationName) is automatically replaced with your app's display name
"Add a task in \(.applicationName)",
"Add \(.taskName) in \(.applicationName)"
],
shortTitle: "Add Task",
systemImageName: "plus.circle"
)
}
}No @main annotation needed. No registration call. Just include this struct in your build target and it registers automatically at app launch. This is one of the pieces Antigravity occasionally omits from generated code — worth checking your output explicitly.
One thing to keep in mind: Apple limits the number of AppShortcut entries per app, and the phrases need to contain your app name token. If Antigravity generates a phrase without \(.applicationName), Siri won't accept it.
Three Pitfalls in Antigravity-Generated App Intent Code
These are the issues I've hit consistently when using Antigravity to write App Intents. All three are things Antigravity gets wrong not from lack of knowledge, but from not having enough context about where the code runs.
1. UI updates inside perform() without MainActor
perform() runs off the main thread. Antigravity sometimes generates code that calls ViewModel or UIKit update methods directly inside perform(), which crashes at runtime with a threading violation.
// ❌ What Antigravity sometimes generates
func perform() async throws -> some IntentResult {
viewModel.refreshTaskList() // 💥 will crash
return .result()
}
// ✅ Wrap UI work in MainActor.run
func perform() async throws -> some IntentResult {
await MainActor.run {
viewModel.refreshTaskList()
}
return .result()
}The fix is straightforward once you know what to look for. I now add "wrap any ViewModel or UI calls in MainActor.run" to the end of any App Intents prompt I give Antigravity.
2. Older API usage when the iOS target isn't specified
If you don't mention your iOS target, Antigravity may default to iOS 16-compatible API patterns. The IntentResult protocol's associated type requirements differ between iOS 16 and 17, and some of the more expressive return type combinations only work on 17+. Specifying "iOS 17 and above" in the prompt consistently produces current API code.
3. Optional @Parameter behavior in Shortcuts
Declaring @Parameter(title: "Due Date", default: nil) doesn't always make the parameter optional in the Shortcuts app UI the way you'd intuit. In some configurations the Shortcuts app still prompts the user to fill it in. If a parameter should genuinely be skippable — meaning the user can run the intent without specifying it — test that explicitly in the Shortcuts app rather than trusting the generated code's comments.
What Antigravity Is Particularly Good at with App Intents
Beyond the initial scaffolding, there are a few App Intents tasks where Antigravity adds real value:
Expanding intents incrementally. Once you have a working intent, asking Antigravity to "add an intent to complete a task" or "add an intent to list today's tasks" generates consistent, correctly-structured code that fits your existing pattern. The first intent acts as implicit context.
Writing unit tests. Antigravity generates XCTestCase subclasses for App Intents that properly handle the async throws semantics of perform(). Testing intents is often overlooked, and having Antigravity scaffold these tests makes it easier to add coverage.
Localization. LocalizedStringResource in App Intents integrates with your .xcstrings file. Antigravity can generate the full set of localized strings for an intent when you provide the target locales.
Testing on Device vs. Simulator
Testing Siri integration is one area where the simulator falls meaningfully short. Voice recognition accuracy, phrase matching, and the visual flow of Siri's response dialog all behave differently on a real device. The basic testing flow:
- Build and install on device
- Settings → Siri & Search → find your app → enable "Ask Siri"
- "Hey Siri, [your registered phrase]"
- Test the non-voice path through the Shortcuts app separately
I find it useful to test with the screen locked, since that's often when users actually invoke Siri. App Intents that need to display information on screen require handling the locked-screen state explicitly — another detail Antigravity sometimes overlooks when generating code for intents that return .result(view:).
For combining App Intents with Live Activities and Dynamic Island — a pattern that works well for ongoing task tracking — see the Live Activities and Dynamic Island guide. If you're building a widget alongside your intent, the WidgetKit home widget guide covers the complementary setup in detail.
App Intents has become one of iOS's most quietly capable extension points. Siri, Spotlight search, Focus Filters, and widgets can all share the same intent definitions — and with iOS 26 likely to expand this surface area further, building familiarity with the framework now pays off compoundingly.
The most useful thing you can do with this article is pick one specific action your app already supports and write one intent for it. From that starting point, Antigravity becomes genuinely useful for expanding the intent set incrementally. The first build where Siri responds to a phrase you wrote is a satisfying moment that makes the initial setup feel worth it.