本文由CocoaChina译者YueWang翻译自Tuts+
iOS 9: Introducing Search APIs介绍
在WWDC 2015会议上,苹果官方公布了iOS9。除开许多新的特性和增强功能,这次升级也给了开发者们一个机会让他们的app里的内容能通过Spotlight搜索功能被发现和使用。在iOS9中可用的新APIs允许你去索引APP里面的内容或者界面状态,通过Spotlight来让用户使用。 这些新的搜索APIs的三大组件为:
NSUserActivity 类, 它是为可被看见的APP内容而设计的
Core Spotlight 框架, 为任何APP内容而设计的
web markup,为这一类型的APP设计的,就是APP的内容在某个网站上有镜像
在这个教程里,我将会向你展示可以怎样在你的应用中使用NSUserActivity类以及 Core Spotlight 框架。
这个教程需要你运行在Xcode7 和OSX 10.10系统或更后的系统。为了紧跟我的步伐,还需要你去 GitHub 上下载初始工程。
在这个教程的开始部分,我将会给你展示怎样通过NSUserActivity类来索引一个APP里的内容。这个API也是在Handoff里使用的那一个,handoff是在去年iOS8中介绍的功能,它用于保存和复原一个应用的当前状态。
如果你之前没有使用过NSUserActivity,那么我建议你在开始这个教程之前先阅读我的 这篇教程 ,它覆盖了Handoff和NSUserActivity的基础内容。
在开始写代码之前,打开 初始工程 ,然后在iOS模拟器上或者测试机上运行APP。在这个阶段,你会看到这个APP简单地展示了一个有着4个TV节目的列表,以及每个节目的详细页面。
首先,打开工程然后到DetailViewController.swift文件。把DetailViewController类里configureView方法里的内容替换为如下:
func configureView() { // Update the user interface for the detail item. if self.nameLabel != nil && self.detailItem != nil { self.nameLabel.text = detailItem.name self.genreLabel.text = detailItem.genre let dateFormatter = NSDateFormatter() dateFormatter.timeStyle = .ShortStyle self.timeLabel.text = dateFormatter.stringFromDate(detailItem.time) let activity = NSUserActivity(activityType: "com.tutsplus.iOS-9-Search.displayShow") activity.userInfo = ["name": detailItem.name, "genre": detailItem.genre, "time": detailItem.time] activity.title = detailItem.name var keywords = detailItem.name.componentsSeparatedByString(" ") keywords.append(detailItem.genre) activity.keywords = Set(keywords) activity.eligibleForHandoff = false activity.eligibleForSearch = true //activity.eligibleForPublicIndexing = true //activity.expirationDate = NSDate() activity.becomeCurrent() } }
在view controller里,配置label的代码是不变的,让我们来一步一步分析 user activity 代码:
使用唯一标识符 com.tutsplus.iOS-9-Search.displayShow创建一个新的NSUserActivity对象。 这个工程已经被配置成确保使用这个标识符时要保证它不会被改变。
然后为这个user activity 分配一个userInfo字典。它将会在后面被用来修复应用的状态。
给activity的title属性赋予了一个字符串值。这就是将会在Spotlight 搜索结果里出现的内容。
为了确保可搜寻的内容不仅止限于应用的标题,你也要提供一系列的关键字。在上面的代码段中,关键字列表中包含了每个节目的名字以及它的类型。
接下来,你向NSUserActivity对象赋予一些属性来告诉操作系统你想让这个user activity用来做什么。在这个教程中,我们只是查看搜索组件的API 因此我们把Handoff禁用掉然后把search开启。
最后, 调用user activity的becomeCurrent方法,就在此时它自动的被加入到了设备的搜索结果索引中。
在以上的实现代码中,你可能注意到了两条被注释的语句。尽管我们不会在这个教程中使用这些属性,但是了解每个属性是做什么用的也是很重要的。
在上面的实现代码中,每个节目的user activity和 搜索结果都是仅当应用曾经被打开过时而创建的。当你让你的user activity有eligibleForPublicIndexing属性时,Apple就开始从用户的搜索结果当中观察这个特殊activity的作用和交互了。如果这个搜索结果是被很多用户所使用的,Apple就提升这个user activity到它自己的云索引(cloud index)中。一旦这个user activity在这个云索引中了,它就可以被所有安装过你的应用的人搜索得到,而不管他们是否有打开过那些内容。这个属性只有当且仅当activities能被你应用的所有用户使用时才能被设置为true。
一个user activity 可以有一个可选的属性expirationDate。 当这个属性被设置时,你的user activity 只会在设置的时期之前才会展示在搜索结果里。
现在你已经知道了怎样创建一个可以在Spotlight中展示搜索结果的NSUserActivity,现在就来实验吧。编译运行你的APP,然后在你的应用中打开一些节目。做完这些后,返回到home页面(在iOS 模拟器中按 Command-Shift-H)然后向下扫或者滑动到最左边的屏幕就可以拉起搜索框视图。
在搜索框里填入某个你已经打开了的节目的标题,你将会在搜索结果里看到它被显示出来,如下图。
另外的,输入某个你已经打开了的节目的类别。归功于你已经对user activity赋予了关键字信息,这也会导致节目将会在搜索结果列表里被列举出来。
你应用的内容被操作系统正确的索引出来并且结果就展现在Spotlight 里。但是,当你轻触一个搜索结果时,你的应用并不会带领用户进入他们想要的搜索结果里面去,而只是简单地拉起这个应用。
幸运的是,通过 Handoff, 你可以利用NSUserActivity类来复原应用里的正确状态。为了使这成为可能我们需要实现两个方法。
如下所示在AppDelegate类里实现 application(_:continueUserActivity:restorationHandler:) 方法:
func application(application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> Bool { let splitController = self.window?.rootViewController as! UISplitViewController let navigationController = splitController.viewControllers.first as! UINavigationController navigationController.topViewController?.restoreUserActivityState(userActivity) return true }
接下来,在MasterViewController类里实现restoreUserActivityState方法:
override func restoreUserActivityState(activity: NSUserActivity) { if let name = activity.userInfo?["name"] as? String, let genre = activity.userInfo?["genre"] as? String, let time = activity.userInfo?["time"] as? NSDate { let show = Show(name: name, genre: genre, time: time) self.showToRestore = show self.performSegueWithIdentifier("showDetail", sender: self) } else { let alert = UIAlertController(title: "Error", message: "Error retrieving information from userInfo:/n/(activity.userInfo)", preferredStyle: .Alert) alert.addAction(UIAlertAction(title: "Dismiss", style: .Cancel, handler: nil)) self.presentViewController(alert, animated: true, completion: nil) } }
在写这篇文章的当下,Xcode7(Beta3)的最新版本有一个问题那就是一个用于修复的user activity的 userInfo 属性会变成空。这就是为什么我会处理errors以及展示一个userInfo(被操作系统返回的)信息的警告。
再次编译运行你的APP,搜索一个节目。当你在搜索结果里轻触一个节目时,APP将会直接将你带到详细信息的view controller并展示出你选择的节目的当前信息。
另外一些在iOS9中能使你的内容可被用户搜索得到的APIs就是Core Spotlight 框架。这个框架有一个类似数据库的设计并且能够给你提供更多的关于你想被搜索到的内容的信息。
在你可以使用Core Spotlight框架之前,我们需要把这个工程同这个框架链接起来。在Project Navigator中,选中这个工程然后打开最上面的Build Phases栏目。接下来,展开 Link Binary With Libraries 区域然后点击加号按钮。在弹出的菜单中,搜索 CoreSpotlight 然后把你的工程跟这个框架链接起来。重复这些步奏来链接 MobileCoreServices 框架。
接下来,为了确保我们的APP提供的搜索的结果确实来自于Core Spotlight,在你的测试机或者模拟器上删除你的应用然后在DetailViewController类中注释掉下面的这条语句:
activity.becomeCurrent()
最后,打开MasterViewController.swift然后在Show结构体定义之前添加下面的语句:
import CoreSpotlight import MobileCoreServices
接下来,在MasterViewController类的viewDidLoad方法里添加下面的代码:
var searchableItems: [CSSearchableItem] = [] for show in objects { let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeItem as String) attributeSet.title = show.name let dateFormatter = NSDateFormatter() dateFormatter.timeStyle = .ShortStyle attributeSet.contentDescription = show.genre + "/n" + dateFormatter.stringFromDate(show.time) var keywords = show.name.componentsSeparatedByString(" ") keywords.append(show.genre) attributeSet.keywords = keywords let item = CSSearchableItem(uniqueIdentifier: show.name, domainIdentifier: "tv-shows", attributeSet: attributeSet) searchableItems.append(item) } CSSearchableIndex.defaultSearchableIndex().indexSearchableItems(searchableItems) { (error) -> Void in if error != nil { print(error?.localizedDescription) } else { // Items were indexed successfully } }
在验证这段代码之前,我们先过一遍for循环里的每一步。
你创建一个 CSSearchableItemAttributeSet 对象, 给这个项目传入一个内容类型(content type)。例如,如果你的搜索结果链接到一张照片,那么你就应该传入kUTTypeImage常量。
给属性组合(attribute set)的title属性赋予一个节目的名字。就如同 NSUserActivity一样,这个标题就是将在搜索结果列表的最顶端出现的那个。
接下来,创建一个描述性字符串然后把它赋值给可搜索的属性组合(attribute set)的contentDescription属性。这个字符串将会在Spotlight中搜索结果的标题下方出现。
就像在NSUserActivity当中创建的那样,创建一个来自于搜索结果的关键字数组。
最后,创建一个有着唯一项目标识符的,唯一域标识符(用来聚集CSSearchableItem项目)的,和一个属性组合(attribute set)的CSSearchableItem,与NSUserActivity不同的是, NSUserActivity 从搜索结果中返回user activity, 当你的搜索结果被用户选中时,为CSSearchableItem设置的所有唯一标识符信息就是你可以从操作系统那里得到的唯一信息。 你需要利用这些标识符来复原你的应用回到正确状态。
一旦你为每个TV节目创建了一个CSSearchableItem项目时,你利用 indexSearchableItems(_:completionHandler:) 方法和默认的CSSearchableIndex对象来索引它们。
编译运行你的APP,你所有的节目将会被Spotlight索引到。去到搜索页面然后搜索其中一个节目。
Core Spotlight搜索结果会被跟NSUserActivity里一样的那个方法所处理,但是过程有一些轻微区别。当一个CSSearchableItem项目在搜索结果里被选中时,系统为你创建一个包含选中项目的唯一标识符信息的NSUserActivity对象。
在你的 app delegate的 application(_:continueUserActivity:restorationHandler:)方法中,可以利用下面的实现代码从Core Spotlight 搜索结果中获取你要的信息:
if userActivity.activityType == CSSearchableItemActionType { if let identifier = userActivity.userInfo?[CSSearchableItemActivityIdentifier] as? String { // Use identifier to display the correct content for this search result return true } }
使用Core Spotlight框架来索引APP内容的一个良好的实践就是当项目不再被需要的时候删除它们。CSSearchableIndex类提供了三种方法来删除可搜索项目:
deleteAllSearchableItemsWithCompletionHandler(:)
deleteSearchableItemsWithDomainIdentifiers(:completionHandler:)
deleteSearchableItemsWithIdentifiers(_:completionHandler:)
作为一个示例,添加下面代码到MasterViewController类里的viewDidLoad方法:
CSSearchableIndex.defaultSearchableIndex().deleteSearchableItemsWithDomainIdentifiers(["tv-shows"]) { (error) -> Void in if error != nil { print(error?.localizedDescription) } else { // Items were deleted successfully } }
再一次的编译运行你的应用。当你想要搜索任何节目时,不会有任何结果返回回来,因为它们已经在索引当中被删除掉了。
另一个在iOS9中NSUserActivity类的新增特性就是contentAttributeSet属性。这个属性允许你赋予一个CSSearchableItemAttributeSet, 正如你先前创建的那个。这个属性集合(attribute set)允许NSUserActivity对象的搜索结果可以展示如同 Core Spotlight搜索结果那样的相同数量的详细信息。
首先向DetailViewController.swift中最顶部添加下面的imports:
import CoreSpotlight import MobileCoreServices
接下来,用下面的实现代码更新DetailViewController类的configureView方法:
func configureView() { // Update the user interface for the detail item. if self.nameLabel != nil && self.detailItem != nil { self.nameLabel.text = detailItem.name self.genreLabel.text = detailItem.genre let dateFormatter = NSDateFormatter() dateFormatter.timeStyle = .ShortStyle self.timeLabel.text = dateFormatter.stringFromDate(detailItem.time) let activity = NSUserActivity(activityType: "com.tutsplus.iOS-9-Search.displayShow") activity.userInfo = ["name": detailItem.name, "genre": detailItem.genre, "time": detailItem.time] activity.title = detailItem.name var keywords = detailItem.name.componentsSeparatedByString(" ") keywords.append(detailItem.genre) activity.keywords = Set(keywords) activity.eligibleForHandoff = false activity.eligibleForSearch = true //activity.eligibleForPublicIndexing = true //activity.expirationDate = NSDate() let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeItem as String) attributeSet.title = detailItem.name attributeSet.contentDescription = detailItem.genre + "/n" + dateFormatter.stringFromDate(detailItem.time) activity.becomeCurrent() } }
最后一次编译运行APP,然后打开一些节目。当你搜索一个节目时,你将会看到你的结果,伴随NSUserActivity的创建,拥有和Core Spotlight 搜索结果相同级别的细节信息。
在这个教程中,你学习到了使用NSUserActivity类和 Core Spotlight框架来使你的应用里的内容可被iOS Spotlight 索引。我也向你展示了怎样使用这两个APIs在你的应用里索引内容以及当一个搜索结果被用户选中时怎样复原你的应用的状态。
在iOS9中介绍的新的搜索APIs使用都很方便而且可以使你的应用中的内容更简单的被用户发现和接触。一如既往的,如果你有任何评论或问题,在下方的评论框里留言。