In the last week’s post, Walk Mate started donating favorite routes to Spotlight. Favorite a route and give a title. Search in Spotlight, tap, the route opens on the map. That used App Intents the not a fancy way: and IndexedEntity for the favorite, an OpenIntent that load it and a small router so a Spotlight tap can reach the same reducer as a tap inside the app. There was no Siri phrases yet.
Spotlight already knows the walk exists. Siri on iOS 27 is supposed to be able to talk about it now.
Last time, Spotlight could find a favorite route. This time, I’m making Siri able to talk about it, hand it to Maps and open it by voice.
1. Make the entity speak: @Property and Spotlight keywords
Before Siri can refer to a favorite, the entity behind it needs a field Siri is allowed to read aloud. I updated WalkMatePlaceEntity so name is wrapped in @Property. That wrapper publishes a field so Shortcuts can display it and Siri can say it. On Xcode 16+, @Property and @Parameter titles are optional. Apple derives a localizable title from the property name, so I only set title: when the Shortcuts label should differ from the key.
struct WalkMatePlaceEntity: IndexedEntity {
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Favorite walk"
static let defaultQuery = WalkMatePlaceEntityQuery()
static let spotlightIndexName = "WalkMateFavorites"
let id: UUID
@Property
var name: String?
var startLatitude: Double
var startLongitude: Double
}I also store the route’s starting coordinate directly on the entity that I’ll need it later for the Maps handoff. Note the entity only exports the trailhead not the whole walk.
Spotlight search still works the way Part 1 showed -searching “Berlin” finds a favorite named Berlin- because attributeSet puts the name into Spotlight keywords as well as the title:
var attributeSet: CSSearchableItemAttributeSet {
let attributes = defaultAttributeSet
attributes.displayName = name
attributes.title = name
if let name {
attributes.keywords = [name]
}
return attributes
}2. Let Shortcuts search and reindex
EnumerableEntityQuery is unchanged. Spotlight and Shortcuts still resolve a favorite by id through entities(for:) and list everything through allEntities().
Two things are new on top of that:
1-) When a Shortcuts parameter is “pick a saved walk,” suggestedEntities() offers the full list and EntityStringQuery filters it as you type:
func suggestedEntities() async throws -> [WalkMatePlaceEntity] {
try await allEntities()
}
extension WalkMatePlaceEntityQuery: EntityStringQuery {
func entities(matching string: String) async throws -> [WalkMatePlaceEntity] {
try await allEntities().filter { entity in
entity.name?.localizedStandardContains(string) == true
}
}
}2-) iOS 27 can ask the app to rebuild Spotlight. IndexedEntity describes the shape of what goes in the index but it doesn’t reindex by itself. IndexedEntityQuery is the write Spotlight calls when the index is stale:
@available(iOS 27, *)
extension WalkMatePlaceEntityQuery: IndexedEntityQuery {
func reindexEntities(
for identifiers: [WalkMatePlaceEntity.ID],
indexDescription: CSSearchableIndexDescription
) async throws {
let entities = FavoriteRouteStorage.load()
.filter { identifiers.contains($0.id) }
.compactMap(WalkMatePlaceEntity.init)
try await CSSearchableIndex.default().indexAppEntities(entities)
}
func reindexAllEntities(
indexDescription: CSSearchableIndexDescription
) async throws {
let entities = FavoriteRouteStorage.load().compactMap(WalkMatePlaceEntity.init)
try await CSSearchableIndex.default().indexAppEntities(entities)
}
}Same FavoriteRouteStorage.load(). The system passes a CSSearchableIndexDescription; I write the same default index Part 1 used.
3. Keep Siri’s phrase list in sync:
Part 1’s donateAll() deleted every WalkMatePlaceEntity in Spotlight then indexed whatever SQLite still had. That write is unchanged. What’s new: it also tells App Shortcuts the favorite names changed, so a phrase like “Load Berlin in Walk Mate” stays valid after a rename, add or delete.
static func donateAll() {
Task { @MainActor in
WalkMateAppShortcuts.updateAppShortcutParameters()
}
Task {
guard CSSearchableIndex.isIndexingAvailable() else { return }
let entities = FavoriteRouteStorage.load().compactMap(WalkMatePlaceEntity.init)
let namedIndex = CSSearchableIndex(name: spotlightIndexName)
try? await namedIndex.deleteAppEntities(ofType: WalkMatePlaceEntity.self)
let index = CSSearchableIndex.default()
try? await index.deleteAppEntities(ofType: WalkMatePlaceEntity.self)
guard !entities.isEmpty else { return }
try? await index.indexAppEntities(entities)
}
}The App.init function also calls updateAppShortcutParameters() once at launch, so the phrase cache is already warm the first time Siri needs it.
4. Open a favorite by voice
Part 1 had a single OpenWalkMatePlaceIntent: OpenIntent. That is still the iOS 27 open but it now adopts Apple’s .system.open schema Siri AI treats “open this favorite” as the system open action. TargetContentProvidingIntent is the iOS piece that actually brings the scene to that entity.
@available(iOS 27, *)
@AppIntent(schema: .system.open)
struct OpenWalkMatePlaceIntent: OpenIntent, TargetContentProvidingIntent {
var target: WalkMatePlaceEntity
@MainActor
func perform() async throws -> some IntentResult {
QuickActionRouter.dispatch(.openFavorite(id: target.id))
return .result()
}
}ShowSavedWalkIntent is the custom intent that runs on iOS 18+: same dispatch, same reducer.
struct ShowSavedWalkIntent: AppIntent {
static let title: LocalizedStringResource = “Show Saved Walk”
static let openAppWhenRun = true
@Parameter(title: “Saved walk”)
var walk: WalkMatePlaceEntity
@MainActor
func perform() async throws -> some IntentResult {
QuickActionRouter.dispatch(.openFavorite(id: walk.id))
return .result()
}
}Both send .openFavorite(id:). Part 1 already queued that id during splash. Tap “Berlin” in Spotlight, Shortcuts, Siri and you still get the same map as tap “Berlin” in Favorite Routes.
5. Give Siri a phrase to say
Writing an AppIntent is not enough for Siri to pick it up as a command. You register it on an AppShortcutsProvider. Every phrase has to interpolate \(.applicationName).
struct WalkMateAppShortcuts: AppShortcutsProvider {
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: ShowSavedWalkIntent(),
phrases: [
“Load \(\.$walk) in \(.applicationName)”
],
shortTitle: “Open Favorite Walk”,
systemImageName: “figure.walk”
)
}
}\(\.$walk) expands with the donated favorite names. That is why donateAll() and App.init call updateAppShortcutParameters(). After you name a walk “Berlin”, “Load Berlin in Walk Mate” is a real phrase.
(Part 1 already showed “Open Favorite Walk” and “Find Favorite walk” inside the Shortcuts app because the system lists every discoverable intent. The tile and the spoken phrase are new. They come from this provider.)
6. Understand “this walk“ on screen
Siri can mean the row you are looking at if the view is annotated. Each FavoriteRouteCard gets the entity id. iOS 18.4+ appEntityIdentifier.
func walkMateEntityIdentifier(_ id: UUID) -> some View {
if #available(iOS 18.4, *) {
appEntityIdentifier(
EntityIdentifier(for: WalkMatePlaceEntity.self, identifier: id)
)
} else {
self
}
}This is separate from exporting data. It just lets someone say “third walk in the favorites list” while the favorites list is open and have Siri know which one that means.
7. Hand the walk’s starting point to Apple Maps
A favorite is a private loop. Apple’s Maps place schema (@AppEntity(schema: .maps.place)) is a POI which contains hours, category etc. I did not adopt .maps.place since it doesn’t fit what Walk Mate provides.
What I exported instead is a PlaceDescriptor for the first coordinate and the name. Shortcuts or Siri can hand that to Maps so you can get to where the walk starts. Maps cannot ingest Walk Mate’s polyline. Extra Maps waypoints would be a different path Maps drew.
@available(iOS 26.4, *)
extension WalkMatePlaceEntity: Transferable {
static var transferRepresentation: some TransferRepresentation {
ValueRepresentation(exporting: \.startPlace)
}
}
@available(iOS 26, *)
extension WalkMatePlaceEntity {
var startPlace: PlaceDescriptor {
PlaceDescriptor(
representations: [
.coordinate(
CLLocationCoordinate2D(
latitude: startLatitude,
longitude: startLongitude
)
)
],
commonName: nil
)
}
}The @available(iOS 26.4, *) is not about Transferable itself and it is not about iOS 27. ValueRepresentation(IntentValueRepresentation) is the API that exports an AppEntity as a structured system value (PlaceDescriptor) instead of a file. File/data representations cannot carry a coordinate, Maps needs a place value. Apple added that structured export in 26.4.
8. Keep every device in sync: SyncableEntity
Favorites in Walk Mate already sync across devices via CloudKit, using the same UUID (favorite.id) everywhere. iOS27’s SyncableEntity protocol is what tells Siri that fact that this id is stable across a user’s devices, so a conversation about a favorite can move from iPhone to iPad.
Apple SyncableEntity says;
The presence of this protocol tells the system that it can refer to your entity consistently across devices. For example, Siri uses this capability to transfer a conversation from one device to another.
Since WalkMatePlaceEntity.id was already the CloudKit-synced UUID, adopting the protocol is a one-line addition:
@available(iOS 27, *)
extension WalkMatePlaceEntity: SyncableEntity {}9. Donate the action, not only the entity
Part 1 donated entities to Spotlight. That teaches the system the walk exists. It doesn’t teach the system you open it.
When a favorite is opened from the Favorites UI, I donate that open after it succeeds. On iOS 27 the donated intent is the schema open.
donateFavoriteOpened: { favorite in
guard let entity = WalkMatePlaceEntity(favorite) else { return }
if #available(iOS 27, *) {
var intent = OpenWalkMatePlaceIntent()
intent.target = entity
IntentDonationManager.shared.donate(intent: intent)
} else {
var intent = ShowSavedWalkIntent()
intent.walk = entity
IntentDonationManager.shared.donate(intent: intent)
}
}Donate after the action completes. Do not donate on every list render or you’ll flood the system with noise.
Phrases (Siri Voice Commands (Saved Walks & Search))
🗣️ “Generate a route in Walk Mate”, “Generate me a route in Walk Mate”, “Make me a walk in Walk Mate”, “Create a walk in Walk Mate”, “Generate walking routes in Walk Mate”
When the app closed and said these, it opens the app and immediately generates a new route based on the distance.
🗣️ “Load Riverside Loop in Walk Mate”
Siri opens Walk Mate directly to Riverside Loop on the map.
🗣️ “Open Riverside Loop in Walk Mate” or “Open Riverside Loop with Walk Mate”
Siri resolves the entity and opens Riverside Loop on the map.
🗣️ “Search Riverside Loop in Walk Mate”
App opens in the foreground, searches favorites and displays Riverside Loop on the map.
🗣️ “Search Central Park in Walk Mate” (not a favorited route)
App opens gracefully to the Favorites List so I can pick or browse saved routes.
Shortcuts App
Generate Routes (Custom Distance)
1. Open the Shortcuts app.
2. Tap + (New Shortcut) → Add Action.
3. Search for Walk Mate → Select Generate Routes.
4. Set Distance to 5 km.
5. Tap Run.
Walk Mate opens to the foreground and starts generating a route clamped to ~5 km. Also, Walk Mate opens and generates routes using the app's current default target distance without specifying the Distance from Shortcuts app. There is also a caveat for upper-lower bounds for distances like when the user enters a distance value more than 20 kms or lesser 3 kms that distance value safely clamps within the 3-20 km operating range.
When the Open Favorite Route shortcut runs, it asks you to select with which favorite you want to open the app;
If you have multiple walks with similar names (e.g. “Park North” and “Park South”) and you say: “Open Park in Walk Mate”, Siri can’t uniquely match one, so it prompts “Which one?” with the list of matching walks.
What I did not add
Even though Walk Mate has no search bar or a results screen for it,
.system.searchInApp stays and Siri hands Walk Mate a search term. Unique match loads that favorite otherwise the existing Favorites list opens. Since Walk Mate has no start/stop button to start your walking sessions there is no start button for Siri to press. You walk, medals come to you. There is also no option for sending the full loop to Apple Maps.





