Making Your App Searchable With CoreSpotlight in iOS 9

When you're on the Springboard screen in iOS, you can swipe down or left to search your device.

As of iOS 9, it's possible for developers to include the content within their app in the search index, meaning users can quickly access specific data in your app just by searching their phone.

Consider the screenshot below from our app Streaks. This shows how a user-created task ("Write Blog Post") can be found by searching (note that this feature is not actually in the app - it has just been created for the purposes of demoing this functionality!).

There are two main things that need to be done in order to add this functionality to your app:

  1. Include the data from your app in the search index
  2. Handle the action when the user selects an item

Managing the Index

To manage the items in the index, there are two primary steps:

  1. Adding new data
  2. Removing old data

You need to ensure that you add or remove data at appropriate times, depending on when data is added or removed within your app. You can add multiple items (or remove multiple items) with a single call.

To add or remove items from the index, you will need to use import CoreSpotlight in your code.

Adding New Searchable Items

When adding data, you need to build up a CSSearchableItemAttributeSet for each entry in the search index.

This object dictates which words are indexable (in other words, which words will the user needs to enter in order for the item to appear), and also how it looks when it appears.

let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeData as String)

There are many different types of data that can be indexed (such as contacts, images, text files, URLs, HTML), but this example uses kUTTypeData, which is used to describe generic data that doesn't have a better category based on those available.

Note: You will need to import MobileCoreServices to access the kUTTypeData value.

Next you need to populate the attribute data. The title value is displayed as the main value in the search results. You can also set a contentDescription value (although this isn't included in the above screenshot).

attributeSet.title = "Write Blog Post"
attributeSet.keywords = [ "write", "blog" ]

if let image = UIImage(named: "some-image") {
    attributeSet.thumbnailData = UIImagePNGRepresentation(image)
}

Note: Adding the given keywords in this example is redundant, since the title is also indexed, but it demonstrates how you can add other additional keywords to a search item.

Finally, create a CSSearchableItem object using a unique identifier for this data, as well as a domain identifier and the attributes defined above.

The unique identifier should be different for each item added to your index, and you should be able to then find the content in future using that value.

The domain identifier is a value associated with your app (although in theory, you could have multiple domain identifiers if you want to index multiple types of data).

This is the item added to the search index.

let identifier = "Your ID"
let domainIdentifier = "com.yourcompany.app.spotlight"

let searchableItem = CSSearchableItem(
    uniqueIdentifier: identifier,
    domainIdentifier: domainIdentifier,
    attributeSet: attributeSet
)

You can then add it to the search index using the following code:

let index = CSSearchableIndex.defaultSearchableIndex()
index.indexSearchableItems([ searchableItem ], completionHandler: { (error) in
    // Done
})

Once the item has been successfully added, you can return to springboard and attempt to search for it. If the above steps were correctly achieved, it should appear when you search with appropriate keywords (there may be other unrelated items also returned though, such as web searches or App Store results).

Removing Old Searchable Items

To remove one or more items, you need the identifiers used when adding them to the index.

let identifiers: [String] = [ "Your ID" ]

let index = CSSearchableIndex.defaultSearchableIndex()
        
index.deleteSearchableItemsWithIdentifiers(identifiers) { (error: NSError?) in
    // Done
}

You should remove items from the index when they are no longer relevant or available in your app.

Handling Search Item Selection

When a user taps on an item you have included in the index, the application:continueUserActivity:restorationHandler method is called.

extension AppDelegate {
    func application(application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> Bool {
        
        return true
    }
}

This method is also used for handoff, so you need to firstly check the activity type (CSSearchableItemActionType).

The unique identifier for the searchable item you added to the index in the previous section is available in the userInfo that is passed in the NSUserActivity object. It is retrievable using the CSSearchableItemActivityIdentifier key.

extension AppDelegate {
    func application(application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> Bool {
        if userActivity.activityType == CSSearchableItemActionType {
            if let uniqueIdentifier = userActivity.userInfo?[CSSearchableItemActivityIdentifier] as? String {

                // Handle the unique identifier here,
                // such as "Your ID"

            }
        }
        
        return true
    }
}