This sample app demonstrates how to make the activities and content of your app searchable via Spotlight, Safari and Siri by using new API's introduced in iOS 9 and supported by Titanium 5.0.0.
I highly recommend reading through all of our new Spotlight Search Guide as well as Apple's App Search Programming Guide and related documentation but here's the gist of it:
- Use NSUserActivity to Index Activities and Navigation Points on-device and make them available for private search, Siri and Handoff.
- Use the Core Spotlight Framework to Index App Content on-device and make it available for private search.
- Mark Up Web Content to index content on web pages and make it available for public search.
- Use Universal Links and Smart App Banners to enable users to open the current content or activity in your app.
- Combine APIs for NSUserActivity, Core Spotlight and Web Content Mark Up for the same content to increase coverage and ranking.
They say a picture says more then 5 bullets:
As you can see Apple wants users to seamlessly move between apps (via search), devices (via handoff) as well as between native apps and websites (via Safari search, universal links and handoff). Apple's programming guide has a nice list of Example Implementations for different types of apps to give an idea of how this might work for your app.
To show the APIs in action I've created the iOS App Search Sample App.
The first tab in the sample app shows you a list of The Beatles. The four individual band members will be indexed by SpotLight. The list itself is a user activity, but we'll come back to that later.
Quick Tip: I use a local instance of an definition-less model/collection which means I can populate (reset) the collection to use Alloy's data-binding on any array of objects.
Scroll down to line 53 of app/controllers/list.js
to see how I add the Beatles to the Spotlight index. There are three parts to it:
- Ti.App.iOS.SearchableItemAttributeSet to create meta data for a..
- Ti.App.iOS.SearchableItem which I add to an instance of..
- Ti.App.iOS.SearchableIndex
The attribute set has a huge amount of properties you can use to describe the item. Some let iOS play a song, call a phone number or navigate to an address directly from the Spotlight results without even opening your app.
In short, this is how you'd index a single item:
var index = Ti.App.iOS.createSearchableIndex();
index.addToDefaultSearchableIndex([
Ti.App.iOS.createSearchableItem({
uniqueIdentifier: 'my-id',
domainIdentifier: 'my.content.type',
attributeSet: Ti.App.iOS.createSearchableItemAttributeSet({
title: 'My Item'
})
})
], function (e) {
e.success || alert('Oops!');
});
Indexed items by default expire after one month unless you have set Ti.App.iOS.SearchableItem.expirationDate. You can also manually delete all items, items with a shared domainIdentifier
or specific items by uniqueIdentifier
.
The list in the sample app has a Trash/Add icon as the left navigation button to delete all items for the Beatles domain or re-index them. Search for appsearch
before and after to verify the change is effective immediately.
var index = Ti.App.iOS.createSearchableIndex();
index.deleteAllSearchableItemByDomainIdenifiers(['content.type'],
function (e) {
e.success || alert('Oops!');
}
);
When a user taps on a Spotlight search result, your app will open and receive the continueactivity event.
Be aware that this event is also fired when a User Activity is opened from the search results or handed off from another device. In the case of a Spotlight search result the event's activityType
property will be com.apple.corespotlightitem
and searchableItemActivityIdentifier
will have the uniqueIdentifier
you've set on the indexed item.
From line 141 you can see how to use this information to navigate your app to the content the user requested. In our case we share an openDetail() helper function with the ListView's itemclick
listener to look up the model and open the detail window.
In short:
Ti.App.iOS.addEventListener('continueactivity', function(e) {
// Not for us
if (e.activityType !== 'com.apple.corespotlightitem') {
return
}
var uniqueIdentifier = e.searchableItemActivityIdentifier;
// Navigate to the content
});
The NSUserActivity class introduced in iOS 8 to enable Handoff can now also be included in both private and even public search results. The new Ti.App.iOS.UserActivity API in Titanium 5.0 gives you access to all these features.
There's a very subtle difference between indexing for on-device search using Core Spotlight and User Activities. Think of indexing User Actives as tracking the pages users have visited, where Core Spotlight allows you to index the actual content that might be on one or more of these pages. We'll come back to how they work together later.
In the app we track two user activities. The first is the activity of viewing the list of Beatles and the other is the activity of viewing a individual Beatle's details.
Both are created in the same way in list.js from line 233 and most of detail.js.
As you can see we listen to the focus
and blur
events of both windows to create and invalidate (end) the related activity. Once invalidated the activity cannot become current again and must be re-created.
The activity itself is uniquely identified by a reverse-domain activityType
. The current state of the activity is saved to userInfo
. In case of the detail window this is where we save the model ID of the Beatle we're viewing. While handoff is enabled by default, we need to set eligibleForSearch:true
to have Spotlight index the activity.
If you search for appsearch you should find the list activity as The Beatles.
When you compare lines 73-88 in list.js
and lines 61-79 in detail.js
you will see that we can describe both a Spotlight item and a User Activity using the same Ti.UI.SearchableItemAttributeSet
. In detail.js
we also set the relatedUniqueIdentifier
property. This prevents duplicate search results and makes that every time the activity becomes current it will count as a pageview for the related Spotlight item and improve its ranking.
From line 141 of list.js you can see we handle a user activity search result in almost the same way as for other Spotlight items. We use the activityType
to identify it and act accordingly.
The exact same continueactivity
event is also what handoff will fire so we get that for free! Install the app on two devices and double-tap home to try it out:
Since the sample is not in the App Store I cannot fully demonstrate how to combine the NSUserActivity and Core Spotlight APIs with Web Content Mark Up and Universal Links. Fortunately, Apple has a excellent guide on this topic.
I already showed you how to combine NSUserActivity and Core Spotlight APIs for the same content. In the same way you can link Web Content as well.
-
First, set
Ti.App.iOS.UserActivity.webpageURL
to the related webpage URL and use the same value for the Spotlight item'suniqueIdentifier
which in turn is linked to the activity viarelatedUniqueIdentifier
. -
Then set the activity's
eligibleForPublicIndexing:true
as well as the requiredrequiredUserInfoKeys
property. Eligible means that a user activity on its own will never show up in search results. It will Enhance Your Search Results for Web Content by counting as pageviews for the related content. -
Last but not least follow Apple's guide to Mark Up Web Content and use the App Search API Validation Tool to verify if you've set it all up correctly.
Another benefit of setting webpageURL
is that you can now handoff a user activity to a device (including desktops) that doesn't have the app, in which case it will open the website instead. We will come back to that in a separate Handoff sample.