Containing information in CSSearchableItemAttributeSet

The attributes set are populated correctly since they describe almost all of the important information for Spotlight. You can set a title, content description, a thumbnail image, keywords, even ratings or phone numbers, GPS information, and much, much more. For a full overview of what's possible, refer to the CSSearchableItemAttributeSet documentation. Every time you are about to create a new item that can be indexed, you should take a look at the documentation to make sure you don't miss any attributes.

The better use you make of the available attributes, the more effectively your content can be indexed and the higher your app will rank. Therefore, it's worth putting slightly more time and effort into your search attributes because getting it wrong can be a costly mistake, especially considering the available documentation. At a minimum, you should always try to set title, contentDescription, thumbnailData, rating, and keywords. This isn't always relevant or even possible for the items you're indexing, but whenever possible make sure that you set these attributes.

You may have noticed that the NSUserActivity instances we indexed in our app didn't receive any special attributes. We just set a name and some other basic information, but we never added a description or a rating to any of the indexed objects. If you're indexing user activities in your own applications, it's worth noting that user activities can and should have attributes associated with them. All you need to do is set the contentAttributeSet property on the user activity. After we implement indexing through CSSearchableItem, we'll shortly revisit user activity indexing to make the indexed item richer and also to make sure that CoreSpotlight understands that the user activities and searchable items point to the same underlying index in Spotlight.

Whenever we index items through multiple methods, it's inevitable that we'll run into data duplication. The application we're working on right now indexes every visited screen. So, if a user visits the details page of a movie, we will index a user activity that points to that specific movie. However, we also want to index movies as they are created by the user. To avoid duplicate results in Spotlight search, we should add a relatedUniqueIdentifier to the attributes set. Setting this attribute on a user activity makes sure that Spotlight doesn't add duplicate entries for items with the same identifier.

Let's expand the IndexingFactory with two methods that can generate attribute sets for searchable items. Putting this functionality in the IndexingFactory as a separate method is a good idea because, if it is set up correctly, these methods can be used to generate attributes for both user activities and searchable items. This avoids code duplication and makes it a lot easier to add or remove properties in the future. Add the following methods to the IndexingFactory struct:

static func searchableAttributes(forMovie movie: Movie) -> CSSearchableItemAttributeSet {
do {
try movie.managedObjectContext?.obtainPermanentIDs(for: [movie])
} catch {
print("could not obtain permanent movie id")
}

let attributes = CSSearchableItemAttributeSet(itemContentType: ActivityType.movieDetailView.rawValue)
attributes.title = movie.name
attributes.contentDescription = "A movie that is favorited by \(movie.familyMembers?.count ?? 0) family members"
attributes.rating = NSNumber(value: movie.popularity)
attributes.identifier = "\(movie.objectID.uriRepresentation().absoluteString)"
attributes.relatedUniqueIdentifier = "\(movie.objectID.uriRepresentation().absoluteString)"

return attributes
}

static func searchableAttributes(forFamilyMember familyMember: FamilyMember) -> CSSearchableItemAttributeSet {
do {
try familyMember.managedObjectContext?.obtainPermanentIDs(for: [familyMember])
} catch {
print("could not obtain permanent family member id")
}

let attributes = CSSearchableItemAttributeSet(itemContentType: ActivityType.familyMemberDetailView.rawValue)
attributes.title = familyMember.name
attributes.identifier = "\(familyMember.objectID.uriRepresentation().absoluteString)"
attributes.contentDescription = "Family Member with \(familyMember.favoriteMovies?.count ?? 0) listed movies"
attributes.relatedUniqueIdentifier = "\(familyMember.objectID.uriRepresentation().absoluteString)"

return attributes
}

For both objects, a set of attributes is created. This set meets Apple's recommendations as closely as possible. We don't have any thumbnail images or keywords that we can add besides the movie name or the name of a family member. Adding these to the keywords is kind of pointless because the title in itself is essentially a keyword that our item will match on.

Note that we use the objectID property as a means of uniquely identifying objects. Because the factory methods is called before the objects are assigned a permanent ID, we could be in trouble. Objects are assigned a permanent ID when they are saved or if we explicitly tell the managed object context to obtain permanent IDs.

We need to do this to ensure that the objectID does not change at a later time. The objectID property is available on all managed objects and is the most reliable and convenient way for us to make sure that we have a unique identifier available.

To create an attribute set, all we have to do now is call the method that matches the object we want to index and we're good to go. Nice, convenient, and simple.