Overview

As iOS continues to evolve, Apple is enhancing how apps can integrate with Siri and system-wide shortcuts through AppIntents and AppShortcuts. These frameworks allow for custom, predefined tasks within apps that users can trigger with voice commands, Spotlight, or the Shortcuts app. This article explores the benefits and implementation details of these frameworks in iOS, using the Groceries app as a working example to demonstrate how they can make user interactions easier and more intuitive.

Before deep dive in, you can check these videos so that you can understand it more visually.

Ask Siri to interact with our app’s features

AppIntents become available to the iOS Spotlight search

AppIntents also available through iOS Shortcuts app

Pretty cool right? So let’s deep dive in 🙂.

What are AppIntents and AppShortcuts?

AppIntents and AppShortcuts are two frameworks designed to let apps offer custom actions through voice and search.

AppIntents: AppIntents enable developers to create structured, purpose-built actions within an app that can be invoked by Siri, the Shortcuts app, and Spotlight. These intents define specific tasks, such as adding an item to a grocery list or marking a grocery item as bought, each with parameters and potential results. Each intent is created to guide Siri on what actions are possible, creating a more natural interaction between Siri and the app.

AppShortcuts: AppShortcuts offer a simpler, preconfigured way for users to perform app-specific tasks without manually setting them up. For instance, AppShortcuts for the Groceries app can offer one-tap access to common actions like “Add Grocery Item” or “Clear Grocery List.” These shortcuts can be customized by users, made discoverable in Spotlight, and even shown in the Siri suggestions widget.

Together, AppIntents and AppShortcuts form a cohesive system that allows apps to offer high-value, frequently accessed tasks in a way that is both accessible and intuitive.

Why Use AppIntents and AppShortcuts?

1. Enhanced User Experience: These frameworks make apps more accessible, especially in hands-free situations where users may need to operate their devices by voice. In the case of the Groceries app, a user can simply say, “Hey Siri, add milk to my grocery list,” making it easy to update their list without opening the app.

2. Seamless System Integration: AppIntents and AppShortcuts allow apps to become an integral part of the iOS environment. For instance, once configured, the shortcuts for the Groceries app appear directly in Spotlight, in the Shortcuts app, and within Siri suggestions. This makes it easier for users to engage with your app’s core functionality from anywhere on their device.

3. Increased Visibility and Usage: Through shortcuts, your app’s functionalities can be promoted within the system, helping with user retention and engagement. When users see their shortcuts in Spotlight or receive Siri suggestions based on past usage, it prompts them to continue using your app’s features, potentially increasing app visibility.

Examples from the Groceries App

In the Groceries app, the AppIntents and AppShortcuts frameworks can offer intuitive actions that save users time and effort:

List Items Shortcut: This shortcut could let users ask Siri, “What’s on my grocery list?” Siri would then use the AppIntent designed for listing items, displaying or reading out the current list. It’s a practical feature for multitasking, like when users want to check their list while shopping.

import AppIntents

struct ShowGroceriesIntent: AppIntent {
    
    static var title: LocalizedStringResource = "Show Groceries list"
    
    static var description = IntentDescription("Show current available groceries list item.")
    
    static var openAppWhenRun: Bool = false
    
    @MainActor
    func perform() async throws -> some IntentResult & ProvidesDialog {
        let groceriesList = try await loadGroceries()
        let dialogMessage = groceriesList.isEmpty
            ? "Your groceries list is empty."
            : "Here are your groceries: \(groceriesList.joined(separator: ", "))"
        return .result(dialog: IntentDialog(stringLiteral: dialogMessage))
    }
    
    @MainActor
    private func loadGroceries() async throws -> [String] {
        let groceryLoader = LocalGroceryLoader(modelContext: GroceriesApp.sharedModelContainer.mainContext)
        return try await groceryLoader.loadAllGroceries()
    }
}

Add Item Shortcut: With AppShortcuts, the app can predefine actions like “Add Grocery Item.” This is useful for users who frequently need to add items to their list on the go. Using this shortcut, they can add items quickly by invoking Siri or tapping directly in Spotlight, making it much faster than opening the app.

import AppIntents

struct AddItemToGroceriesIntent: AppIntent {
    
    static var title: LocalizedStringResource = LocalizedStringResource(stringLiteral: "Add item to groceries list")
    
    static var description: IntentDescription? = IntentDescription(stringLiteral: "Add an item to groceries list")
    
    static var openAppWhenRun: Bool = false
    
    @Parameter(title: "Item name")
    var itemName: String?
    
    @MainActor
    func perform() async throws -> some IntentResult & ProvidesDialog {
        
        guard let itemName else {
            let dialog = IntentDialog("What grocery item you would like to add?")
            throw $itemName.needsValueError(dialog)
        }
        
        do {
            let modelContext = GroceriesApp.sharedModelContainer.mainContext
            let adder = LocalGroceryAdder(modelContext: modelContext)
            try await adder.add(grocery: GroceryItem(name: itemName))
            let dialog = IntentDialog("\(itemName) is added to your groceries list")
            return .result(dialog: dialog)
        } catch {
            throw error
        }
    }
}

Clear Grocery List Shortcut: Users can set up a shortcut to clear the entire grocery list, which is helpful after shopping trips. This shortcut allows for a single tap or Siri command to quickly refresh their list without going through multiple steps.

import AppIntents

struct RemoveAllGroceriesIntent: AppIntent {
    
    static var title: LocalizedStringResource = "Remove all Grocery items"
    
    static var description: IntentDescription? = IntentDescription(stringLiteral: "Remove all items inside groceries list")
    
    static var openAppWhenRun: Bool = false
    
    @MainActor
    func perform() async throws -> some IntentResult & ProvidesDialog {
        let modelContext = GroceriesApp.sharedModelContainer.mainContext
        let deleter = LocalGroceryDeleter(modelContext: modelContext)
        let loader = LocalGroceryLoader(modelContext: modelContext)
        
        do {
            let groceries = try await loader.loadGroceries()
            
            guard !groceries.isEmpty else {
                let dialog = IntentDialog(stringLiteral: "You don't have any items to be deleted.")
                return .result(dialog: dialog)
            }
            
            do {
                try await deleter.deleteGroceries()
                let count = try await loader.loadGroceries().count
                let dialog = IntentDialog(stringLiteral: "All grocery items are deleted. You have \(count) item.")
                return .result(dialog: dialog)
            } catch {
                throw error
            }
            
        } catch {
            throw error
        }
    }
}

Registering AppIntents for Siri: Binding Intents to Specific Phrases

To fully utilize the power of AppIntents, it’s essential to register them and bind them to specific trigger phrases. This step ensures that Siri can recognize and associate user commands with the corresponding AppIntents. By doing so, you enable users to interact with your app seamlessly through voice, Spotlight, or the Shortcuts app.

import AppIntents

struct GroceriesAppShortcutProvider: AppShortcutsProvider {

    @AppShortcutsBuilder
    static var appShortcuts: [AppShortcut] {

        AppShortcut(
            intent: ShowGroceriesIntent(),
            phrases: [
                "Show my groceries",
                "What's on my grocery list?"
            ],
            shortTitle: LocalizedStringResource(stringLiteral: "Show groceries"),
            systemImageName: "cart"
        )

        AppShortcut(
            intent: RemoveAllGroceriesIntent(),
            phrases: [
                "Remove all groceries",
                "Empty my grocery list"
            ],
            shortTitle: LocalizedStringResource(stringLiteral: "Remove all groceries"),
            systemImageName: "cart.fill.badge.minus"
        )

        AppShortcut(
            intent: AddItemToGroceriesIntent(),
            phrases: [
                "Add item to groceries",
                "Add an item to my grocery list"
            ],
            shortTitle: LocalizedStringResource(stringLiteral: "Add an item to groceries"),
            systemImageName: "cart.badge.plus"
        )
    }
}

By implementing these examples in the Groceries app, users experience a much smoother and faster way to interact with grocery tasks, fully demonstrating how AppIntents and AppShortcuts can enrich the app experience.

To explore the full implementation of AppIntents and AppShortcuts in action, visit the Groceries app repository on GitHub.

Leave a comment