In this episode we take a look at how to provide deep search in our iOS applications. We start by leveraging NSUserActivity to provide relevant search results for recent activities to users. Then we look at how to customize the search results with rich metadata. Finally we look at using CoreSpotlight to fully index the content to make it available for search after we launch our app.
Episode Links Source Code NSUserActivity Class Reference CoreSpotlight Framework Reference Indexing User Activities userActivity = NSUserActivity(activityType: "com.companyroster.viewcontact") userActivity?.requiredUserInfoKeys = ["contactName", "contactDepartment"] userActivity?.title = contact.name userActivity?.delegate = self userActivity?.eligibleForHandoff = false userActivity?.eligibleForSearch = true userActivity?.eligibleForPublicIndexing = false userActivity?.becomeCurrent() Then, make sure to provide the items in userInfo when it is about to be saved: func userActivityWillSave(userActivity: NSUserActivity) { userActivity.addUserInfoEntriesFromDictionary([ "contactName": contact!.name, "contactDepartment": contact!.department ]) } In my testing, setting userInfo on the view controller's userActivity property didn't seem to have an effect unless I did this. For your own custom (strongly held) NSUserActivity properties it worked as expected. If you do this, make sure you have a strong reference to the user activity, and that you properly call resignCurrent when the view is going away. the built-in property does this for you. Enrich Search Results with Custom Metadata to provide more than a title and your app icon, you can provide a searchable attribute set that gives you more control over how search results are displayed. First, we define an extension that gives us our searchable attribute set: import CoreSpotlight import UIKit extension Contact { func searchableAttributeSet() -> CSSearchableItemAttributeSet { let attr = CSSearchableItemAttributeSet(itemContentType: "com.companyroster.contact") attr.title = name attr.keywords = ["nsscreencast", department] attr.contentDescription = "In department: \(department)" attr.thumbnailData = UIImageJPEGRepresentation(UIImage(named: imageName)!, 1.0) return attr } } Then set these attributes on the user activity: userActivity?.contentAttributeSet = contact.searchableAttributeSet() Indexing All Records For content-based applications like this, it's a better idea to index all the content at once, so the user doesn't have to visit the screen to have it indexed. To do this, we'll use CoreSpotlight directly. func indexContacts() { CSSearchableIndex.defaultSearchableIndex().deleteAllSearchableItemsWithCompletionHandler(nil) let contacts = ContactsStore().getContacts() let items = contacts.map { c in return CSSearchableItem(uniqueIdentifier: c.name, domainIdentifier: c.department, attributeSet: c.searchableAttributeSet()) } CSSearchableIndex.defaultSearchableIndex().indexSearchableItems(items) { (error) in if error == nil { print("Indexed \(contacts.count) contacts.") } else { print("Error: \(error)") } } } Place this in your AppDelegate.swift and then your content will be searchable upon first launch.