IOS Search API

#Search Drives User Engagement

Search in iOS 9 gives people great ways to access information inside of your app, even when it isn’t installed.

When you make your content searchable, users can access activities and content deep within your app through Spotlight and Safari search results, Handoff, Siri Suggestions, and Reminders. Making your content searchable helps you enhance the user experience of your app and improve its discoverability.


App search is easy for you to adopt and customize.

You don’t need any prior experience with implementing search, and you control what content gets indexed, which information to show in search results, and where the user goes after tapping a result related to your content.

兩種幫助使用者保護隱私的 索引方式 (Indexes)

隱私是 iOS 9 基礎的搜尋功能。

它給予使用者最佳的搜尋體驗並保護他們的私人資料,Apple 提供了兩種 索引方式,能儲存可搜尋的項目相關於的你的內容。
there are two indexes that can store searchable items related to your content.

你可以使用許多 搜尋相關 (search-related) APIs 對每個項目指出 相近的索引,所以了解每個索引的運作方式是非常重要低。

iOS 9 提供下列索引方式:

  • 裝置上的私有索引(A private on-device index)
    每個裝置包含了私有索引(一種資訊且它並不會分享給 Apple 或 同步到其他裝置上)。
    當你在使用者裝置上的索引建立了有效的項目,只有那位使用者能在搜尋結果看到那筆 項目 (item)。

  • Apple Server 方的索引 (Apple’s server-side index)
    Server 方的索引只有儲存在你網站上標示的公開有效資料。

Search Consists of Several Technologies

為了讓使用者有更好的搜尋體驗並提高她們對的你的 APP 的 engagement,你可以結合不同的 APIs 和 技術。

在你開始之前,評估你的內容並決定要儲存的內容 跟 誰應該能夠訪問這筆資訊。

基於你的調查,選擇你該採用的 APIs 和技術。


iOS 9 introduces several APIs that help you make your content available in the appropriate index.

  • NSUserActivity 有新的方法及屬性,能幫助你index items as users perform activities in your app,such as visiting a navigation point or creating and viewing content.
  • The Core Spotlight framework provides APIs that help you add app-specific content to the on-device index and enable deep links into your app.
  • Web markup lets you make your related web content searchable and helps you enrich the user’s search experience.

NOTE
Although app search is available for iOS 9 users, the search functionality of NSUserActivity and Core Spotlight is not supported on iPhone 4s, iPad 2, iPad (3rd generation), iPad mini, and iPod touch (5th generation).


非常建議所有的 APP 使用 NSUserActivity 且它們支援 Handoff

使用 NSUserActivity 搜尋相關的屬性 是展示給使用者他們關注的資訊最好的方式 並 提高你的搜索結果排名。

當使用者在你的 APP 執行一個活動,你可以使用 NSUserActivity 把這個項目加入到裝置上的索引。

在預設情況下, NSUserActivity 表現的項目是屬於私有的,這表示說這項目被加到裝置索引。

如果你內容物包含了讓所有使用者能夠觀看的活動,you identify them as eligible for public indexing.


如果你的 APP 處理使用者儲存的資料,例如: 文件 照片 或其他型態的資料(使用者創建的或使用者參與一半),可以使用 Core Spotlight APIs 對內容物上索引。

並不像 NSUserActivityCore Spotlight 並不需要為了使用者訪問內容物,特別對內容物上索引

此外,你可以在任何地點用 CoreSpotlight APIs 索引內容物,例如當應用程序加載。

Core Spotlight 能幫你讓項目在裝置上變得可搜尋;你不必用 Core Spotlight APIs 讓項目變成 可公開搜尋。


如果你的 APP 主機網站有內容物(一些或全部),使用 web markup 讓 Apple 的 web crawler (called Applebot) 在 APPLE Server 索引你的內容,並且讓你的內容讓所有 iOS 使用者在 Spotlight and Safari search results 搜尋的到。

Note that search results generated by web markup are shown in countries that are supported by Spotlight Suggestions.


Regardless of the APIs you use to make content searchable, you can also let users take actions directly from a search result, such as making a phone call and getting directions to a location.

Users can get the actions you enable in views such as these:

Make a phone call Get directions to a location

除了使用 NSUserActivity Core Spotlight web markup,你應該使用三個關鍵技術來給予使用者更好的體驗。

  • Universal links
    在 iOS 9 之後, 使用 universal links 來取代 custom URL schemes with standard HTTP or HTTPS links
    Universal links work for all users: 如果使用者有安裝你的 APP, 這條 link 會帶領他們直接到妳的 APP; 反之如果他們沒有安裝你的 APP, 這條 link 會用 Safari 打開你的網站。
  • Smart App Banners
    當使用者用 Safari 瀏覽你的網站,Smart App Banner 可以讓他們打開你的 APP (如果有安裝) 或 獲得他們下載妳 APP 的機會(如果沒裝)。
  • Handoff
    Handoff lets users continue an activity from one device to another.
    For example, when browsing a website on their Mac, they can jump straight to your native app on their iPad.
    In iOS 9, Handoff includes specific support for app search. To learn more about supporting Handoff, see Handoff Programming Guide.

Several Factors Determine the Ranking of Search Results

Adopting the search-related APIs appropriately can improve the relevancy and ranking of the search results related to your content.

To give users the best search experience, the system measures how often users interact with app content and with Spotlight and Safari results.

Because iOS measures the level of user engagement with search results, items that users don’t find useful are quickly identified and can eventually stop showing up in results.


iOS determines relevancy and ranking using information such as:

  • The frequency with which users view your content (which is captured only through the use of NSUserActivity)
  • The amount of engagement users have with your content, demonstrated when users tap a search result or find the information useful
  • In the case of marked-up web content, the popularity of a URL and the amount of structured data available

At a high level, you can provide great search results and encourage people to use your app by doing the following things:

  • Create an app with compelling content.
  • Improve the relevance of your search results by adopting the search-related APIs just described.
  • As much as possible, index only the items that users are most likely to search for.
  • Keep the indexes up to date by removing and updating items when necessary.
  • Encourage users to engage with an indexed item by providing relevant, rich information about it.
  • Minimize the time between tapping on a search result and viewing the content in your app.

To learn about ways to improve the user’s search experience, see Combine APIs to Increase Coverage and Improve the Ranking of Your Results.


Example Implementations

實現搜尋的方式取決於妳的 APP 的 使用模型及使用者經驗(usage model and user experience)。

At a high level,你的實作決策建立在幾個主要問題上面:

  • Do you have a website that hosts some or all of the content that’s in your app?
  • Does your app contain user-generated or user-specific data?
  • Does your app contain content that users are likely to want to revisit?

Travel

In terms of search functionality, a travel app is similar to a ticket sales app, because it helps users plan for future events and buy related items.

In addition, users typically expect a travel app to provide information about locations, events, and transportation options while also helping them access their private booking or receipt information.

Table 2-4 lists some examples of how a travel app can use search APIs.

API Example usage
NSUserActivity
Core Spotlight 預約
使用者附近位置的地點
餐廳,酒店或其他被使用者標示為最愛的地點
Web markup
Use universal links to let users open your app when they tap a link to your website.

Index Activities and Navigation Points

NSUserActivity 提供了讓你捕捉特定 APP 狀態的方法且 navigation points navigation points that the user has previously visited and then restore them later using Handoff (to learn more about enabling Handoff in your app, see Handoff Programming Guide).

iOS 8 之後的 APP,使用者預期 Handoff 能幫助他們從一個裝置上開始一個活動 並且在下個裝置上繼續使用。

除了支援 Handoff, 在 iOS 9 之後使用 NSUserActivity 能讓你:

  • Index app items as users perform activities in your app.
    Activities could include creating or viewing content, viewing a set of items (such as a results list), or visiting a navigation point within your app.

  • Mark specific items as available for public searching (for some examples of items that can be appropriate for public searching, see Example Implementations)

  • Provide indexable metadata about an item, which gives users rich information in search results

使用 NSUserActivity APIs 也讓你在 Siri suggestions 和智能提醒(smart reminders) 處於優勢。

Siri suggestions 顯示在 Spotlight search screen 且能囊括可搜尋的活動searchable activities

(注意:只有 high engagement ratio 的活動才有資格顯示在 Siri suggestions 內容中。)

使用者可以使用 Siri smart reminders 提醒一下有特定相關的內容在你的 APP 內。

當使用者收到 smart reminder, the activity they specified is displayed in the reminder.

As the user uses your app, you create activity objects associated with various navigation points and app states. Each item is added to the on-device index by default. In iOS 9, marking a public item as eligible for public indexing also adds it to the on-device index and confers an additional advantage: When you use web markup to make your related website content searchable, user engagement with publicly eligible search results from your app can help improve the ranking of your website’s content. When a user taps a searchable activity or state in Spotlight search results, you use NSUserActivity APIs to continue the activity and return the user to the relevant area in your app.

建立可搜尋的活動 (Creating Searchable Activities)

activity or navigation point 變得可搜尋,建立 NSUserActivity 物件 來呈現它。

使用 NSUserActivity 屬性來標示項目的型態,提供 metadata 來描述它,並讓它夠資格被搜尋。

將一個項目設定有資格被搜尋,代表這個項目被加到裝置索引上 when the item becomes current.

Listing 3-1 Creating an activity
1
2
3
4
5
6
7
8
9
// It's recommended that you use reverse DNS notation for the required activity type property.
var activity:NSUserActivity = NSUserAcivity(activityType: "com.myCompany.myContentType")

// Set properties that describe the activity and that can be used in search.
activity.title = "My Activity Title"
activity.userInfo = ["id": "http://www.mydomain.com/myContentItem/ABC-123"]

// Add the item to the private on-device index.
activity.eligibleForSearch = true

雖然沒有展示在 Listing 3-1, NSUserActivity 也定義了內容屬性集 contentAttributeSet 的屬性,which lets you specify as many attributes as you need to describe an item.

contentAttributeSet 使用 CSSearchableItemAttributeSet 物件,你可以使用 Core Spotlight 物件 來提供 indexable metadata that enriches search results

Core Spotlight defines a large number of properties that specify metadata in several topic areas, such as media, events, and messages.

只有 title userInfocontentAttributeSet 是必須的,但是為了給使用者更好的體驗,還是建議你盡可能低提供越多的描述越好。

特別是建議你可以總是提供 thumbnailData and contentDescription(content-specific values)


三個特別提到的 NSUserActivity 授權(warrant)屬性:

  • eligibleForPublicIndexing
  • expirationDate
  • webpageURL

活動 Activities 預設情況下是 private

當你設定項目的 eligibleForPublicIndexing 並且你使用 web markup 讓你的網站上的相關內容物變得可搜尋,user engagement with the item can help improve the ranking of your website’s content.


如果你沒有設定適當的過期時間 expirationDate,系統過一段時間後會自動將這項目變成過期。


當你的 APP 內容物同時在你的有效網站 且你分別在妳的 APP 用 NSUserActivity APIs 跟 在你的網站上用 web markup時,而 webpageURL 屬性是相當有用的。

特別是你可用 webpageURL 來避免相同項目重複索引的問題。

當你設定 webpageURL 屬性,同時設定 requiredUserInfoKeys 屬性,using the keys of the userInfo dictionary that must be stored.

如果你沒有設定 requiredUserInfoKeys,當活動被儲存的時候 userInfo dictionary 將會是空的。


當使用者執行了活動 或 enters the app state associated with the NSUserActivity object you created,你的 APP 會呼叫 becomeCurrent 方法 來標示活動為目前活動。

目前活動會有資格被搜尋且自動被加到隱私裝置索引(就是 CSSearchableIndex)

此外你可以用搜尋結果來啟動 user actions,例如打電話或導引導某地點。


為了保證 活動和它的 metadata 被標上索引,你必須對活動保持強引用(strong reference)直到他被標上索引。

這邊有兩種方法:

  • 第一種方法就是在 controller 建立活動,並 assign 活動為 controller 的屬性。
  • 第二種方法使用 UIResponder 物件的 userActivity
    如果你使用第二種方法,你必須設定 the metadata—such as information in the userInfo property—in the updateUserActivityState: method;
    否則,你設定在活動的 metadata 將不會被儲存。
ViewController
1
self.userActivity = activity

如果你想要活動有資格被搜尋但是並不是為了用 Handoff

1
2
activity.eligibleForSearch = true
activity.eligibleForHandoff = false

Use Core Spotlight APIs to remove items you indexed using NSUserActivity.

When an item is indexed using both NSUserActivity and Core Spotlight APIs and the item is connected using the relatedUniqueIdentifier property, removing the item by using the Core Spotlight APIs makes the activity ineligible for indexing.

For more information about using the relatedUniqueIdentifier property, see Combine APIs to Increase Coverage.


Continuing Activities Chosen in Search Results

當使用者從搜尋結果中點擊你添加索引的 NSUserActivity item,你的 APP 應當開啟且回復到相關於那項目的情境(context)。

為了達成這個功能,你的 app delegate 要實做 application:continueUserActivity:restorationHandler:,檢查近來的活動的型態是因為使用者點擊在搜尋結果中被索引的項目而打開 APP。

這個 application:continueUserActivity:restorationHandler: 方法同 Handoff

Continuing a user activity
1
2
3
4
5
6
func application(UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: [AnyObject]? -> Void) -> Bool {
if userActivity.activityType == "com.myCompany.myContentType" {
// Restore app state for this userActivity and associated userInfo value.
}
return true
}

Index App Content

The Core Spotlight framework provides ways to index the content within your app and manage the private on-device index. Core Spotlight works best for indexing user-specific content, such as friends, items marked as favorite, purchased items, and so on. When you use Core Spotlight APIs to make items searchable, you make it easy for users to find their content in Spotlight search results.

At a high level, Core Spotlight APIs help you provide a comprehensive description of an item and to add it to the on-device index. In addition, CoreSpotlight APIs support indexing items at any time, such as when your app loads.

When users tap an indexed item in Spotlight search results, your app delegate’s application:continueUserActivity:restorationHandler: method is called (this is the same method you implement to support Handoff). In this scenario, the activity type your app delegate receives is CSSearchableItemActionType, which helps you determine the app content to show the user. Creating Searchable Items describes the process in more detail.

In addition to using Core Spotlight APIs to make app content searchable, you should also use NSUserActivity APIs to make app states and navigation points searchable (you can learn more about indexing activities in Index Activities and Navigation Points). Both APIs use the same object to provide metadata about searchable items (that is, a CSSearchableItemAttributeSet object), and both give you ways to prevent duplication of items in search results. Unlike the NSUserActivity API, however, Core Spotlight APIs don’t require users to visit the content before it gets indexed. Learn more about the advantages of using multiple search-related APIs in Combine APIs to Increase Coverage.

Creating Searchable Items

To make content searchable, first create an attribute set containing properties that specify the metadata you want to display about an item in a search result. The attributes you choose vary, depending on your domain: You can use the attributes that Core Spotlight provides in categories defined on CSSearchableItemAttributeSet, or you can define your own. (If you want to define a custom attribute, be as specific as possible in your definition and use the contentTypeTree property so that your custom attribute can inherit from a known type.)

Next, create a CSSearchableItem object to represent the item and add it to the on-device index. When you create a searchable item, you must specify a unique identifier so that you can access the item in the index. (Note that you need to specify an appropriate expiration date for items that should remain searchable after a period of time; for more information, see expirationDate.) You can also specify a domain identifier that identifies a group or owner for the item and lets you access groups of items at the same time. Listing 4-1 shows how to create a CSSearchableItem object and index it.

Creating a searchable item and adding it to the on-device index
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Create an attribute set to describe an item.
let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeData as String)
// Add metadata that supplies details about the item.
attributeSet.title = "July Report.Numbers"
attributeSet.contentDescription = "iWork Numbers Document"
attributeSet.thumbnailData = DocumentImage.jpg

// Create an item with a unique identifier, a domain identifier, and the attribute set you created earlier.
let item = CSSearchableItem(uniqueIdentifier: "1", domainIdentifier: "file-1", attributeSet: attributeSet)

// Add the item to the on-device index.
CSSearchableIndex.defaultSearchableIndex().indexSearchableItems([item]) { error in
if error != nil {
print(error?.localizedDescription)
}
else {
print("Item indexed.")
}
}

Using the code shown in Listing 4-1, an item that represents a Numbers document is added to the on-device index. When the user searches for part of the document’s title, the search results look something like this:

When the user taps on a searchable item from your app in Spotlight search results, your app delegate’s application:continueUserActivity:restorationHandler: method is called. In your implementation of this method, check the type of the incoming activity to confirm that your app is opening because the user tapped an indexed item in a search result. Listing 4-2 shows a skeletal implementation of application:continueUserActivity:restorationHandler:.

Continuing the activity that represents the searchable item
1
2
3
4
5
6
7
8
9
func application(UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: [AnyObject]? -> Void) -> Bool {
if userActivity.activityType == CSSearchableItemActionType {
// This activity represents an item indexed using Core Spotlight, so restore the context related to the unique identifier.
// Note that the unique identifier of the Core Spotlight item is set in the activity’s userInfo property for the key CSSearchableItemActivityIdentifier.
let uniqueIdentifier = userActivity.userInfo? [CSSearchableItemActivityIdentifier] as? String
// Next, find and open the item specified by uniqueIdentifer.
}
return true
}

The CSSearchableItemAttributeSet class also helps you support actions that users can take from an item in search results; specifically, you can let users make a phone call or get directions to a location. To enable a phone call, set the item’s supportsPhoneCall property to 1 and specify a phone number in the phoneNumbers property. Be sure to enable a phone call action only when it’s appropriate and it represents the primary action a user is likely to take. For example, it makes sense to let users call a business, but it doesn’t make sense to let users call a phone number that happens to be displayed on a research paper.

To help users get directions to a location described in a search result, set the item’s supportsNavigation property to 1 and supply values for the latitude and longitude properties. As with the phone call action, be sure to enable the navigation action only when it makes sense. For example, you wouldn’t enable navigation for a person’s photo, but you could enable navigation for an item that represents a restaurant review.

Maintaining the Index

Keeping the on-device index up to date is critical for ensuring that search results from your app remain relevant. When search results contain relevant items, users are more likely to engage with them, which in turn helps to increase the ranking of your searchable items.

Core Spotlight provides several APIs you can use to work with the index and keep it up to date. For example, Listing 4-3 shows how to update the index, using the same method you use to add new items to the index.

Updating the index
1
func indexSearchableItems(items: [CSSearchableItem], completionHandler: ((NSerror?) -> Void)?)

You also use the indexSearchableItems:completionHandler: method to update an item’s title, description, or any other property.

To ensure that only relevant items are returned in search results, you may need to delete indexed items. Core Spotlight provides a few ways to remove items from the index, three of which are shown in Listing 4-4.

Three ways to delete items from the on-device index
1
2
3
4
5
6
7
8
// Delete the items represented by an array of unique identifiers.
func deleteSearchableItemsWithIdentifiers(identifiers: [String], completionHandler: ((NSError?) -> Void)?)

// Delete the items represented by an array of domains.
func deleteSearchableItemsWithDomainIdentifiers(domainIdentifiers: [String], completionHandler: ((NSError?) -> Void)?)

// Delete all items from the on-device index.
func deleteAllSearchableItemsWithCompletionHandler(completionHandler: ((NSError?) -> Void)?)

Using Advanced Features

Core Spotlight supports advanced features that help you batch updates to the index and, in case there was a problem indexing your data, perform index updates while your app isn’t running. For example, Core Spotlight includes support for data protection classes that help you choose the appropriate security policy for the information that you’re indexing (to learn more about using protection classes, see initWithName:protectionClass:). To take advantage of Core Spotlight advanced features, be sure to set the indexDelegate property in your app and implement the required methods of CSSearchableIndexDelegate.

When you have to index multiple items, you can break the task into batches and save the state of your indexing process. Listing 4-5 shows how to perform a batched update to the index.

Batching updates to the index
1
2
3
4
5
6
7
CSSearchableIndex *index = [CSSearchableIndex new];
[index beginIndexBatch];
[index indexSearchableitems:items completionHandler:nil];
[index deleteSearchableItemsWithIdentifiers:identifiers completionHandler:nil];
[index endIndexBatchWithClientState:clientState completionHandler:^(NSError *error) {
// Handle errors.
}];

To resume indexing, use the value of the clientState parameter to determine where to restart, as shown in Listing 4-6.

Restarting a batched update to the index
1
2
3
4
5
6
7
8
9
10
11
12
13
CSSearchableIndex *index = [CSSearchableIndex new];
[index fetchLastClientStateWithCompletionHandler:^(NSData *clientState, NSError *error) {

if(error == nil) {
NSArray *items = // Fetch a batch of items for the specified client state.
[index beginIndexBatch];
[index indexSearchableitems:items completionHandler:nil];
[index endIndexBatchWithClientState:clientState completionHandler:^(NSError *error) {
// Handle errors.
}];
}

}];

Core Spotlight defines an app extension you can implement to index items when your app isn’t running. The system calls this extension when the index is lost or there was a problem saving data that had been indexed, which can happen if the app crashes. Listing 4-7 shows one way to write this type of app extension.

Listing 4-7 Creating an index-maintenance app extension
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@interface MyIndexRequestExtensionHandler : CSIndexExtensionRequestHandler

@end

@implementation MyIndexRequestExtensionHandler

- (void)searchableIndex:(CSSearchableIndex *)searchableIndex reindexAllSearchableItemsWithAcknowledgementHandler:(void (^)(void))acknowledgementHandler {
// Use index API to re-index all your data.
// Call the acknowledgement handler when this is done.
}

- (void)searchableIndex:(CSSearchableIndex *)searchableIndex reindexSearchableItemsWithIdentifiers:(NSArray *)identifiers acknowledgementHandler:(void (^)(void))acknowledgementHandler {
// Use index API to re-index data with the specified identifiers.
// Call the acknowledgement handler when this is done.
}

@end