Making app entities available in Spotlight
Annotate your app entity types to support Spotlight indexing, and donate entities to make them findable in searches.
Overview
Spotlight provides systemwide search capabilities and integrates with Apple Intelligence, Siri, and other system technologies. To make your app’s content findable by Spotlight, you need to add information about your content to the Spotlight indexes. If your app defines AppEntity types, index them so that Spotlight can use them to open your app and display that content.
During the indexing process, you provide Spotlight with information about your app’s content. The standard indexing process entails building a CSSearchableItemAttributeSet for each type. However, the App Intents framework offers a more declarative approach for specifying that information. This approach requires less code because it leverages existing code you use to create your entities.
Add support for indexing your entity types
To index your app’s entities, each AppEntity type you define needs to conform to the IndexedEntity protocol. The following example shows the LandmarkEntity type from doc:#adopting-app-intents-to-support-system-experiences, which includes this protocol in its declaration:
struct LandmarkEntity: IndexedEntity {
// ...
}The IndexedEntity protocol provides default implementations of its properties, so you don’t need to add any code by default. However, you can override the implementations of those properties to customize the information you pass to Spotlight during indexing.
Specify which properties of your entity to index
Conformance to the IndexedEntity protocol unlocks support for indexing entities, but you need to take additional steps to specify what content to index. When you create an entity, you add certain properties to your type and annotate other properties with macros the App Intents framework requires. Spotlight uses this same information when indexing your type. For example, Spotlight automatically indexes the contents of your entity’s displayRepresentation property, including the title, subtitle, and image values you provide.
If you adorn properties with the AppEntity.Property or ComputedProperty(indexingKey:) property wrappers, you can use those same wrappers to tell Spotlight what content to index. When you include an indexing key with those property wrappers, Spotlight automatically adds the data in that property to your app’s index. Specify an indexing key using Swift key paths and one of the property names in the CSSearchableItemAttributeSet type. To create this path, specify a slash and period (\.) followed by the property name. You can also use this approach to specify key paths for your app’s custom indexing keys.
The following code from the Accelerating app interactions with App Intents sample shows the LandmarkEntity type, which makes the app’s landmark data available to the system. The property wrapper for description tells Spotlight to index the property using the Spotlight-provided contentDescription key. The property wrapper for continent tells Spotlight to index the property using the provided custom key.
struct LandmarkEntity: IndexedEntity {
// ...
// Maps the description variable to the Spotlight indexing key `contentDescription`.
@ComputedProperty(indexingKey: \.contentDescription)
var description: String { landmark.description }
// Maps the continent variable to a custom Spotlight indexing key.
@ComputedProperty(
customIndexingKey: CSCustomAttributeKey(
keyName: "com_AppIntentsTravelTracking_LandmarkEntity_continent"
)!
)
var continent: String { landmark.continent }
// ...
}If your entity doesn’t have a declared property for data you want to index, specify that data in your entity’s attributeSet property. The IndexedEntity protocol provides the default implementation of this property, but you can implement it yourself and return a custom CSSearchableItemAttributeSet with additional data to index. The following example shows how you might use this property to return additional information related to a landmark that the entity doesn’t expose directly:
extension LandmarkEntity {
var attributeSet: CSSearchableItemAttributeSet {
let attributes = CSSearchableItemAttributeSet()
attributes.latitude = NSNumber(value: landmark.latitude)
attributes.longitude = NSNumber(value: landmark.longitude)
attributes.supportsNavigation = true
return attributes
}
}Add your entities to a Spotlight index
When your app runs, you must deliver instances of your AppEntity types to Spotlight so it can index them. If your app doesn’t yet support Spotlight, index your entities directly by calling the indexAppEntities(_:priority:) method of a named CSSearchableIndex object. This method passes the entities to Spotlight, which processes them and adds them to your app’s index. The following example donates entities containing landmark data to an app-specific index:
static func donateLandmarks(modelData: ModelData) async throws {
let landmarkEntities = await modelData.landmarkEntities
try await CSSearchableIndex(name: "AppIntentsTravelTracking_Landmarks").indexAppEntities(landmarkEntities)
}If your app already indexes its content using CSSearchableItem objects, associate your entities with those items before passing them to the indexer. For information on how to do that, see Integrate entities into your existing Spotlight code. For additional information about the Spotlight indexing process, see Adding your app’s content to Spotlight indexes.
Integrate entities into your existing Spotlight code
If you already index your app’s content using CSSearchableItem objects, attach matching entities to those items to improve the search experience. When you attach an entity to one of your searchable items, Spotlight can use that entity to display the search result in your app, if you also have an open intent for the entity. The following steps explain the process for how to associate an entity with a searchable item:
Populate the CSSearchableItemAttributeSet of your searchable item with the data you want Spotlight to index.
Create or locate the matching app entity for the item.
Call the associateAppEntity(_:priority:) method to add the entity to the attribute set.
Create the CSSearchableItem using the attribute set.
Index the item with the rest of your content.
The following example creates an array of searchable items for an app that manages hiking trails. For each trail, the code creates an app entity for the trail and associates it with the trail’s search attributes. When calling the associateAppEntity(_:priority:) method, the code also specifies a priority value to indicate the importance of that trail to the person. Spotlight elevates items with higher priority values in suggestions and search results to make them more visible.
let searchableItems = trails.map { trail in
let attributes = trail.searchableAttributes
let isFavorite = favoritesCollection.members.contains(trail.id)
let weight = isFavorite ? 10 : 1
let entity = TrailEntity(trail: trail)
attributes.associateAppEntity(entity, priority: weight)
let item = CSSearchableItem(uniqueIdentifier: String(trail.id),
domainIdentifier: nil,
attributeSet: attributes)
return item
}After running the preceding code, create a named CSSearchableIndex object and use it to index the items. For information about how to index content using the Core Spotlight APIs, see Adding your app’s content to Spotlight indexes.
Provide an intent to open your app from search results
For each AppEntity type that you define and donate to Spotlight, create an OpenIntent type that opens that entity in your app. When Spotlight returns one of your app’s entities in a search result, you want people to be able to tap that result and navigate to the associated content in your app. When an open intent is present, Spotlight can use it to provide that behavior. The following example from doc:#adopting-app-intents-to-support-system-experiences shows the open intent for the app’s LandmarkEntity type. When someone taps a landmark in search results, Spotlight uses the intent to open the app and display the chosen landmark.
struct OpenLandmarkIntent: OpenIntent {
static let title: LocalizedStringResource = "Open Landmark"
@Parameter(title: "Landmark", requestValueDialog: "Which landmark?")
var target: LandmarkEntity
}