Set Up Core Spotlight with Core Data: Getting Started
Learn how to connect Core Data with Core Spotlight and add search capability to your app using Spotlight. By Warren Burton.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Set Up Core Spotlight with Core Data: Getting Started
30 mins
- Getting Started
- Adding Spotlight to Your Core Data Model
- Configuring Spotlight in Your Persistent Store
- Creating a Spotlight Delegate
- Connecting a Spotlight Delegate
- Describing Searchable Items
- Trying It Out
- Heavy Lifting
- Building a Bridge
- Importing From an API
- Running the Fetch
- Opening a Search Result in Your App
- Including Image Thumbnails with Spotlight
- Deleting Spotlight Data
- Searching Inside the App
- Searching Core Spotlight
- Searchable SwiftUI
- Where to Go From Here?
Opening a Search Result in Your App
You observed earlier that when you tap the search result in Spotlight, PointyBug opens. In this section, you’ll learn how to respond to that event and open the record that corresponds to the search.
First, you need to add some helper methods. In the Project navigator, open CoreDataStack.swift. Then, add this extension at the end of the file:
extension CoreDataStack { func bugWithURI(_ uri: URL) -> Bug? { guard let objectID = viewContext .persistentStoreCoordinator? .managedObjectID(forURIRepresentation: uri) else { return nil } return viewContext.object(with: objectID) as? CDBug } }
You can reference NSManagedObject
instances using a Uniform Resource Identifier (URI). The URI acts like a street address for an object. Here, you add a method to recover a record from the database by using that URI.
Next, in the group Controller, open BugController.swift and add the following extension to the end of the file:
extension BugController { func selectBugWithURI(_ uri: URL) { if let bug = dataStack.bugWithURI(uri) { selectedBug = bug } } }
This method uses bugWithURI
to select the bug referenced by the URI.
In the Project navigator, in the group Views, open AppMain.swift. Add this import to the top of the file:
import CoreSpotlight
Next, add the following method to AppMain
:
func continueActivity(_ activity: NSUserActivity) { if let info = activity.userInfo, let objectIdentifier = info[CSSearchableItemActivityIdentifier] as? String, let objectURI = URL(string: objectIdentifier) { bugController.selectBugWithURI(objectURI) } }
When you tap a Spotlight result, the event that opens PointyBug sends an NSUserActivity
. This method unwraps the userInfo
dictionary in NSUserActivity
to recover the URI for the referenced CDBug
. BugSpotlightDelegate
creates the reference automatically when it calls attributeSet(for:)
.
In the body
of AppMain
, at the marker //add modifier here
, add this code:
.onContinueUserActivity(CSSearchableItemActionType) { activity in continueActivity(activity) }
This modifier is called when PointyBug opens as a result of an NSUserActivity
. Actions that use NSUserActivity
include Handoff and Spotlight. The identifier type is CSSearchableItemActionType
, which might seem odd, as the search result comes from the database of PointyBug. The key takeaway here is that Core Spotlight — not PointyBug — is the owner of the search result.
Build and run. When the app finishes launching, press Command-Shift-H to return to the Home Screen. Search for “cell” in Spotlight. You’ll see the record that matches in Spotlight if you scroll:
Tap the result. PointyBug will open and select that record. Your users now have a full journey back to their content from Spotlight. In the next part, you’ll add some images to the search results.
Including Image Thumbnails with Spotlight
PointyBug uses images to help show where bugs are. In this section, you’ll learn how to add images to your Spotlight results.
The code to render a full-size image already exists, but you need to provide a thumbnail of an appropriate size.
In the Project navigator, in the group Controller, open BugRenderer.swift. Then, add the following to the main class:
static func spotlightExport(_ bug: Bug) -> UIImage? { let renderer = BugRenderer(bug: bug, maxDimension: 270) return renderer.render(forSpotlight: true) }
Here, you ask the renderer to create an image of a maximum size of 270 x 270 pixels. The rendering code is implemented in render(forSpotlight:)
. To keep you focused on Spotlight, all you need to know for now is that UIGraphicsImageRenderer
paints the full size bug image into a rectangle of 270 × 270 pixels.
Next, in the group Core Data, open BugSpotlightDelegate.swift, and locate attributeSet(for:)
. Then, find the following line:
let attributeSet = CSSearchableItemAttributeSet(contentType: .text)
Replace that line with the following:
let attributeSet: CSSearchableItemAttributeSet if let thumb = BugRenderer.spotlightExport(bug), let jpegData = thumb.jpegData(compressionQuality: 0.8) { attributeSet = CSSearchableItemAttributeSet(contentType: .image) attributeSet.thumbnailData = jpegData } else { attributeSet = CSSearchableItemAttributeSet(contentType: .text) }
In this fragment, if possible, you generate a thumbnail image and then set thumbnailData
on a CSSearchableItemAttributeSet
with a content type of .image
. If there’s no image, you fall back to the original text only representation.
Build and run. Then, change the description of PB 001 to Image picker isn’t presented. Press Command-Shift-H to return to the Home Screen. Search for picker in Spotlight. You now have a little thumbnail image to go along with the search result:
You’ve learned how to create Spotlight data. Next, you’ll find out how to delete it.
Deleting Spotlight Data
There are many reasons to delete data — one example is the case of a user logging out of your app. The customer expects to have all their data erased and doesn’t want to see their personal data in a search result.
In the Project navigator, in the group Core Data, open CoreDataStack.swift. Then, add this method to CoreDataStack
:
func deleteSpotlightIndex(completion: @escaping (Error?) -> Void) { toggleSpotlightIndexing(enabled: false) spotlightIndexer?.deleteSpotlightIndex(completionHandler: completion) }
In this method, you stop the indexer and tell it to delete the Spotlight index. You don’t start the indexer again afterward because you’ll soon prove that the indexed data is gone. How you handle restarting the indexer after a delete in your own app will depend on your business case.
Next, in the group Controller, open BugController.swift. Then, add this code to the end of the file:
extension BugController { func deleteIndex() { dataStack.deleteSpotlightIndex { error in if error != nil { // TBD - handle error appropriately } else { print("*** Index erased successfully.") } } } }
This code provides an accessor for deleteSpotlightIndex(completion:)
in CoreDataStack
. The goal with this architecture is to ensure that the UI layer doesn’t know that Core Data exists.
In the group Views, open BugListView.swift. Next, inside the HStack
near the bottom of the view, locate the comment // Insert second button here
. Add the following at the mark to declare a button:
Button("DELETE INDEX") { bugController.deleteIndex() } .padding(8) .foregroundColor(.white) .background(Color.red) .lineLimit(1) .cornerRadius(10, antialiased: true)
Build and run. Now you have an extra button at the bottom of the main list:
That big red DELETE INDEX button sure is inviting. Tap the button, and then press Command-Shift-H to return to the Home Screen. Search for “bug” and you’ll notice nothing appears in the Spotlight search results, apart from a match on the PointyBug app itself.