如何使用 iOS 9 的 Core Spotlight 框架

dwdf3388 8年前
   <p>每一代 iOS 都会为全球的开发者们带来新鲜的“小玩意儿”和对现有技术进行提升。显然,最新的 iOS 9 也不例外,开发者们拥有了全新的框架和 APIs 以方便调用、这可以显著地提升应用程序的水平。Core Spotlight 框架就是其中之一,它包含了许多优秀 APIs,开发者可以很方便地应用在工程中。</p>    <p>Core Spotlight(CS)框架属于一个更大的 API 集合 Search APIs,它让开发者们可以地将应用变得更容易被发现,以及访问起来更加便利。这在以前的 iOS 版本里是不可想象的。Search APIs 让用户和应用之间的联系更加紧密。用户可以更迅速地访问应用,同时应用也能更主动及时地响应用户。除 Core Spotlight 以外,iOS 9 其他新的搜索功能还包括(仅供参考):</p>    <ol>     <li><em>NSUserActivity</em> 类的新方法和属性(负责保存应用的状态以便稍后恢复)。</li>     <li><em>web markup</em> 让网页的内容在设备上可被搜索。</li>     <li><em>universal links</em> 允许从网页内容里的链接直接打开应用。</li>    </ol>    <p>我们不会在这篇文章里讨论以上三项,但会详细地介绍 Core Spotlight 框架。但开始之前,我们先来搞清楚这个框架的用途。</p>    <p><img src="https://simg.open-open.com/show/d8363836a908105cf0739b166390fe3c.jpg"></p>    <p><a href="/misc/goto?guid=4959660329178648247" rel="nofollow,noindex">Core Spotlight 框架</a> 让应用里的数据在 Spotlight <em>可搜索</em> ,然后把与应用相关的搜索结果与系统返回的其他结果一同展示出来。这令人印象深刻并具有革命性,因为这是用户首次可以搜索到除 Apple 官方应用外、任意应用中的数据,然后与之进行交互。用户可以与自定义应用的相关搜索结果进行交互的意思是:不但在搜索结果项被选中时会自动启动应用,而且开发者们也能引导用户跳转到特定视图控制器,用来展示 Spotlight 中被选择的数据。</p>    <p>从开发者的角度看来,集成 Core Spotlight 框架和使用它的 API 并不复杂。正如本教程随后会介绍的那样,只需要几行代码就能搞定。整个过程的重点在于开发者需要“请求” iOS 去索引他们应用里的数据,并且这些数据必须预先以特定的方式来表示。</p>    <p>鉴于这是一篇关于 Core Spotlight 框架的教程,我不打算在简介部分过于详细。如果你有兴趣学习如何实现一些我个人觉得非常棒的功能,那么请继续阅读。我相信,当你读完之后,就能很轻松地让你的应用支持 Spotlight 搜索。</p>    <h2>关于示例应用</h2>    <p>为了深入剖析本节主题的细节部分,我们还是一如既往的借助实例应用来研究。在本教程,我们的应用会展示一系列的数据,这些数据能在设备(或者模拟器)的 Spotlight 中搜索到。尽管这只是一个大的蓝图,但再补充一下应用程序的细节也是很必要的。</p>    <p>我们的示例应用展示了一些 <em>电影</em> 及相关信息,例如简介、导演、演员、评价,等等。所有的电影数据会展示在 tableView 里,当点击某一行时,被选中电影的详情会展示在一个新的视图控制器里。没有更复杂的功能了,这种功能和数据就足以让我们了解 Core Spotlight API 是如何工作的。再补充一点,我们数据的来源是 <a href="/misc/goto?guid=4959614351075459514" rel="nofollow,noindex">IMDB</a> ,我是从这里获取示例数据的。</p>    <p>你可以先看看下面的动图,大致对这个示例应用有个初步印象</p>    <p><img src="https://simg.open-open.com/show/5ee5c5ef203c9b03046e2ef6888b0686.gif"></p>    <p>这个教程里我们有两个目标: 最首要的是在 Spotlight 中能搜索到应用的所有电影数据。这样,当用户搜索关键词时,应用中涉及到该电影相关的数据会展示出来。设置这些关键词也是稍后的工作之一,因为定义它们(关键词)也是我们的职责。</p>    <p>点击搜索的电影结果,会启动应用,接着我们来完成第二个目标。如果什么都不做,就会加载默认的视图控制器并呈现给用户,在我们的例子里就是那个包含了电影列表的 tableview。但是我们想要兼顾用户体验的话,这并不是一种好的设计;更好的方案是我们的应用应该在 Spotlight 中展示选中电影的详细信息,而这正是我们最终要实现的。总而言之,我们不仅要在 Spotlight 中可以搜索电影数据,还要把相关搜索结果所对应的电影详情展现出来。通过下面示例的学习,你基本就懂了。</p>    <p><img src="https://simg.open-open.com/show/986ea32a7a7f6e8586aaab8c186e82ec.gif"></p>    <p>为了立即可以开始工作,你可以先 <a href="/misc/goto?guid=4959713498815626079" rel="nofollow,noindex">下载初始工程</a> 。在这个工程里,主要包含以下几部分:</p>    <ul>     <li>UI 部分以及所有必要的 IBOutlet 属性已经设置完成。</li>     <li>实现了基本的 tableView</li>     <li>所有的电影数据、以及每部电影的封面(一共5张)都存放在 a.plist 文件里。</li>    </ul>    <p>假如你对 plist 文件里面每部电影包含的信息类型感兴趣,下面的截图能清晰地说明一切:</p>    <p><img src="https://simg.open-open.com/show/436bc39494cd3bb8d070a6ffc109f37b.png"></p>    <p>在深入了解 Core Spotlight API 的细节之前,我们会执行两个明确的任务:</p>    <ol>     <li>加载电影数据并展示在 tableView 里。</li>     <li>在详情视图控制器里展示被选中电影的数据。</li>    </ol>    <p>尽管在初始项目里实现以上任务能让你更快地开始本文主题的学习,但我并没有这么做,原因很简单:我坚信,通过对 demo 应用的极其数据内容的探索会让你更直观地明白特定数据是如何在 Spotlight 里被搜索的。不过不用担心,准备工作都不多且都能快速完成。</p>    <h2>加载和展示示例数据</h2>    <p>好的,让我们开始吧!假设现在你已经下载了初始工程并查看了包含电影数据的 plist 文件。在 <em>MoviesData.plist</em> 文件里,你会看到总共五项在 IMDB 网站里随机选取的示例电影数据。我们的第一个目标是把 .plist 文件里的数据加载到一个数组里,然后展示在 tableView 里。</p>    <p>直接进入代码部分,打开最主要的 <em>ViewController.swift</em> 文件,并在类的顶部声明一个属性:</p>    <pre>  <code class="language-objectivec">var moviesInfo: NSMutableArray!  </code></pre>    <p>所有的电影都会加载到这个数组里,每一部电影都会以键值对字典的形式与 .plist 文件相对应。</p>    <p>我们先来写一个简单的自定义方法来加载数据。正如下面所展示的,首先确保了 .plist 文件的存在,然后就可以用该文件的内容来初始化数组。</p>    <pre>  <code class="language-objectivec">func loadMoviesInfo() {      if let path = NSBundle.mainBundle().pathForResource("MoviesData", ofType: "plist") {          moviesInfo = NSMutableArray(contentsOfFile: path)      }  }  </code></pre>    <p>接着,我们要在 <em>viewDidLoad()</em> 里调用这个方法。要确保在 <em>configureTableView()</em> 方法之前调用它,即要按照以下代码片段的展示:</p>    <pre>  <code class="language-objectivec">override func viewDidLoad() {      super.viewDidLoad()         // 从文件里加载电影数据。      loadMoviesInfo()         configureTableView()      navigationItem.title = "Movies"  }  </code></pre>    <p>我们本可以直接在 <em>viewDidLoad()</em> 方法里面加载文件内容,而无需创建自定义方法。但是我喜欢整齐的代码,即使对于这么简单的一个小功能,创建一个自定义方法还是好很多。</p>    <p>我们既然知道了应用会在每次启动时加载电影数据,就可以继续修改当前 tableView 的实现让它展示我们的电影。这里并没有太多需要做的:我们会根据电影数量定义 tableView 行数,然后把适合的数据展示在 Cell 里。</p>    <p>先从行数开始,很明显行数需要与电影数目相等。然而,我们首先要确保有电影可以展示,不然当数组没有加载到文件内容时应用会崩溃。</p>    <pre>  <code class="language-objectivec">func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {      if moviesInfo != nil {          return moviesInfo.count      }      return 0  }  </code></pre>    <p>最后,让我们显示电影数据。出于演示的目的,在起始项目里你能找到一个 <em>UITableViewCell</em> 的子类 <em>MovieSummaryCell</em> ,还有与其对应的 <em>.xib</em> 文件代表了单个电影 Cell:</p>    <p><img src="https://simg.open-open.com/show/0f592fbfc113c8b96995c3b5b19b5202.png"></p>    <p>这样的 Cell 展示了每部电影的图片、标题、简介、以及评分。所有的 UI 控制器都有相对应的 IBOutlet 属性,你可以在 <em>MovieSummaryCell.swift</em> 文件里找到它们:</p>    <pre>  <code class="language-objectivec">@IBOutlet weak var imgMovieImage: UIImageView!     @IBOutlet weak var lblTitle: UILabel!     @IBOutlet weak var lblDescription: UILabel!     @IBOutlet weak var lblRating: UILabel!  </code></pre>    <p>以上的命名方式表明了每个属性的功能,搞清楚后,我们利用它们来展示电影的详情。回到 <em>ViewController.swift</em> 文件,按照下面的代码片段更新 tableView 方法:</p>    <pre>  <code class="language-objectivec">func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {      let cell = tableView.dequeueReusableCellWithIdentifier("idCellMovieSummary", forIndexPath: indexPath) as! MovieSummaryCell         let currentMovieInfo = moviesInfo[indexPath.row] as! [String: String]         cell.lblTitle.text = currentMovieInfo["Title"]!      cell.lblDescription.text = currentMovieInfo["Description"]!      cell.lblRating.text = currentMovieInfo["Rating"]!      cell.imgMovieImage.image = UIImage(named: currentMovieInfo["Image"]!)         return cell  }  </code></pre>    <p><em>currentMovieInfo</em> 字典并不是必须的,但它可以让代码变得更加简单。</p>    <p>现在能如你所愿地第一次尝试运行这个应用了。可以看到一些电影详情在 tableView 中罗列出来。到现在为止都是大家很熟悉的步骤,下面让我们直接开始第二个准备步骤:展示所选电影的详情信息。</p>    <h2>展示数据详情信息</h2>    <p>我们在 <em>ViewController</em> 类的 tableView 里选中电影的详情,将通过 <em>MovieDetailsViewController</em> 类来展示,对应的场景在 Interface Builder 里已经写好,所以现在有两个任务: 从 <em>ViewController</em> 里传递对应的电影字典到这个类里,然后把字典里的值传递到适当的 UI 控制器里,而这些 IBOutlet 属性都已经被声明并且正确地连线了。</p>    <p>说到字典,让我们在 <em>MovieDetailsViewController</em> 类的顶部做出以下声明:</p>    <pre>  <code class="language-objectivec">var movieInfo: [String: String]!  </code></pre>    <p>先暂时回到 <em>ViewController.swift</em> ,看看当一行电影数据被点击的时候,我们需要做些什么。这时需要了解被选中行的索引,以便从 <em>movieInfo</em> 数组中选择恰当的字典,并在 Segue(名为 <em>idSegueShowMovieDetails</em> )执行的时候传递给下一个视图控制器。从 tableView 的代理方法里获取索引很简单,但我们仍需要一个自定义属性来保存它。因此在 <em>ViewController</em> 类的顶部我们需要声明:</p>    <pre>  <code class="language-objectivec">var selectedMovieIndex: Int!  </code></pre>    <p>然后,我们需要按照以下方法处理 tableView 的行选择:</p>    <pre>  <code class="language-objectivec">func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {      selectedMovieIndex = indexPath.row  performSegueWithIdentifier("idSegueShowMovieDetails", sender: self)  </code></pre>    <p>在这儿,我们做两件非常简单的事情:首先,把选中的行索引保存在自定义的属性里,然后执行展示电影详情的 Segue。然而这还不够,因为还没有从 <em>moviesInfo</em> 数组里选择合适的电影字典,而且也还没把任何数据传递给 <em>MovieDetailsViewController</em> 类。那么我们需要做些什么呢? 那就是重写 <em>prepareForSegue:sender:</em> 方法并完成上述功能。</p>    <pre>  <code class="language-objectivec">oerride func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {      if let identifier = segue.identifier {          if identifier == "idSegueShowMovieDetails" {              let movieDetailsViewController = segue.destinationViewController as! MovieDetailsViewController              movieDetailsViewController.movieInfo = moviesInfo[selectedMovieIndex] as! [String : String]          }      }  }  </code></pre>    <p>足够简单了吧。我们只是通过这个 Segue 的 <em>destinationViewController</em> 属性获得 <em>MovieDetailsViewController</em> 实例,然后把对应的电影字典赋值给在本部分最开始声明的 <em>movieInfo</em> 属性。</p>    <p>现在,重新打开 <em>MovieDetailsViewController.swift</em> 文件,其中只有一个自定义方法。通过该该方法,将 <em>movieInfo</em> 字典中的值分配给相应的 UI 控制器,至此我们的工作就结束了。以下是一个简单的实现,我就不详述了:</p>    <pre>  <code class="language-objectivec">func populateMovieInfo() {      lblTitle.text = movieInfo["Title"]!      lblCategory.text = movieInfo["Category"]!      lblDescription.text = movieInfo["Description"]!      lblDirector.text = movieInfo["Director"]!      lblStars.text = movieInfo["Stars"]!      lblRating.text = movieInfo["Rating"]!      imgMovieImage.image = UIImage(named: movieInfo["Image"]!)  }  </code></pre>    <p>最后,让我们在 <em>viewWillAppear:</em> 方法里调用以上方法:</p>    <pre>  <code class="language-objectivec">override func viewWillAppear(animated: Bool) {      ...        if movieInfo != nil {          populateMovieInfo()      }  }  </code></pre>    <p>这部分就结束了。你可以再运行一下,然后在 tableView 里选择某电影的时候查看一下电影的详情。</p>    <h2>为 Spotlight 索引数据</h2>    <p>使用 iOS 9 的 Core Spotlight 框架让任何应用的数据都能通过 Spotlight 搜索到。要做到这一点,关键在于请求 Core Spotlight API <em>索引</em> 我们的数据,以便用户可以搜索到。但无论是我们的应用还是 CS API 都无法判断数据的类型。因为准备数据并把数据以特定格式提供给 API 是我们的职责。</p>    <p>再解释一下,我们希望能在 Spotlight 中搜索到的所有数据都必须表现为 <em>CSSearchableItem</em> 对象,然后组织成数组形式提供给 CS API 索引。单个 <em>CSSearchableItem</em> 对象包括一组属性,它可以让 iOS 完全掌握被搜索项的细节,类似于哪部分数据应该在搜索时展示(例如,电影的名字、它的图片和描述信息),还有哪些关键词会触发包含相关数据的应用在 Spotlight 里出现。单个可被搜索的项目的所有属性都会展示在一个 <em>CSSearchableItemAttributeSet</em> 对象里,它提供了许多属性让我们用于赋值。作为参考,我提供了 <a href="/misc/goto?guid=4959660329272394610" rel="nofollow,noindex">官方文档链接</a> 便于你查看所有可用的属性。</p>    <p>Spotlight 索引数据是最后一步。正常情况下涉及以下步骤(包括索引):</p>    <ol>     <li>为每个数据片段设置属性,例如一部电影(CSSearchableItemAttributeSet 对象)。</li>     <li>利用上一步获得的属性为每个数据片段初始化一个可搜索项目(CSSearchableItem 对象)。</li>     <li>把所有可搜索项添加到一个数组里。</li>     <li>利用以上数组为 Spotlight 索引数据。</li>    </ol>    <p>我们来按步骤执行,为了实现目标,需要在 <em>ViewController.swift</em> 文件里创建一个名为 <em>setupSearchableContent()</em> 的自定义方法。在实现本部分内容后,你会发现想要搜索全部的数据并不是一件难事。但是,我们不打算一步登天,也不准备一次性把所有的实现都告诉你们;而是把代码分段,以便你们理解。别担心,这并不复杂。</p>    <p>在我们实现新方法之前,需要先导入两个框架:</p>    <pre>  <code class="language-objectivec">import CoreSpotlight  import MobileCoreServices  </code></pre>    <p>让我们开始定义新方法,首先声明一个数组,待会用来收集可被搜索的项目:</p>    <pre>  <code class="language-objectivec">func setupSearchableContent() {      var searchableItems = [CSSearchableItem]()     }  </code></pre>    <p>现在我们能在循环里读取每一部电影:</p>    <pre>  <code class="language-objectivec">func setupSearchableContent() {      var searchableItems = [CSSearchableItem]()         for i in 0...(moviesInfo.count - 1) {          let movie = moviesInfo[i] as! [String: String]      }  }  </code></pre>    <p>我们会为每部电影创建一个 <em>CSSearchableItemAttributeSet</em> 对象,然后设置相应的属性,这样在 Spotlight 搜索时就会展示相关的结果。在示例中,我们会指定电影标题、简介和图片这部分数据展示给用户。</p>    <pre>  <code class="language-objectivec">func setupSearchableContent() {      var searchableItems = [CSSearchableItem]()         for i in 0...(moviesInfo.count - 1) {          let movie = moviesInfo[i] as! [String: String]             let searchableItemAttributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeText as String)             // 设置标题.          searchableItemAttributeSet.title = movie["Title"]!             // 设置电影封面.          let imagePathParts = movie["Image"]!.componentsSeparatedByString(".")          searchableItemAttributeSet.thumbnailURL = NSBundle.mainBundle().URLForResource(imagePathParts[0], withExtension: imagePathParts[1])             // 设置简介.          searchableItemAttributeSet.contentDescription = movie["Description"]!      }  }  </code></pre>    <p>留意在以上代码片段里我们是如何为电影图片这个属性进行赋值的。有两种方式:一是指定图片的 URL,二是把图片作为 NSData 对象。对我们来说,最简单的方式就是为提供所有电影图片文件的 URL,众所周知这些图片就存在于应用 bundle 里。然而,这种方式需要把每个图片文件名分成实际名字和扩展名,因此我们利用 String 类的 <em>componentsSeparatedByString:</em> 方法来实现分割操作,剩下的就很简单了。</p>    <p>现在是时候设置一波关键词了,这样就能通过 Spotlight 搜索到 App 中的相关数据了。在指定关键词之前要考虑清楚,因为你的决定会影响 App 在 Spotlight 里的搜索结果、这对用户也很重要。在本例中,我们会把电影所属的类别及其评星数设置为关键词。</p>    <pre>  <code class="language-objectivec">func setupSearchableContent() {      var searchableItems = [CSSearchableItem]()         for i in 0...(moviesInfo.count - 1) {          ...             var keywords = [String]()          let movieCategories = movie["Category"]!.componentsSeparatedByString(", ")          for movieCategory in movieCategories {              keywords.append(movieCategory)          }             let stars = movie["Stars"]!.componentsSeparatedByString(", ")          for star in stars {              keywords.append(star)          }             searchableItemAttributeSet.keywords = keywords      }  }  </code></pre>    <p>要知道电影的分类在 <em>MoviesData.plist</em> 文件中用一段字符串表示,每部电影之间用逗号分隔。因此很有必要把这段字符串所代表的电影类比分隔出来,然后存在 <em>movieCategories</em> 数组里方便访问。接着使用内循环把每个值添加到 <em>keywords</em> 数组。对于评星数,我们也执行完全相同的步骤,再次把一个包含所有电影评星数的字符串分隔为许多独立的部分,最后添加到关键词数组。</p>    <p>需要注意的是最后一行;我们为每部电影的属性集合设置了关键词。如果漏了这一行,那么 Spotlight 就不会展示任何关于这个应用的搜索结果。</p>    <p>现在我们已经为 Spotlight 设置了属性和关键词,是时候初始化一个可搜索项目并添加到 <em>searchableItems</em> 数组了:</p>    <pre>  <code class="language-objectivec">func setupSearchableContent() {      var searchableItems = [CSSearchableItem]()         for i in 0...(moviesInfo.count - 1) {          ...             let searchableItem = CSSearchableItem(uniqueIdentifier: "com.appcoda.SpotIt.\(i)", domainIdentifier: "movies", attributeSet: searchableItemAttributeSet)             searchableItems.append(searchableItem)      }  }  </code></pre>    <p>以上的初始化方法接收三个参数:</p>    <ul>     <li><em>uniqueIdentifier</em> : 这个参数唯一地标识了当前在 Spotlight 的可搜索项目。你可以用你喜欢的方式编写这个标识符,但是要注意一个细节:在这个示例里我们添加了当前电影的索引作为标识符,因为稍后会展示与索引值相匹配的电影详情。总体来说,在标识符中包含一个指向某些数据的索引是个好主意,这些数据将会用于详情展示。你稍后会更好地了解电影索引的作用。</li>     <li><em>domainIdentifier</em> : 使用这个参数把可搜索项目组成集合。</li>     <li><em>attributeSet</em> 这是我们刚刚用于赋值的属性集合对象。</li>    </ul>    <p>最后,新的可搜索项目被加到 <em>searchableItems</em> 数组里。</p>    <p>我们最后需要执行的步骤是使用 Core Spotlight API 索引这些项目。通过 <em>for</em> 循环来实现:</p>    <pre>  <code class="language-objectivec">func setupSearchableContent() {      ...            CSSearchableIndex.defaultSearchableIndex().indexSearchableItems(searchableItems) { (error) -> Void in          if error != nil {              print(error?.localizedDescription)          }      }  }  </code></pre>    <p>上面的方法已经功能齐全了,就等调用了。我们会在 <em>viewDidLoad()</em> 方法里调用它:</p>    <pre>  <code class="language-objectivec">override func viewDidLoad() {      ...        setupSearchableContent()  }  </code></pre>    <p>我们现在已经准备好首次使用 Spotlight 搜索电影了。运行应用,退出,然后在 Spotlight 使用之前定义好的任意关键词。你会看见搜索结果展现在眼前。点击任意搜索结果,会自动启动相关应用。</p>    <p><img src="https://simg.open-open.com/show/65bab7bf137e8e44be54f80fd0720ac2.gif"></p>    <h2>实现定点着陆</h2>    <p>虽然通过 Spotlight 可以搜索到我们应用中的电影数据这一点令人印象深刻,但还是能更上一层楼。目前,点击搜索结果,会跳转到应用首页 <em>ViewController</em> 界面。但是我们的目标是让它直接跳转到电影详情的视图控制器,并展示所选择电影的相关信息。</p>    <p>虽然听起来比较复杂,但其实相当容易。针对我们这个示例应用则更简单了。因为我们已经完成了绝大部分的基础工作,可以很容易地实现展示选中电影的详情页面。</p>    <p>这里的主要工作是重写一个 UIKit 方法 <em>restoreUserActivityState:</em> ,来处理在 Spotlight 被选中的搜索结果。当我们最终想要实现的是,从可搜索的项目标识符中取出该电影在 <em>moviesInfo</em> 数组里的索引值(如果你记得的话,我们在之前的部分动态地创建了这个标识符)。</p>    <p>该方法接受一个 <em>NSUserActivity</em> 对象作为参数。这个对象有一个名为 <em>userInfo</em> 的字典属性,其中包括了在 Spotlight 中被选中的可搜索项目的标识符。我们通过标识符在 <em>moviesInfo</em> 数组里获取该电影的索引值,然后展示详情视图控制器。就这些。</p>    <p>来看看具体实现:</p>    <pre>  <code class="language-objectivec">override func restoreUserActivityState(activity: NSUserActivity) {      if activity.activityType == CSSearchableItemActionType {          if let userInfo = activity.userInfo {              let selectedMovie = userInfo[CSSearchableItemActivityIdentifier] as! String              selectedMovieIndex = Int(selectedMovie.componentsSeparatedByString(".").last!)              performSegueWithIdentifier("idSegueShowMovieDetails", sender: self)          }      }  }  </code></pre>    <p>如你所见,首先检查 <em>activity type</em> 和 <em>CSSearchableItemActionType</em> 是必要的。坦白地讲,这么做并不重要,但假设应用需要处理多个 <em>NSUserActivity</em> 对象,那么你就别忘了做这件事(例如,在 iOS 8 首次出现的 <em>Handoff</em> 特性利用了 <em>NSUserActivity</em> 类)。这个标识符是一个储存在 <em>userInfo</em> 字典里的字符串值。得到这个字符串之后,我们会把它根据点符号(dot symbol)分成不同部分,然后获取最后一部分,这是被选中的电影在电影集合里的索引。剩下的就很简单了:给 <em>selectedMovieIndex</em> 属性赋值然后执行 Segue。剩下的任务就交给我们之前的实现了。</p>    <p>现在打开 <em>AppDelegate.swift</em> 文件。我们需要添加一个新的代理方法。每一次与应用相关的搜索结果在 Spotlight 里被选中的时候,这个方法都会被调用,我们只需调用上面实现的方法,传递 user activity 对象即可。来看看具体实现:</p>    <pre>  <code class="language-objectivec">func application(application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> Bool {      let viewController = (window?.rootViewController as! UINavigationController).viewControllers[0] as! ViewController      viewController.restoreUserActivityState(userActivity)         return true  }  </code></pre>    <p>在以上代码片段里,在恢复用户活动状态前,我们首先通过 <em>window</em> 属性获取到 <em>ViewController</em> 视图控制器。你还可以利用 <em>NSNotificationCenter</em> 和发送自定义通知来实现,这样你需要在 <em>ViewController</em> 类里处理通知。显然第一种方案更为直观。</p>    <p>这就是全部内容了!我们的示例应用已经完成,那么再运行一次看看在 Spotlight 里搜索电影时会发生什么吧。</p>    <p><img src="https://simg.open-open.com/show/986ea32a7a7f6e8586aaab8c186e82ec.gif" alt="如何使用 iOS 9 的 Core Spotlight 框架" width="373" height="686"></p>    <h2>总结</h2>    <p>iOS 9 最新的搜索 API 对于开发者而言前景广阔,因为这些 API 能大幅提高应用的曝光度、也更容易被用户访问。在本教程里,我们涉及了索引应用数据的所有步骤,最终在 Spotlight 搜索时能发现这些数据。也说明了应用该如何处理选中的搜索结果,并展现特定的数据给用户。在实现这些特性一定能大幅提升用户体验,因此你应该认真地考虑在现有的和将来的项目中添加这些特性。我们又到了说再见的时候,希望这篇文章对你有帮助!祝开心!</p>    <p> </p>    <p> </p>    <p>来自:http://swift.gg/2016/08/30/core-spotlight-framework/</p>    <p> </p>