Skip to content

Instantly share code, notes, and snippets.

@juanarzola
Last active November 21, 2025 07:35
Show Gist options
  • Select an option

  • Save juanarzola/131c81f2cdb438dc8114bb003b87eff2 to your computer and use it in GitHub Desktop.

Select an option

Save juanarzola/131c81f2cdb438dc8114bb003b87eff2 to your computer and use it in GitHub Desktop.
import SwiftData
import CoreSpotlight
import FetchDescriptorObserver
import OSLog
import AppIntents
private let logger = Logger.spotlight
// Initialize from a SwiftData PersistentModel
protocol FromPersistentModel {
associatedtype Model: PersistentModel
init(persistentModel: Model)
}
extension ModelContainer {
/// Observe changes to IndexedEntity's associated PersistentModel, using a FetchDescriptor.
/// When they change, they are reindexed in Spotlight
@MainActor
func indexFoldersInSpotlight() async throws {
try await FolderEntity.indexInSpotlight(.allFolders, from: self)
}
...
}
// Extend `IndexedEntities` that can be initialized `FromPersistentModel`
// to be indexable in Spotlight
extension IndexedEntity where Self: FromPersistentModel {
/// Invoke in a Task in your App to index in spotlight in the background
@MainActor
static func indexInSpotlight(
_ descriptor: FetchDescriptor<Self.Model>,
from modelContainer: ModelContainer) async throws
{
let observer = descriptor.makeObserver { models in
/// Fetching and mapping happens in a background thread
models.map { Self(persistentModel: $0) }
}
var errorCount = 0
for try await entities in observer
.values(modelContainer)
.debounce(for: .milliseconds(500)) {
do {
let spotlightIndex = CSSearchableIndex.default()
try await spotlightIndex.deleteAppEntities(ofType: Self.self)
try await spotlightIndex.indexAppEntities(entities)
} catch {
errorCount += 1
logger.error("""
\(#function): Indexing error #\(errorCount):
\(error)
""")
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment