如何使用iOS9中的Core Spotlight框架

如何使用iOS9中的Core Spotlight框架html

做者: Gabriel Theodoropoulos,时间:2015/12/22
翻译:BigNerdCoding, 若有错误欢迎指出。原文连接ios

伴随这每个iOS新版本的发布,苹果公司都会为全球的开发者带来新的“好东西”,以及对于原有功能的持续改进。是在最新的版本iOS9中,苹果不只依旧保留了这个传统,再次为咱们提供了新的框架和API接口。利用这些新特性,开发者能够将他们的应用程序提升到一个更高的水平上。Core Spotlight框架就是这其中之一,该框架包含了不少等待咱们去发现和使用的强大API接口。git

Core Spotlight框架是被称为Search APIs这个大集合API中的的一部分。该框架为程序员提供了一个机会来增长他们应用程序可发现性、可见性以及访问的便捷性,而且做为新特性该框架没法在以前版本的iOS中使用的。Search APIs让用户和应用之间的关系变得更加密切,前者能够以更新更快的方法来使用后者,同时后者也能够当即响应前者。在iOS9中除了Core Spotlight还有其它一些搜索功能,包括(仅供参考):程序员

  1. NSUserActivity类里面的新方法和属性(该类负责存储应用的状态并用于后面状态的恢复)。github

  2. 是网页内容能在设备中进行搜索的网页标记(web markupweb

  3. 让程序从网页内容中的连接直接启动的通用连接(universal links数据库

咱们不会对上面全部都进行讲解,可是咱们会对Core Spotlight框架进行仔细的分析和讲解。但在此以前,咱们须要对Core Spotlight有个初步的了解。swift

clipboard.png

Core Spotlight框架使得应用程序的数据可以Spotlight中被搜索查询,系统会将与之相关的数据以及其它结果一块儿返回。第一次用户能够查询到除苹果自家应用之外其它第三方应用数据并与之交互,这让人影响深入且意义重大。这里所说的与第三方应用相关的数据进行交互的意思是:不只仅是当咱们点击搜索结果应用程序会自动启动,还有开发者能够给予用户权利去为Spotlight中选择的数据选择最合适的视图控制器。数组

从开发者角度来讲,集成Core Spotlight框架并使用其中的API接口并非意见复杂的事情。就像你在这篇教程后面发现的一些,它仅仅须要几行代码而已。这里的关键是开发者须要向系统查询他们应用程序数据的索引,而这些索引在以前必须已经定义描述好了。app

因为这篇教程自己就是专门关于Core Spotlight框架内容的,我就再也不对概念进行更细致的介绍了。若是你对于如何将其应用于实践实现一些东西(我以为这才是真正有趣的地方),那么就继续往下阅读吧。我相信在你读完这篇教程以后,你会对让应用支持Spotlight是如此简单的一件事而感到满意与开心。

关于Demo

与往常同样,咱们经过一个Demo应用来深挖咱们今天话题的一些具体细节。在该Demo中咱们会加入一些数据到应用中,这些数据可以在设备或者模拟器的Spotlight中被搜索到。虽然应用的大概是这些,可是仍是有必要对一些细节进行说明一下。

咱们的演示应用的目的是展现一些电影以及与之相关的信息,例如:摘要、导演、明星、评价等等。全部的这些电影数据都会在一个tableview展现出来,当用户选择了某个电影的时候会跳转到详细介绍的页面视图中。没有更进一步的操做了,这个功能和数据已经足以让咱们理解 Core Spotlight接口是如何工做的了。至于应用数据的获取,你能够去国际电影数据库(IMDB)去查找;演示应用中数据也是我从里面找到的。

你能够经过下面演示动画的流程和效果一睹为快该应用。

clipboard.png

在这个教程里主要要实现两个目标:首先最重要的是让应用里的数据可以在Spotlight里面搜索到。经过这样作,当用户经过使用关键值进行搜索时与之相关的结果将展现出来。设置这些关键值是后面须要作的工做的一部分,定义它们也是咱们的职责之一。

当用户点击搜索出来的电影时应用将被触发启动,并带出咱们的第二个目标。当应用启动后,若是此时用户不采起任何动做的话,默认的视图控制器将会加载一个包含电影列表的tableview并将其显示给用户。而后,若是从用户体验方面考虑的话,这样作并非很好。一个更理想的状况是,咱们可以展现出Spotlight搜索出来结果的详细信息,这也是最终咱们所作的。总之,咱们不只须要可以让电影能被搜索到,还要在用户点击搜索结果时可以展现详细介绍。下面的这个演示可以更加清楚表达出这两个目标:

clipboard.png

为了能如今就能开始工做,你能够下载开始工程。在工程里面你能够发现:

  • UI部分已经完成了,同时IBOutlet属性也设置好了。

  • 实现了最小化的tableview

  • 因此的电影数据都存在于.plist文件。另外,这里还有五个图像与之对应。

若是你想知道列表文件中包含的每个电影的数据,下面会展现一个截图来讲明一切:

clipboard.png

在了解Core Spotlight接口的一些细节以前,咱们先要实现下面两个任务:

  1. 咱们会加载并填充电影数据到tableview

  2. 咱们须要将电影数据并在视图控制器里展现详细信息

虽然可让咱们更快的接近这个话题的要点,可是我并无在上面的工程里面实现这两个任务时由于一个简单的缘由:我坚信经过演示应用和样本数据操做的过程,会让你对这些具体数据是如何变的能够被Spotlight搜索到的理解更加简单直接。不须要担忧,全部的前期工做不多,很快就能够完成。

加载并显示实例数据

假设你已经下载了初始项目并见过电影数据的列表文件,接下来咱们开始工做。在MoviesData.plist中你能够发现五个与IMDB网站上随机选择数据对应的条目。我第一个目标是加载.plist文件中的数据到一个数组中,并在tableview中展现。

首先直接打开ViewController.swift文件,并在文件的头部直接声明以下属性:

var moviesInfo: NSMutableArray!

全部电影都将被加载到钙数组中,每一个电影都使用dictionary中的健值和值与之进行匹配。

接下来咱们写一个小的自定义功能,该功能将实现数据加载。正如接下来看到的同样,我只是确认该文件是否真实存在,若是存在的话,咱们就使用文件的内容初始化数组:

func loadMoviesInfo() {
    if let path = NSBundle.mainBundle().pathForResource("MoviesData", ofType: "plist") {
        moviesInfo = NSMutableArray(contentsOfFile: path)
    }
}

咱们将会在viewDidLoad()中调用上面的函数。可是你确保该函数在configureTableView()函数以前被调用,就像下面代码同样:

erride func viewDidLoad() {
    super.viewDidLoad()

    // Load the movies data from the file.
    loadMoviesInfo()

    configureTableView()
    navigationItem.title = "Movies"
}

请注意,你也能够不用自定义一个函数来完成文件的加载。可是做为有代码对齐强迫症的我来讲,封装到一个函数是一个更好点的方法,即便是对于这么简单的功能。

在肯定全部的电影数据已经在应用启动时候就已经所有加载后,咱们能够开始来修改tableview的实现,以实现电影数据的展现。这里所须要作的事情并非不少:咱们根据电影的的数量来定义行数,而后咱们在tableview cell中正确的显示出来。

显然,行数应该和电影的数量是同样的。可是咱们首先不能忘了必须确保数据确实存在,不然应用加载一个不存在的数据的时候会致使崩溃。

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if moviesInfo != nil {
        return moviesInfo.count
    }

    return 0
}

下面,就该轮到将数据展现出来了。为了最终的演示,你能在起始工程里面找到一个继承于UITableViewCellMovieSummaryCell的子类,还有一个于只对应的.xib文件。

clipboard.png

cell会展现一个图像,题目,以及部分介绍和评价。全部的UI控件都与IBOutlet属性进行了关联,这些属性名称你能够在MovieSummaryCell.swift文件中找到。

@IBOutlet weak var imgMovieImage: UIImageView!

@IBOutlet weak var lblTitle: UILabel!

@IBOutlet weak var lblDescription: UILabel!

@IBOutlet weak var lblRating: UILabel!

上面变量的名称以及代表了本身的目的,下面咱们让它将与之相关的电影数据展现出来。咱们回到ViewController.swift文件中,像下面这样更新一下函数里面的代码:

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
}

上面使用的变量currentMovieInfo其实能够省略不写,可是有了这个变量会让代码书写变的容易一些。

如今你能够容许代码了,如何一切顺利的话,你能够看见一个带有电影信息的tableview列表。当目前为止,咱们完成的工做相信你们早就熟悉了,因此就直接开始第二步吧:展现电影的详细信息。

显示详细数据

咱们使用MovieDetailsViewController类来展现咱们选中的电影的详细信息。Interface Builder中的各个场景已经存在了,接下来咱们须要作两件事:首先,将ViewController中的详细数据传递过来,该数据来源于前面定义的那些UI控件中。

因此,咱们在MovieDetailsViewController类的开始处也定义一个变量:

var movieInfo: [String: String]!

先回到ViewController.swift文件中去看看当用户点击某一行电影的时候咱们须要作些什么。当事件发生的时候,咱们须要知道是当前被点击数据的索引行号,接着咱们就能够从电影数组中找出对应的数据并在idSegueShowMovieDetails触发界面切换时传递给下一个视图控制器。得到行号索引时比较容易的,可是咱们须要一个自定义属性来存储它,所以咱们在ViewController类中进行以下声明:

var selectedMovieIndex: Int!

接下来,咱们就按照下面的方式来处理点击选中事件:

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    selectedMovieIndex = indexPath.row
    performSegueWithIdentifier("idSegueShowMovieDetails", sender: self)
}

在这里咱们作了简单的两件事情:第一保存所选的行号到属性里面,而后触发界面切换事件到电影详细介绍页。

而后这里还缺乏了东西,咱们并无获取相应的数据并将数据传递到MovieDetailsViewController类中。因此咱们须要像下面这样重载prepareForSegue:sender:函数:

override 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]
        }
    }
}

很简单对吧!咱们经过seguedestinationViewController属性实现了对MovieDetailsViewController实例的访问,并将咱们得到的数据赋值到了这部分开头声明的变量中。

如今,咱们再次到开MovieDetailsViewController.swift文件,咱们须要定义一个函数。在这个函数里面,咱们须要将movieInfo变量中的值赋值到对应的UI控件中去,咱们的任务到这里也就完成了。下面的代码很简单我就不进行讲解了:

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"]!)
}

最后,咱们在viewWillAppear函数里面调用上面的函数:

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    
    lblRating.layer.cornerRadius = lblRating.frame.size.width/2
    lblRating.layer.masksToBounds = true
    
    if movieInfo != nil {
        populateMovieInfo()
    }
}

这一部分也完成了,你能够运行应用看看效果。

为Spotlight创建数据索引

使用iOS9中的Core Spotlight框架,可让应用中的数据在Spotlight中被搜索到。而这么作的关键是经过Core SpotlightAPI得到数据的索引,这样它才能被搜索并展现给用户。可是不管是应用自己仍是CS(Core Spotlight)都不能本身决定什么样的数据应该被搜索和展现。以特殊的形式将咱们的数据提供给API接口是咱们本身应该作的事情。

进一步来讲:全部那些咱们但愿能被Spotlight搜索到的数据首先必须是一个CSSearchableItem对象,而后这些对象被放入一个数组中并将索引提供给API。一个CSSearchableItem对象里面包含了一个属性集。该属性集将这个对象的全部细节都提供给了iOS系统,例如什么样的数据应该在搜索的时候显示出来(像电影名称、图片、描述),什么关键字能让咱们的应用出如今Spotlight中。一个单一的CSSearchableItem对象中的全部属性使用一个CSSearchableItemAttributeSet对象进行表示。该对象提供了不少的属性用于咱们进行赋值。你能够查看连接进行进一步了解。

创建索引是这篇教程所须要作的最后一步,一般状况下有如下几个步骤:

  1. 为每个数据设置数据,例如一个电影(CSSearchableItemAttributeSet对象)

  2. 使用第一步设置的属性来实例化一个可搜索对象(CSSearchableItem对象)。

  3. 将全部对象放入到一个数组中。

  4. Spotlight使用上面数组中的数据查询数据

咱们按照上面的步骤一步步实现咱们的目的,首先咱们在ViewController.swift文件中定义一个函数setupSearchableContent()。在这部分的最后,你回发现让数据可以被搜索到其实并非很难的一件事。固然,咱们也不可能一步就实现这个目标,就像我没法一次就把全部的代码实现都给你同样;取而代之的是我把代码分散开来进行讲解,这样也有利于你消化吸取。不用担忧也没多少东西。

在你编写自定义函数以前,首先你须要引入两个框架:

import CoreSpotlight
import MobileCoreServices

下面咱们就来编写函数,并定义一个数组来存放可搜索对象:

func setupSearchableContent() {
    var searchableItems = [CSSearchableItem]()

}

咱们在一个循环里面访问每个电影:

func setupSearchableContent() {
    var searchableItems = [CSSearchableItem]()

    for i in 0...(moviesInfo.count - 1) {
        let movie = moviesInfo[i] as! [String: String]
    }
}

对于每个电影,咱们都建立一个CSSearchableItemAttributeSet对象来存储那些在Spotlight搜索到的时候须要在结果中显示的数据。在这个演示应用里面,咱们将电影名称、电影描述、图片这些属性设置进去。

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)

        // Set the title.
        searchableItemAttributeSet.title = movie["Title"]!

        // Set the movie image.
        let imagePathParts = movie["Image"]!.componentsSeparatedByString(".")
        searchableItemAttributeSet.thumbnailURL = NSBundle.mainBundle().URLForResource(imagePathParts[0], withExtension: imagePathParts[1])

        // Set the description.
        searchableItemAttributeSet.contentDescription = movie["Description"]!
    }
}

注意上面的代码,咱们是如何将电影图片设置为一个属性的。这里有两个方法可以实现:一个是使用图片的URL,或者使用图像的NSData对象。这里简单一点的方法就是提供每个电影图片的URL,由于咱们知道这些图片都在应用里面。可是这么作要求咱们将图片名称划分为图片真是名称和图片类型拓展,因此我使用了String类中的componentsSeparatedByString方法。剩下的部分应该很好理解。

接下来就该轮到设置应用在Spotlight中搜索时的关键字了。关键字必定要提早想好,由于这决定很大程度上影响了你的应用被Spotlight和用户搜索到的可能。在演示应用中咱们使用电影类型和明星来做为关键字。

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
    }
}

请注意在MoviesData.plist文件中电影的类型是一个用逗号进行分隔的单一字符串。因此咱们要将里面全部的种类都分离出来后存储到数组变量movieCategories中。而后再使用循环将里面的每一个类型添加到关键字数组keywords中。对于演员明星,咱们使用同样的步骤进行处理。

上面代码中最重要的是最后一步;咱们将关键字数组设置到每一个电影的属性中。忘记这一行代码的话,Spotlight中将不会显示任何结果。

如今咱们已经有了关键字属性了,下面该实例化可搜索对象了并将该对象添加到可搜索对象数组中。

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)
    }
}

上面的实例化接受三个参数:

  • uniqueIdentifier: 这是当前可搜索对象在Spotlight中的惟一标识。你能够以你本身喜欢的方式编写这个标识,可是又一个细节须要注意:在这个例子里,咱们将当前索引添加到标识里,由于后面咱们须要这个索引来查找匹配要显示的电影细节。通常状况下,将指向要显示数据细节的值添加到标识里是一个不错的想法。真能让你更好的理解电影的索引值的意义。

  • domainIdentifier: 使用这个参数将可搜索对象组合到一块儿

  • attributeSet: 这是咱们刚才进行复杂设置的属性。

最后,这个一个新的可搜索对象被添加到searchableItems数组中了。

还有最后一步咱们须要作:就是使用Core SpotlightAPI对可搜索对象简历索引。该步骤是在for循环外面完成的。

func setupSearchableContent() {
    ...

    CSSearchableIndex.defaultSearchableIndex().indexSearchableItems(searchableItems) { (error) -> Void in
        if error != nil {
            print(error?.localizedDescription)
        }
    }
}

上面函数完成后,咱们须要在viewDidLoad()函数对它进行调用:

ovrride func viewDidLoad() {
    ...

    setupSearchableContent()
}

显示演示应用已经可以使用Spotlight搜索到结果了。运行代码并退出应用到Spotlight中使用关键词进行搜索吧。与之相关的数据会展示在你眼前。点击这些结果,应用会自动启动。

clipboard.png

更具针对性的目标

让应用数据可以在Spotlight被搜索到已经很不错了,然而咱们还能够作的更好。当咱们点击结果的时候,应用会启动并切换界面到电影列表界面,可是咱们的目标当点击的时候直接跳转到详细介绍界面。

虽然这样作听起来很困难和复杂,可是最终你会看到其实很简单。在这个演示应用中这个就更简单了,咱们基于已有的东西以便管理那些点击选中后须要展现的电影的详细信息。

在这里咱们的主要工做的重载UIKit中名为restoreUserActivityState的函数,而且处理Spotlight中的点击事件。咱们最终要实现的是根据Spotlight中的结果的标识找到其在moviesInfo数组中对于的索引号(若是你还记得的话该标识是在前面一部分建立的),而后依据改索引号将正确的数据传递到MovieDetailsViewController中去。

上面那个函数的参数是一个NSUserActivity对象。该对象又一个名为userInfo的字典属性,该属性中含有在Spotlight中所选结果的标识。从这个标识里面咱们就能得到选中电影在moviesInfo数组中的索引,而后咱们将对象的数据传递过去。这就是函数的整个过程。

下面是具体的实现:

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)
        }
    }
}

正如你所看见,首先须要检查activity的类型是否是CSSearchableItemActionType类型。老实说,在这个程序里这么作其实不是很重要,可是若是你须要处理应用中多个NSUserActivity对象的话你就不能忘记须要这样作(例如:iOS8中就引入的Handoff特性中使用的NSUserActivity类) 。在userInfo字典里的标识符是一个字符串。当你得到标示后,咱们按照.号来分解该字符串,其中最后一个久表明着咱们选中电影在集合中的索引。剩余的代码就很容易了:咱们将这个索引赋值给selectedMovieIndex变量,而后再触发界面切换。以前的代码已经实现了切换。

如今咱们打开AppDelegate.swift文件。咱们须要实现一个代理函数。该函数在每次Spotlight中与演示应用相关的数据被选中点击时都会被调用,而的职责就是调用该函数并传递用户激活的对象。下面就是代码实现:

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
}

在上面代码片断中,咱们访问windowview controller的属性并恢复用户状态。固然,咱们也能够采用下面的方法实现:使用NSNotificationCenter并传递一个自定义notification,而后在ViewController里处理该通知。可是前面的方法更直接一点。

好了,教程到此为止。演示应用应该能完成全部想要的结果了。

clipboard.png

总结

对于开发者来讲iOS9中的新的搜索API看起来一片美好,由于这让咱们的应用更容易被用户发现的使用。在这篇教里面,咱们全部的工做都是围绕着让应用中的数据可以在Spotlight搜索中能被搜索到,以及当用户选中搜索结果时该如何处理事件将详细的内容展现出来。在你本身的应用中实现该功能的话绝对会提高用户体验,所以该特性时你须要认真思考添加到你当前或者之后的工程中的。再一次咱们来到了文章的结尾,我但愿这个教程对你有帮助。

注意:原文中的其实工程被墙了。我为你们献上微云连接。完整的工程做者并无提供,下面是我根据教程完成的Spotlt

相关文章
相关标签/搜索