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.

4.7 (6) · 2 Reviews

Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

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:

Spotlight results

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:

Spotlight results with thumbnail

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:

Delete button in UI

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.