I have a SwiftUI document-based app that for the sake of this discussion stores accounting information: chart of accounts, transactions, etc. Each document is backed by a SwiftData DB.
I'd like to incorporate search into the app so that users can find transactions matching certain criteria, so I went to Core Spotlight. Indexing & search within the app seem to work well.
The issue is that Spotlight APIs appear to be App based & not Document based. I can't find a way to separate Spotlight data by document.
I've tried having each document maintain a UUID as a document-specific identifier and include the identifier in every CSSearchableItem
. When performing a query I filter the results with CSUserQueryContext.filterQueries
that filter by the document identifier. That works to limit results to the specific file for search operations.
Index updates via CSSearchableIndexDelegate.reindex*
methods seem to be App-centric. A user may have file #1 open, but the delegate is being asked to update CSSearchableItem
s for IDs in other files.
- Is there a proper way to use Spotlight for in-app search with a document-based app?
- Is there a way to keep Spotlight-indexed data local within the app & not make it available across the system? I.e. I'd like to search within the app only. System-level searches should not surface this data.
The issue is that Spotlight APIs appear to be App based & not Document based.
Sort of. I think the better way to understand this is that the API was intentionally broadened to cover non-document data, but that shift also makes the API appear more "app based".
I can't find a way to separate Spotlight data by document.
Note the "contentURL" property of CSSearchableItemAttributeSet, which is how you'd note the document location. So you'll end up creating multiple CSSearchableItems for every document, all of which (for a given document) will have the same content URL.
I've tried having each document maintain a UUID as a document-specific identifier and include the identifier in every CSSearchableItem. When performing a query I filter the results with CSUserQueryContext.filterQueries that filter by the document identifier. That works to limit results to the specific file for search operations.
My guess here is that this is fairly slow, because you're basically searching "everything" and then discarding results "down" to the target file. I think you'll find that including contentURL as part of the initial query makes things significantly faster.
Index updates via CSSearchableIndexDelegate.reindex* methods seem to be App-centric. A user may have file #1 open, but the delegate is being asked to update CSSearchableItems for IDs in other files.
This depends on what you ask it to do. reindexSearchableItemsWithIdentifiers should only index items with that identifier which you, presumably, have constrained to your file. However, the other option is to more "manually" update the index by using deleteSearchableItems(withIdentifiers:completionHandler:) to delete stale data and/or indexSearchableItems(_:completionHandler:) to add/update new/existing data.
Finally, some general comments on this kind of situation:
I have a SwiftUI document-based app that for the sake of this discussion stores accounting information: chart of accounts, transactions, etc. Each document is backed by a SwiftData DB.
Keep in mind that CoreSpotlight's larger goal is to make your apps content more broadly searchable by the larger system, NOT (necessarily) to serve as the "full" search system for all of your content. In more concrete terms, I think there's a spectrum that runs from something like:
-
A music player, where the metadata of each is well "known" and everything the user can search their library for in-app might reasonably be available system wide.
-
A spreadsheet, where a user might want to search for "22.43" but is unlikely to search for that system wide.
The custom data field system does allow (I think) you to use CoreSpotlight as the index system for data you don't actually need/want the system to search, however, the right approach here depends on the nature of your app and the data you're working with. As a contrived example, text editors should probably not try and implement arbitrary text search on "top" of CoreSpotlight.
Also, keep in mind that being document based does create edge cases which are easy to overlook. Case in point, this doesn't actually work:
I've tried having each document maintain a UUID as a document-specific identifier and include the identifier in every CSSearchableItem.
...since the user can arbitrarily duplicate the same file. In the worst case, you could conceivable end up searching an older/new copy of the file you've opened (because the other file was indexed and the current one wasn't). Similarly, you can also end up with documents with the same UUID having totally different content. contentURL lets you avoid that, (by constraining you to the relevant file) but that also means you may need to index a file you just opened.
I'm not aware of any solution that will REALLY address all of these issues, it's more a matter of working through the edge cases and finding a solution that's "reasonable".
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware