Adding CSSearchableItem instances to the search index

In the MustC application, the goal is to add family members and movies to the search index as soon as the user adds them. A factory method that creates the CSSearchableItemAttributeSet instance that describes the item that should be indexed is already provided. However, you can't directly add these to the index. To add information to the search index manually, you need instances of CSSearchableItem. Every searchable item requires a unique identifier and a domain identifier.

The unique identifier is used to identify an indexed item. It's essential that you set this value to something that is unique because otherwise, Spotlight will overwrite the entry with something else that has the same identifier or you'll get duplicate entries if you combine user activities and search items like we're doing for MustC.

The domain identifier functions as a namespace. Within any given namespace, all entries must be unique and are identified through their unique identifier. These identifiers only have to be unique within their namespace. Think of this as streets and addresses. In a particular area, every street name is unique (domain, namespace). On each street the house number is unique (unique identifier), but the same number can occur on different streets. The domain identifier for your Spotlight entry is not only used to identify entries uniquely, it's also used to perform specific batch actions on the index, such as deleting all indexed items from a particular domain.

The domain identifier, unique identifier, and the attributes together make up a searchable item. The following code adds factory methods to IndexingFactory that will make it simple for the app to add items to the search index:

enum DomainIdentifier: String {
  case familyMember = "FamilyMember"
  case movie = "Movie"
}

static func searchableItem(forMovie movie: Movie) -> CSSearchableItem {
  let attributes = searchableAttributes(forMovie: movie)

  return searachbleItem(withIdentifier: "(movie.objectID.uriRepresentation().absoluteString)", domain: .movie, attributes: attributes)
}

static func searchableItem(forFamilyMember familyMember: FamilyMember) -> CSSearchableItem {
  let attributes = searchableAttributes(forFamilyMember: familyMember)

  return searachbleItem(withIdentifier: "(familyMember.objectID)", domain: .familyMember, attributes: attributes)
}

private static func searachbleItem(withIdentifier identifier: String, domain: DomainIdentifier, attributes: CSSearchableItemAttributeSet) -> CSSearchableItem {
  let item = CSSearchableItem(uniqueIdentifier: identifier, domainIdentifier: domain.rawValue, attributeSet: attributes)

  return item
}

The preceding code defines an enum that contains domain identifiers for items that should be indexed. Note that searchableItem(withIdentifier:domain:attributes:) is marked as private. This is done to make sure anybody using the factory helper has to use searchableItem(forFamilyMember:) and searchableItem(forMovie:) instead. These methods are simpler to use because they only take a family member or a movie and if you use only these methods, you can rest assured that you only insert consistently configured items into the Spotlight index.

Now that everything is set up for indexing new data, let's begin with indexing family members as soon as the user creates them. Add the following code as the implementation for the .insert case in controller(_:didChange:at:for:newIndexPath:) in FamilyMembersViewController. Make sure that you import CoreSpotlight at the top of your file:

guard let insertIndex = newIndexPath,
  let familyMember = fetchedResultsController?.object(at: insertIndex)
  else { return }

let item = IndexingFactory.searchableItem(forFamilyMember: familyMember)
CSSearchableIndex.default().indexSearchableItems([item], completionHandler: nil)

tableView.insertRows(at: [insertIndex], with: .automatic)

Due to the factory methods you set up before, new items can be added to the search index with just a few lines of code. To insert an item into the search index, an instance of CSSearchableIndex is created, and Spotlight is told to index this item. If needed, a completion-handler can be passed to the index method. This handler is called with an optional error. If the indexing has failed, the error should tell you why Spotlight couldn't index the item, and you can retry indexing the item or take a different action. In the MustC app, it is assumed that the indexing succeeds and no error-handling is implemented.

Update the mangedObjectContextDidChange(notification:) method in MoviesViewController as follows to index new movies:

@objc func managedObjectContextDidChange(notification: NSNotification) {
  guard let userInfo = notification.userInfo
    else { return }

  if let updatedObjects = userInfo[NSUpdatedObjectsKey] as? Set<FamilyMember>,
    let familyMember = self.familyMember,
    updatedObjects.contains(familyMember) {

    let item = IndexingFactory.searchableItem(forFamilyMember: familyMember)
    CSSearchableIndex.default().indexSearchableItems([item], completionHandler: nil)

    tableView.reloadData()
  }

  if let updatedObjects = userInfo[NSUpdatedObjectsKey] as? Set<Movie> {
    for object in updatedObjects {
      if let familyMember = self.familyMember,
        let familyMembers = object.familyMembers,
        familyMembers.contains(familyMember) {

        let item = IndexingFactory.searchableItem(forMovie: object)
        CSSearchableIndex.default().indexSearchableItems([item], completionHandler: nil)

        tableView.reloadData()
        break
      }
    }
  }
}

Right after the movie is added to the current family member, it is added to the Spotlight index. You might argue that the item is added too early because the movie's rating has not been fetched at that point. This is OK because when the context is saved the second time with the ratings attached, the item is added to the index again automatically due to the save notification that Core Data sends.

If you run the app now, you should be able to go into Spotlight right after you add a family member or movie, and you should immediately be able to find the freshly added content in Spotlight. If you search for a movie you just added, you'll notice that you can see how many family members have added a certain movie to their favorites list and the rating a movie has. More importantly, there should be only a single entry for each movie because a unique identifier is used to avoid duplicate entries.

Now that you have implemented CSSearchableItem and NSUserActivity indexing, you need to make sure that both of these objects are correctly configured so you can safely combine both indexing methods.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.233.41