Introduce
iOS 9 之后,Apple 开放了三大 Search APIs,以便用户能在 Spotlight、Safari 等搜索入口,搜索到应用中的内容,这十分有助于提升应用的用户活跃度。
这三组 Search APIs 分别是:
- NSUserActivity:能够为用户在 App 中的历史活动建立索引,例如到达某个关键页面,或是浏览到某个内容页。以便以后用户能够通过搜索结果恢复到该页面。
- Core Spotlight:为 App 中的一些重要内容,在设备上建立索引,以提供快速入口。
- Web markup:关联服务器上的内容到搜索结果中,用户即使没有安装 App,也能在搜索结果中看到相关内容。
本文主要介绍前两种 API —— NSUserActivity 和 Core Spotlight。
Note: 虽然全局搜索(app search)功能在 iOS 9 后就可用了,但是实际上,部分旧机型仍然不支持 NSUserActivity 和 Core Spotlight 的搜索功能,如 iPhone 4s、iPad 2、iPad(第三代)、iPad mini,以及 iPod touch 5.
Core Spotlight
Index App Content
Core Spotlight 主要用于为 App 中的一些重要内容(比较静态),在设备上建立索引。例如,应用的一些常用功能页面,以及一些文档、音视频内容,以便用户能够很方便地访问这些常用内容。
使用前需要 import Core Spotlight
。
1、建立索引
索引对应的类是CSSearchableItem
。建立索引前,要先创建索引的属性集CSSearchableItemAttributeSet
。
CSSearchableItemAttributeSet
中包含了大量的属性,包括音视频信息、文件信息、出版信息、联系人信息等等。实例化属性集时,就要指定属性集的类型,如kUTTypeImage
,这会影响到索引的显示样式。这些常量声明于MobileCoreServices.UTCoreTypes
中,因此需要 import 这个头文件。属性集中比较常用的属性为 title、contentDescription 和 keywords,title 就是索引显示的标题,contentDescription 是对内容的描述,keywords 则是索引的关键词数组,官方建议关键词为 5 个左右。需要注意的是,title 默认就能被搜索到,不需要加入 keywords。
设置完索引的属性集之后,就可以通过CSSearchableItem
的init(uniqueIdentifier: domainIdentifier: attributeSet:)
来实例化一个索引。其中,参数uniqueIdentifier
是该索引的唯一标识符,可以通过这个标识符来删除这条索引;domainIdentifier
是索引所在的域名标识,域名标识可以用于将索引分类存放,可以根据它来删除同一个domainIdentifier
下的所有索引。
接下来便是建立索引。建立索引需要调用CSSearchableIndex
实例的indexSearchableItems()
方法。它能够批量建立索引,并提供一个回调。
1 | func createSearchIndex(with followers: [User]) { |
调用indexSearchableItems()
方法成功后,就能立即在 Spotlight 中搜索到刚刚添加的索引了。
通过 CSSearchableIndex
实例的几个删除方法,就能部分,或者全部删除索引。
2、实现回调
要处理从搜索结果中进入 App 的回调事件,需要在 AppDelegate 中实现application:continueUserActivity:restorationHandler:
方法。这个回调在许多地方都会用到,如 Core Spotlight、Siri Kit、Handoff 等。可以在这个页面中,利用 userActivity
的 userInfo
字典中的数据,跳转到相应的页面。
Note:值得注意的是,之前我们为
userActivity
所设置的属性集,在这里都不存在了。只有保存在userInfo
中的数据,才能在这个回调中获取到。
之前创建CSSearchableItem
时传入的uniqueIdentifier
参数,在此处可以通过 userInfo
中的 CSSearchableItemActivityIdentifier
key值读取到,可以参考一下代码。
1 | func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool { |
上文我们已经提到过,有许多功能的回调入口都是这个方法,那么如何区分不同入口呢?
一种办法是像上方代码一样,判断userInfo
中是否存在 key 为CSSearchableItemActivityIdentifier
的值;另一种办法可以判断userActivity
的 activityType
属性的值,是否为CSSearchableItemActionType
。
在某些功能中,userActivity 的 activityType
是设置为自定义域名的,如下面将会讲到 NSUserActivity 部分。但在 Core Spotlight 中,所有的索引的类型都被系统设置为CSSearchableItemActionType
。
NSUserActivity
Index Activities and Navigation Points
NSUserActivity 主要用于为用户在 App 中的历史活动建立索引。NSUserActivity 的作用有些类似于浏览器中的“历史记录”,只是记录的内容是由我们来决定的。NSUserActivity 可以配合 Handoff 一起使用,以便在其他设备上继续当前活动,有关 Handoff 可以参照:Handoff 介绍。
我们可以在用户使用 App 的时候,将他浏览过的一些关键节点,或是重要的内容页面,在系统中建立一个索引,保存必要的数据。日后用户在 Spotlight 等地方搜索相关内容时,就能在搜索结果中显示该活动记录(UserActivity)。当用户点击该搜索结果时,系统会调起 App,并传入之前保存的数据,应用就能以此来恢复现场。
1、建立索引
首先,我们需要在用户浏览到某些页面的时候,将该页面加入系统索引。例如在 viewDidLoad()
的时候:
1 | // MARK: - Indexing activity |
要完成这个任务,我们先要创建一个 NSUserActivity对象,用于保存当前页面的一些必要信息,以及要建立的索引的一些属性。
创建 NSUserActivity 对象时,需要传入一个初始化参数activityType
,这个activityType
类似于 Core Spotlight 中的域名标识,只是作用上有些区别。在 Spotlight 等入口,搜索结果被点击时,系统会根据这个标识,去区分由哪个应用来恢复这个活动。在进入应用后,我们也能够在回调中,利用这个属性来区分活动。
为了让系统知道我们的应用能处理哪些 activityType,我们也需要在 Info.plist 中,创建一个名为NSUserActivityTypes
的 String 数组,标识出所有我们的应用能够处理的 activityType。
通过设置 NSUserActivity
的contentAttributeSet
属性,我们可以自定义索引在搜索结果中的样式。设置给contentAttributeSet
属性的是一个CSSearchableItemAttributeSet
实例,与 Core Spotlight 中的属性集相似,它包含大量的属性可以设置。
Note:Activity 在调用了
becomeCurrent()
方法激活后,必须对 Activity 进行强引用,防止它 dealloc 了,否则 Activity 很可能不会被加入索引。相关讨论见 Apple 论坛:Search APIs are working
2、实现回调
NSUserActivity 的回调入口与 Core Spotlight 的很相似。同样在AppDelegate
中实现application:continueUserActivity:restorationHandler:
方法,像上面说到的,我们可以通过 userActivity
的 activityType
属性来区分活动类型。此时userActivity
的属性集也已经为空,只能访问到 userInfo
中的数据。
1 | func application(UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: [AnyObject]? -> Void) -> Bool { |
Web markup
Engage Web Content
由于本次分享的主要针对 Core Spotlight 和 NSActivity,Web markup 就只作简要介绍。
Web Markup允许应用将它们的内容映射到一个网站(如网页版美拍),从而在 Spotlight 或 Safari 中进行搜索,即使用户没有安装相关应用。用户点击相关结果后,如果没有安装应用,则通过 Safari 打开,如果已经安装应用,则跳转到对应页面。
苹果有类似于搜索引擎爬虫的机器人,能够抓取支持 Web markup 的网站来获取所需的信息(需要后台配置,并结合 Universal Link 使用)。抓取结果会储存在苹果的云索引服务器上,通过 Safari 和 Spotlight 提供给用户。
有关 Web markup 的详细内容可以访问官方文档:Mark Up Web Content
联合使用 Core Spotlight 和 NSUserActivity
苹果在官方文档中表示,三大搜索 API 是为了联合使用而设计的,混合使用多种 API 有助于提升搜索的覆盖率。但在实际使用中,混合使用多种 API 会有一些坑,下面大致讲一下一些注意事项。
###1、唯一标识的使用###
Core Spotlight 中的 uniqueIdentifier
,NSUserActivity 中的 relatedUniqueIdentifier
属性,以及 Web markup 中的 webpageURL
,都会被系统用于索引的关联。但系统不会平等地处理这几个东西。
例如,如果我们分别通过 Core Spotlight 和 NSActivity 生成了两个相同标识的索引/活动,那么在 Spotlight 中搜索到的只会是通过 Core Spotlight 设置的索引;而 NSActivity 生成的索引,则是用于 Siri Kit(如果你有使用 Siri Kit 的话)。
###2、索引的更新和删除###
通过 Core Spotlight 建立的索引,可以通过 CSSearchableIndex 实例的三个删除方法进行删除。也可以设置过期时间,由系统自动清除。默认的过期时间为一个月。
而通过 NSActivity 生成的索引,只能设置过期时间,由系统管理,无法手动删除。
要更新相同 API 建立的索引,只需要再建立一个相同 identifier 的索引或者活动,系统就会自动更新对应索引。但是,不同 API 生成的索引,即使 identifier 相同,也不会相互更新内容。
###3、搜索排序###
苹果对于搜索的排序主要依据以下几个维度:
- 用户浏览 App 中内容的频率 (当我们调用 NSUserActivity 的
becomeCurrent()
方法时,系统会进行统计) - 用户对于应用中的内容的参与度(由“互动率”决定。“互动率”是基于两个数据计算的,分别是:用户点击与你应用相关的条目的次数,以及搜索结果中显示的应用相关的条目的数量)
- 你的网站中某个网址的受欢迎程度,以及可用的结构化数据量。(Web markup)
坊间传闻,苹果为了防止世界被破坏,守护 iOS 的生态圈,在搜索结果的排序上,花费了重金进行优化。没有好好维护 iOS 生态圈的话,可能会导致应用的索引被降低排名,或是被踢出搜索结果。因此在使用搜索 API 时,我们需要注意:
- 防止过度索引;(不要把一大堆有的没的数据,都丢到系统索引中去)
- 尽快将用户带入内容页;(避免中间步骤,以及降低 App 启动时间)
- 如果创建的 NSUserActivity 与已有的 Core Spotlight 索引相同,那么就将它们用相同的 identifier 关联起来,这样每次激活 NSUserActivity 时,也能提升 Core Spotlight 索引的排名;
END
使用 Search APIs 可以有效提升应用的用户体验,但要注意各种 API 的适用场景,同时维护好 iOS 的生态圈。