如何使用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

相關文章
相關標籤/搜索