Core Spotlight & NSUserActivity Reference
Overview
Comprehensive guide to Core Spotlight framework and NSUserActivity for making app content discoverable in Spotlight search, enabling Siri predictions, and supporting Handoff. Core Spotlight directly indexes app content while NSUserActivity captures user engagement for prediction.
Key distinction Core Spotlight = indexing all app content; NSUserActivity = marking current user activity for prediction/handoff.
When to Use This Skill
Use this skill when:
- Indexing app content (documents, notes, orders, messages) for Spotlight
- Using NSUserActivity for Handoff or Siri predictions
- Choosing between CSSearchableItem, IndexedEntity, and NSUserActivity
- Implementing activity continuation from Spotlight results
- Batch indexing for performance
- Deleting indexed content
- Debugging Spotlight search not finding app content
- Integrating NSUserActivity with App Intents (appEntityIdentifier)
Do NOT use this skill for:
- App Shortcuts implementation (use app-shortcuts-ref)
- App Intents basics (use app-intents-ref)
- Overall discoverability strategy (use app-discoverability)
Related Skills
- app-intents-ref β App Intents framework including IndexedEntity
- app-discoverability β Strategic guide for making apps discoverable
- app-shortcuts-ref β App Shortcuts for instant availability
When to Use Each API
| Use Case |
Approach |
Example |
| User viewing specific screen |
NSUserActivity |
User opened order details |
| Index all app content |
CSSearchableItem |
All 500 orders searchable |
| App Intents entity search |
IndexedEntity |
"Find orders where..." |
| Handoff between devices |
NSUserActivity |
Continue editing note on Mac |
| Background content indexing |
CSSearchableItem batch |
Index documents on launch |
Apple guidance Use NSUserActivity for user-initiated activities (screens currently visible), not as a general indexing mechanism. For comprehensive content indexing, use Core Spotlight's CSSearchableItem.
Core Spotlight (CSSearchableItem)
Creating Searchable Items
import CoreSpotlight
import UniformTypeIdentifiers
func indexOrder(_ order: Order) {
let attributes = CSSearchableItemAttributeSet(contentType: .item)
attributes.title = order.coffeeName
attributes.contentDescription = "Ordered on \(order.date.formatted())"
attributes.keywords = ["coffee", "order", order.coffeeName.lowercased()]
attributes.thumbnailData = order.imageData
attributes.latitude = order.location.coordinate.latitude
attributes.longitude = order.location.coordinate.longitude
attributes.rating = NSNumber(value: order.rating)
let item = CSSearchableItem(
uniqueIdentifier: order.id.uuidString,
domainIdentifier: "orders",
attributeSet: attributes
)
item.expirationDate = Date().addingTimeInterval(60 * 60 * 24 * 365)
CSSearchableIndex.default().indexSearchableItems([item]) { error in
if let error = error {
print("Indexing error: \(error.localizedDescription)")
}
}
}
Key Properties
uniqueIdentifier
Purpose Stable, persistent ID unique to this item within your app.
uniqueIdentifier: order.id.uuidString
Requirements:
- Must be stable (same item = same identifier)
- Used for updates and deletion
- Scoped to your app
domainIdentifier
Purpose Groups related items for bulk operations.
domainIdentifier: "orders"
Use cases:
- Delete all items in a domain
- Organize by type (orders, documents, messages)
- Batch operations
Pattern:
item1.domainIdentifier = "orders"
item2.domainIdentifier = "documents"
CSSearchableIndex.default().deleteSearchableItems(
withDomainIdentifiers: ["orders"]
) { error in }
CSSearchableItemAttributeSet
Metadata describing the searchable content.
let attributes = CSSearchableItemAttributeSet(contentType: .item)
attributes.title = "Order #1234"
attributes.displayName = "Coffee Order"
attributes.contentDescription = "Medium latte with oat milk"
attributes.keywords = ["coffee", "latte", "order"]
attributes.thumbnailData = imageData
attributes.contentCreationDate = Date()
attributes.contentModificationDate = Date()
attributes.rating = NSNumber(value: 5)
attributes.comment = "My favorite order"
Common Attributes
| Attribute |
Purpose |
Example |
title |
Primary title |
"Coffee Order #1234" |
displayName |
User-visible name |
"Morning Latte" |
contentDescription |
Description text |
"Medium latte with oat milk" |
keywords |
Search terms |
["coffee", "latte"] |
thumbnailData |
Preview image |
JPEG/PNG data |
contentCreationDate |
When created |
Date() |
contentModificationDate |
Last modified |
Date() |
rating |
Star rating |
NSNumber(value: 5) |
latitude / longitude |
Location |
37.7749, -122.4194 |
Document-Specific Attributes
attributes.contentType = UTType.pdf
attributes.author = "John Doe"
attributes.pageCount = 10
attributes.fileSize = 1024000
attributes.path = "/path/to/document.pdf"
Message-Specific Attributes
attributes.recipients = ["[email protected]"]
attributes.recipientNames = ["Jane Doe"]
attributes.authorNames = ["John Doe"]
attributes.subject = "Meeting notes"
Batch Indexing for Performance
β DON'T: Index items one at a time
for order in orders {
CSSearchableIndex.default().indexSearchableItems([order.asSearchableItem()]) { _ in }
}
β
DO: Batch index operations
let items = orders.map { $0.asSearchableItem() }
CSSearchableIndex.default().indexSearchableItems(items) { error in
if let error = error {
print("Batch indexing error: \(error)")
} else {
print("Indexed \(items.count) items")
<