Open the starter project for this lesson. You’ll see some additions to the final project from Lesson 1 to get started here. These are similar to the intent introduced in the last lesson, so take a quick look before moving on.
-
The open favorites intent has been added. This intent is similar to the
OpenSessionIntent, except that here, the user is taken to their list of favorite sessions. -
The get session details intent has also been added. Like the others, this intent has properties like
titleanddescriptionand aperformmethod. In the next lesson, you’ll add some additional code to this file. -
Finally, a
SessionIntentErrorenum has been added to the project. This allows the developer to specify specific error cases that may be encountered when using the intent in the app. Right now, only.sessionNotFoundis defined but it could be expanded as needed.
Adding Data to Spotlight
As seen in the last lesson, the entities were added to the Spotlight index so they could be searched. This code was placed into the AppMain.swift file.
Task {
try await CSSearchableIndex
.default()
.indexAppEntities(sessionDataManager.sessions.map(SessionEntity.init(session:)))
}
This code fetches the sessions from the sessionDataManager, which are the app’s base model instances, and converts them to SessionEntity instances, which adopt the AppEntity protocol.
Making and Registering Shortcuts
Shortcuts can be created as another way to use the information that Spotlight indexed. This also provides the first hooks for Siri to get involved.
To define the shortcuts for the app, start by importing the Foundation and AppIntents frameworks and then creating a SessionShortcuts class that adopts the AppShortcutsProvider protocol:
import Foundation
import AppIntents
class SessionShortcuts: AppShortcutsProvider {
Then, define the shortcutTileColor property, which is required by the protocol, to indicate the color for your app’s shortcuts in the Shortcuts app.
static var shortcutTileColor = ShortcutTileColor.navy
Next, define a static var called appShortcuts, an array of AppShortcut instances. This, too, is a required property from the AppShortcutsProvider protocol:
static var appShortcuts: [AppShortcut] {
Now, it’s time to define the shortcuts. Start by defining a shortcut for GetSessionDetails:
AppShortcut(
intent: GetSessionDetails(),
phrases: [
//"Get \(\.$sessionToGet) details in \(.applicationName)",
//"Get details for \(\.$sessionToGet) in \(.applicationName)"
"Get details in \(.applicationName)"
],
shortTitle: "Get Details",
systemImageName: "cloud.rainbow.half",
parameterPresentation: ParameterPresentation(
for: \.$sessionToGet,
summary: Summary("Get \(\.$sessionToGet) details"),
optionsCollections: {
OptionsCollection(SessionEntityQuery(), title: "Favorite Sessions", systemImageName: "cloud.rainbow.half")
}
))
The GetSessionDetails intent has a parameter for the session the user wants to get information about. This is similar to the OpenSession intent discussed in lesson 1. To recap, the shortcut has:
-
An
intentto perform when the shortcut is run. - Phrases used when this shortcut is used with Siri. They must contain the app’s name. In this case, there is also a parameter for the session to get information about.
Note: You’ll notice that the first two phrases here have been commented out. Per the demo code Apple released, these phrases, which contain parameters, should work, but as of the publishing of the lesson, they do not get properly triggered, even in iOS 26, a full release after they appeared in iOS 18. A third option has been added without parameters. When this phrase is recognized, and the system detects that the intent needs a parameter, Siri will prompt the user for that information. Additionally, Siri’s phrase detection isn’t always consistent. Sometimes phrases will be detected and processed fine, and other times a set of google search results will be shown. Hopefully, this will be corrected in a future version of iOS.
- A short title to display for the shortcut when shown in the user interface, such as in the search results.
- An image name to display for the shortcut when shown in the user interface.
-
A
parameterPresentationargument that helps display this shortcut in the Shortcuts app, including the parameters in question.
Next, define a shortcut for the OpenFavorites intent. This shortcut is a bit simpler, only requiring the intent, phrases, title and image name. Note that no parameters are used here since this is a simple intent that doesn’t require one.
AppShortcut(
intent: OpenFavorites(),
phrases: [
"Open Favorites in \(.applicationName)",
],
shortTitle: "Open Favorites",
systemImageName: "star.circle")
}
}
Using these shortcuts as a template, you can add other shortcuts as needed. For the system to recognize them, you need to call the updateAppShortcutParameters function on SessionShortcuts. Add this line to the init of the AppMain.swift file:
SessionShortcuts.updateAppShortcutParameters()
Automatic Siri Support
Thanks to the phrases added to the shortcuts, Siri can now respond when the user utters those phrases. To see your spoken words on the screen while debugging, go to Settings -> Apple Intelligence & Siri -> Siri Responses and choose “Always Show Request.”
Note that the iOS 26 simulator does not correctly render the Siri responses on screen.
To see this action, you can trigger Siri and say, “Open favorites in SessionTracker.” The phrase will be displayed on the screen. Once parsed, the favorites will open inside the app.
Note: This may not always parse correctly! It is not clear what causes this to happen. The demo in the next lesson does work correctly, so you can check out Siri phrase recognition there.
App Intent Domains
To introduce app intent domains into the Session Tracker app, you need an appropriate entity and intent to add macros to your code. Early in the Assistant Schema API in iOS 18, now called App Intent Domains, a limited set of domains, including browser, mail and photo, were available.
Since then, Apple has revised their updates for Siri and Apple Intelligence. The following appears on the documentation for App Intent domains
Siri’s personal context understanding, onscreen awareness, and in-app actions are in development and will be available with a future software update.
You can start to add code now, however, to prepare for that future update! To add the ability to launch the URL for a session in a browser, you can use the @AppEntity(browser.tab) macro in the entity and the @AppIntent(.browser.createTab) macro in the intent.
In the AppIntents folder, make a new file called OpenURLInTabIntent.swift, and add the starter code for an intent:
import AppIntents
struct OpenURLInTabIntent: AppIntent {
static let title: LocalizedStringResource = "Open Session in Tab"
@Parameter(title: "Session")
var session: SessionEntity?
func perform() async throws -> some ReturnsValue<SessionEntity?> {
return .result()
}
static var parameterSummary: some ParameterSummary {
Summary("Open \(\.$session) in a browser")
}
}
This intent is similar to the others in the codebase, but it doesn’t do much. The perform function simply returns a .result(). To work towards being able to view the session details in a browser, add an @AppIntent(.browser.createTab) macro to the intent.
@AppIntent(schema: .browser.createTab)
struct OpenURLInTabIntent: AppIntent {
This Swift Macro does some work behind the scenes. First, it handles the title parameter for you and also requires two additional parameters: url and isPrivate:
@AppIntent(schema: .browser.createTab)
struct OpenURLInTabIntent: AppIntent {
var url: URL?
var isPrivate: Bool
The perform method also needs to be updated:
func perform() async throws -> some ReturnsValue<SessionEntity?> {
return .result(value: session)
}
If you build this now, you’ll see that the compiler complains that the return type of the perform method doesn’t meet the required type. This is because the SessionEntity needs to be updated to use it with the intent. In the SessionEntity.swift file, add an @AppEntity macro to the entity:
@AppEntity(schema: .browser.tab)
struct SessionEntity: AppEntity, IndexedEntity {
To conform to this macro, also add properties for url and isPrivate:
var url: URL?
var isPrivate: Bool
Finally, add an additional line for the url and isPrivate properties to the init method:
init(session: Session) {
self.id = session.id
self.imageName = session.featuredImage
self.name = session.name
self.sessionDescription = session.sessionDescription
self.sessionLength = session.sessionLength
self.url = session.url
self.isPrivate = false
}
These additions prepare the app for when Apple Intelligence is fully released in the future release of iOS.