如何使用 Core Spotlight

前言:
先放出原文地址: AppCoda, 支持原創(chuàng)作品哈.
本文為上面這篇文章的譯文, 原文中使用 Swift 語言, 本文使用 Objective-C 語言, 其中大部分內(nèi)容為翻譯(其中有些沒用的廢話就沒翻譯), 有一部分自己的理解. 示例代碼也有修改, 和原文中有出入. 喜歡看英文文檔的小伙伴請直接查看原文.

伴隨著每一個(gè)新版本 iOS 的更新, 蘋果都會給全世界的開發(fā)者們帶來一些新的技術(shù), 當(dāng)然 iOS9 也不會違背這個(gè)傳統(tǒng). 其中之一就是 Core Spotlight 框架, 它包含了許多很棒的 APIs, 一旦開發(fā)者們將它們集成在自己的應(yīng)用中, 就可以讓他們的應(yīng)用提升到一個(gè)新的高度.

Core Spotlight 框架是蘋果提供的 APIs 集合中的一部分, 被稱之為 Search APIs. 它可以幫助你很有效的拉近與用戶之間的距離, 讓用戶更容易訪問你的應(yīng)用程序. 除了 Core Spotlight 之外, iOS9 中其它的搜索功能還包括:

  1. NSUserActivity: 新的方法和屬性(用來保存 App 當(dāng)前狀態(tài), 并在以后恢復(fù)該狀態(tài)).
  2. Web Markup: 可以讓你在設(shè)備中搜索 Web 中的內(nèi)容.
  3. Universal Links: 通過 Web 直接開啟應(yīng)用程序.

在本篇文章中, 我們不會去討論上面這三個(gè)搜索功能, 我們只專注于 Core Spotlight 框架. 首先我們來看一下 Core Spotlight 到底是什么.

圖片出自: AppCoda

Core Spotlight Framework 可以讓你 App 中的內(nèi)容在 Spotlight 中搜索到, 并且將相關(guān)的搜索結(jié)果展現(xiàn)給用戶, 并且允許用戶和搜索的結(jié)果進(jìn)行交互. 當(dāng)用戶選擇了其中一個(gè)搜索的結(jié)果后, 不但可以自動(dòng)的打開你的應(yīng)用程序, 同時(shí)還可以跳轉(zhuǎn)到指定的頁面來查看詳細(xì)的內(nèi)容.

從開發(fā)者的角度來看, 集成 Core Spotlight 框架并使用它的 APIs 并不復(fù)雜. 通過接下來的教學(xué), 你會發(fā)現(xiàn)必要的代碼可能也就只有幾行就夠了. 其中最核心的處理過程就是開發(fā)者需要讓 iOS 對應(yīng)用程序中的數(shù)據(jù)進(jìn)行索引操作.

本篇教學(xué)主要是針對 Core Spotlight 框架, 但是我并不打算在本篇文章中對一些細(xì)節(jié)部分進(jìn)行講解. 如果你有幸去學(xué)習(xí)一些我個(gè)人認(rèn)為很棒的東西, 那就請繼續(xù)閱讀本篇文章. 我很自信的告訴你, 在你閱讀完本篇文章之后, 你會發(fā)現(xiàn)集成 Core Spotlight 框架, 以及使用 Spotlight 搜索你應(yīng)用中的內(nèi)容是多么的簡單.

關(guān)于 Demo

和往常一樣, 我們會通過一個(gè)實(shí)例程序來進(jìn)行講解和學(xué)習(xí). 這一次我們的重點(diǎn)內(nèi)容是在 App 內(nèi)填充一些數(shù)據(jù), 并允許這些數(shù)據(jù)在 Spotlight 中進(jìn)行搜索. 除了重點(diǎn)之外, 我們還需要更多的了解一下實(shí)例程序.

實(shí)例程序的主要目的是為了展示一些電影相關(guān)的數(shù)據(jù), 例如: 簡介跛蛋、導(dǎo)演、評分等. 所有的數(shù)據(jù)都會用 TableView 來進(jìn)行展示, 當(dāng)選中一個(gè)電影, 會進(jìn)入到一個(gè)新的詳情頁面來進(jìn)行展示. 基本上就是這些內(nèi)容, 我們通過這些數(shù)據(jù)以及功能來學(xué)習(xí) Core Spotlight 是如何工作的. 實(shí)例程序中的數(shù)據(jù)來自這里.

通過下面這張動(dòng)圖, 來感受一下實(shí)例程序.


圖片出自: AppCoda

在本篇教學(xué)中, 我們有兩個(gè)目標(biāo): 最重要的一個(gè)目標(biāo)是, 我們需要讓示例程序中的所有電影數(shù)據(jù)都可以在 Spotlight 中通過關(guān)鍵詞來進(jìn)行搜索. 當(dāng)然, 設(shè)置關(guān)鍵詞也是我們的任務(wù)之一.

當(dāng)用戶點(diǎn)擊了一條搜索結(jié)果, 應(yīng)用程序?qū)詣?dòng)打開, 接下來就是我們的第二個(gè)目標(biāo), 如果我們不做任何處理, 那么默認(rèn)的視圖控制器將會被加載并呈現(xiàn)在用戶眼前, 也就是我們的首頁(電影列表的那個(gè)頁面). 然而當(dāng)我們站在用戶體驗(yàn)的角度來思考這個(gè)問題, 其實(shí)這樣并不是一個(gè)完美的解決方案. 完美的解決方案是, 當(dāng)用戶選擇了一個(gè)搜索結(jié)果, 我們應(yīng)該啟動(dòng)應(yīng)用程序, 并給用戶展示對應(yīng)電影的詳情數(shù)據(jù), 這就是我們得第二個(gè)目標(biāo). 簡單來說, 我們不僅要做到在 Spotlight 中所搜到應(yīng)用中的電影, 我們還要在用戶選擇了某一個(gè)電影后, 開啟應(yīng)用程序并進(jìn)入到詳情頁面來顯示電影的詳情信息. 看下面這張動(dòng)圖, 你就明白了:

圖片出自: AppCoda

為了不浪費(fèi)時(shí)間, 你可以在這里下載實(shí)例程序. 在實(shí)例程序中, 你將會看到以下的內(nèi)容:

  • UI 以及 IBOutlet 已經(jīng)完成.
  • 最簡單的實(shí)現(xiàn)了 UITableView.
  • 所有的電影數(shù)據(jù)都在 .plist 文件中, 另外還包含了對應(yīng)的圖片(一共5個(gè)).

用一張圖片來解釋, 下圖中展示了 .plist 文件中包含數(shù)據(jù)以及數(shù)據(jù)的結(jié)構(gòu).

圖片出自: AppCoda

在學(xué)習(xí) Core Spotlight 之前, 先來完成兩個(gè)小任務(wù):

  1. 加載數(shù)據(jù), 并填充在 UITableView 中.
  2. Detail View Controller 中顯示選中的電影詳情.

在實(shí)例工程的初始階段, 如果我實(shí)現(xiàn)了上面這兩個(gè)功能會使我們更快的進(jìn)入 Core Spotlight 的學(xué)習(xí)階段, 但是我并沒有這么做, 原因很簡單: 我相信通過這個(gè)過程, 你會更容易理解這些數(shù)據(jù)是如何從一些普通的數(shù)據(jù)變?yōu)?Spotlight 可搜索到的數(shù)據(jù). 不用擔(dān)心, 實(shí)現(xiàn)上面兩個(gè)功能很簡單, 我們會很快完成的.

加載毕泌、展示實(shí)例數(shù)據(jù)

OK, 我們現(xiàn)在就可以開始了, 假設(shè)你現(xiàn)在已經(jīng)下載好了實(shí)例程序, 并且已經(jīng)對 .plist 文件中的電影數(shù)據(jù)有了一定的了解. 我們的第一個(gè)任務(wù)是將 .plist 文件中的數(shù)據(jù)加載到數(shù)組中, 并將它們填充到 TableView 中.

我們直接開始寫代碼, 打開 MovieListViewController.m 文件, 先來定義一個(gè)數(shù)據(jù)源數(shù)組:

@property (nonatomic, strong) NSMutableArray<MovieModel *> *moviesInfo;

所有的電影數(shù)據(jù), 都會被加載到這個(gè)數(shù)組中. 每一個(gè)電影數(shù)據(jù)都用一個(gè) MovieModel 來表示. 這個(gè)數(shù)據(jù)模型的邏輯已經(jīng)寫完了, 有興趣的同學(xué)可以看看, 本人實(shí)在是懶得導(dǎo)入 MantleYYModel 框架了, 所以就直接用 objectForKey 解析數(shù)據(jù)了. 哈哈! 尷尬!

.plist 數(shù)據(jù)轉(zhuǎn)數(shù)據(jù)模型的代碼已經(jīng)封裝在 MovieModel 中了, 在 MovieListViewController.m 中添加以下代碼:

#pragma mark - Lazy Load
- (NSMutableArray<MovieModel *> *)moviesInfo {
    if (!_moviesInfo) {
        _moviesInfo = [MovieModel models];
    }
    return _moviesInfo;
}

接下來我們來修改 TableView 的數(shù)據(jù)源方法, 來展示數(shù)據(jù): 首先根據(jù)數(shù)據(jù)源的數(shù)量, 返回 Row 的個(gè)數(shù), 然后在 Cell 中展示相應(yīng)的內(nèi)容.

我們從 numberOfRows 方法開始修改, 很明顯在這里我們應(yīng)該返回?cái)?shù)據(jù)源數(shù)組的個(gè)數(shù).

#pragma mark - UITableView DataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.moviesInfo.count;
}

最后, 我們來將數(shù)據(jù)顯示在 TableView 中, 在工程中你會看到一個(gè) UITableViewCell 的子類 MovieCell, 它包含了一個(gè) Xib 文件用來描述 Cell 的樣式.

Movie Cell

MovieCell 展示了電影的圖片、標(biāo)題之众、一部分的描述和電影評分. 所有的 UI 控件都已經(jīng)生成了對應(yīng)的 IBOutlet 屬性, 你可以在 MovieCell.h 中進(jìn)行查看:

@property (nonatomic, weak) IBOutlet UIImageView *movieImageView;

@property (nonatomic, weak) IBOutlet UILabel *labelTitle;

@property (nonatomic, weak) IBOutlet UILabel *labelDesc;

@property (nonatomic, weak) IBOutlet UILabel *labelRating;

上面這些屬性的名字已經(jīng)很明顯的代表了它們所對應(yīng)的 UI 控件, 現(xiàn)在我們就用它們來展示電影的內(nèi)容. 回到 MovieListViewController.m 文件中, 利用下面的代碼塊更新 cellForRowAtIndexPath 方法:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    MovieCell *cell = [tableView dequeueReusableCellWithIdentifier: @"MovieCell" forIndexPath: indexPath];
    
    MovieModel *movieModel = self.moviesInfo[indexPath.row];
    cell.labelTitle.text = movieModel.title;
    cell.labelDesc.text = movieModel.desc;
    cell.labelRating.text = movieModel.rating;
    cell.movieImageView.image = movieModel.image;
    
    return cell;
}

OK, 現(xiàn)在你可以將實(shí)例程序跑起來看一下效果了, 至今為止我們所做的這些, 對于每一個(gè)開發(fā)者來說都應(yīng)該是非常簡單的, 所以在這里不做過多的講解, 直接進(jìn)入下一個(gè)階段: 選中一部電影, 在詳情頁面展示電影的詳細(xì)內(nèi)容.

顯示電影詳情

MovieDetailViewController.m 文件中, 我們將會顯示選中電影的詳情信息. 在 Storyboard 中我已經(jīng)添加好了詳情視圖控制器. 在這里我們需要做兩件事: 首先是將 MovieListViewController 中選中電影的數(shù)據(jù)模型傳遞給 MovieDetailViewController. 其次是利用數(shù)據(jù)模型的內(nèi)容對 MovieDetailViewControllerUI 控件進(jìn)行數(shù)據(jù)的填充.

MovieDetailViewController.h 文件該為下面代碼塊顯示的一樣:

@class MovieModel;

@interface MovieDetailViewController : UIViewController

@property (nonatomic, strong) MovieModel *movieModel;

@end

接下來我們暫時(shí)先回到 MovieListViewController.m 文件中, 來看一下當(dāng)選中了一部電影之后, 我們需要做哪些事情. 當(dāng)點(diǎn)擊事件發(fā)生之后, 我們希望知道點(diǎn)擊的是第幾部電影, 并且獲取到數(shù)據(jù)源數(shù)組中對應(yīng)的數(shù)據(jù)模型, 并將它傳遞給 MovieDetailViewController. 利用 TableView 的代理方法來獲取對應(yīng)的數(shù)據(jù)模型很簡單, 但是我們需要將它保存下來, 所以在 MovieListViewController.m 中我們需要再定義一個(gè)成員變量:

@interface MovieListViewController () <UITableViewDelegate, UITableViewDataSource> {
    
    NSInteger _selectedMovieIndex;
}

然后我們來處理 didSelectRowAtIndexPath 方法, 在 MovieListViewController.m 文件中添加以下代碼:

#pragma mark - UITableView Delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    
    [tableView deselectRowAtIndexPath: indexPath animated: YES];
    _selectedMovieIndex = indexPath.row;
    [self performSegueWithIdentifier: @"idSegueShowMovieDetails"
                              sender: self];
}

我們在該方法中做了三件事: 第一件事是取消了 Cell 的選中狀態(tài), 第二件事是將選中的行數(shù)保存了下來, 第三件事是執(zhí)行了一個(gè) Segue(PushMovieDetailViewController). 然而僅做這三件事還是不夠的, 因?yàn)槲覀冞€沒有從數(shù)據(jù)源數(shù)組中獲取到對應(yīng)的數(shù)據(jù)模型, 并且我們還沒有向 MovieDetailViewController 傳遞任何數(shù)據(jù). 那我們現(xiàn)在該怎么做呢? 很簡單, 只需要復(fù)寫 prepareForSegue:sender: 方法, 來看下面的代碼塊:

#pragma mark - Override Methods
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    
    if ([segue.identifier isEqualToString: @"idSegueShowMovieDetails"]) {
        
        MovieDetailViewController *detailViewController = segue.destinationViewController;
        detailViewController.movieModel = self.moviesInfo[_selectedMovieIndex];
    }
}

非常簡單, 我們通過 seguedestinationViewController 屬性來獲取 MovieDetailViewController 的實(shí)例對象, 然后我們從 moviesInfo 數(shù)據(jù)源數(shù)組中獲取到了相應(yīng)的數(shù)據(jù)模型, 并將數(shù)據(jù)模型傳遞給了 MovieDetailViewController.

現(xiàn)在, 回到 MovieDetailViewController.m 中, 我們添加下面這段代碼:

#pragma mark - Private Methods
- (void) populateMovieInfo {
    
    self.titleLabel.text = self.movieModel.title;
    self.categoryLabel.text = self.movieModel.category;
    self.descLabel.text = self.movieModel.desc;
    self.directorLabel.text = self.movieModel.director;
    self.starsLabel.text = self.movieModel.stars;
    self.ratingLabel.text = self.movieModel.rating;
    self.movieImageView.image = self.movieModel.image;
}

我們將數(shù)據(jù)模型中的內(nèi)容填充到了控件中, 注意, 我們需要在 viewDidLoad 中調(diào)用該方法.

#pragma mark - Life Cycles
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self setupUI];
    
    [self populateMovieInfo];
}

這一部分的內(nèi)容基本就這么多了, 現(xiàn)在你可以跑一下示例程序來看看效果. 然后我們就開始進(jìn)入下一階段的學(xué)習(xí).

對數(shù)據(jù)進(jìn)行索引操作

使用 Core Spotlight 框架, 可以讓應(yīng)用中的內(nèi)容在 Spotlight 中搜索到, 達(dá)成這一目的的關(guān)鍵步驟就是調(diào)用 Core SpotlightAPI 來對相應(yīng)的數(shù)據(jù)進(jìn)行索引操作, 這樣一來, 當(dāng)用戶使用 Spotlight 進(jìn)行搜索時(shí), 就能搜索到相應(yīng)的數(shù)據(jù)了. 至于哪些數(shù)據(jù)可以被搜索到, 不是應(yīng)用程序決定的, 也不是 Core Spotlight 決定的, 而是我們決定的, 所以我們有責(zé)任為 Core Spotlight 提供數(shù)據(jù), 告知哪些數(shù)據(jù)是允許被搜索的.

所有允許被搜索到的數(shù)據(jù), 都被描述成為一個(gè) CSSearchableItem 對象, 將這些對象放到一個(gè)數(shù)組中, 遞交給 Core Spotlight 框架中相應(yīng)的 API 來進(jìn)行索引操作. 一個(gè) CSSearchableItem 中包含了一系列的屬性用來描述一個(gè)允許被搜索的數(shù)據(jù), 例如: 電影名稱朴皆、圖片、描述萌庆、搜索關(guān)鍵詞等... 在 CSSearchableItem 中所有的這些屬性都被描述成為了一個(gè) CSSearchableItemAttributeSet 對象, 該對象中提供了這些我們可能需要用到的屬性. 作為參考, 在這里給你一個(gè)官方文檔的鏈接進(jìn)行查看.

對數(shù)據(jù)進(jìn)行索引操作是最后一步, 也是必須要做的一步. 一般包含以下幾個(gè)步驟:

  1. CSSearchableItemAttributeSet: 設(shè)置搜索對象的屬性.
  2. CSSearchableItem: 為每一個(gè)搜索數(shù)據(jù)創(chuàng)建一個(gè) CSSearchableItem 對象, 并關(guān)聯(lián)第一步中生成的屬性對象.
  3. 將所有的 CSSearchableItem 對象放入到數(shù)組中.
  4. 將數(shù)組遞交給 Core Spotlight 對應(yīng)的 API 對數(shù)據(jù)進(jìn)行索引操作.

我們接下來會一步一步的根據(jù)上面這四個(gè)步驟進(jìn)行操作. 不過在這之前我們需要在 MovieListViewController.m 中新增一個(gè)方法叫 - (void)setupSearchableContent. 當(dāng)我們完成這個(gè)方法的實(shí)現(xiàn)部分之后, 你會發(fā)現(xiàn)其實(shí)很簡單. 不過我不會把所有的實(shí)現(xiàn)代碼一口氣都寫出來, 取而代之的是, 我將會把它分成幾個(gè)小的片段, 我相信這樣對于你來說會更易于理解.

在我們實(shí)現(xiàn)這個(gè)方法之前, 先來到 MovieListViewController.m 的最上面, 我們可以看到在實(shí)例代碼中, 我引入了兩個(gè)系統(tǒng)的頭文件:

#import <CoreSpotlight/CoreSpotlight.h>
#import <MobileCoreServices/MobileCoreServices.h>

OK, 現(xiàn)在我們開始實(shí)現(xiàn)這個(gè)方法. 先來定義一個(gè)變量 index, 然后來寫一個(gè) for...in 循環(huán):

NSInteger index=0;
for (MovieModel *movieModel in self.moviesInfo) {

}

對于每一個(gè)電影來說, 我們都會創(chuàng)建一個(gè) CSSearchableItemAttributeSet 對象, 然后我們將會設(shè)置一些屬性, 這些屬性將會在用戶搜索的時(shí)候, 在 Spotlight 中進(jìn)行顯示. 在我們得示例代碼中, 我們來設(shè)置一下電影的名稱燥透、圖片以及描述.

for (MovieModel *movieModel in self.moviesInfo) {
        
        CSSearchableItemAttributeSet *attributeSet = [[CSSearchableItemAttributeSet alloc] initWithItemContentType: (NSString *)kUTTypeText];
        
        // Set the title
        attributeSet.title = movieModel.title;
        
        // Set the movie image
        NSArray<NSString *> *imagePathParts = [movieModel.imageName componentsSeparatedByString: @"."];
        attributeSet.thumbnailURL = [[NSBundle mainBundle] URLForResource: [imagePathParts firstObject] withExtension: [imagePathParts lastObject]];
        
        // Set the description
        attributeSet.contentDescription = movieModel.desc;
    }

在上面這個(gè)代碼片段中, 需要注意一下我們是如何設(shè)置圖片這個(gè)屬性的, 我們有兩種方法可以設(shè)置圖片: 我們可以提供一個(gè)圖片的 URL, 也可以直接使用一個(gè)圖片的 NSData 對象. 對于我們來說, 最簡單的方法就是提供一個(gè)圖片文件的 URL.

現(xiàn)在, 我們來設(shè)置關(guān)鍵詞, 在設(shè)置關(guān)鍵詞之前, 你應(yīng)該好好的考慮一下你需要哪些關(guān)鍵詞, 因?yàn)殛P(guān)鍵詞對于用戶的搜索來講, 是至關(guān)重要的. 在示例程序中, 我們會將電影的分類以及演員陣容設(shè)置為關(guān)鍵詞. 看下面代碼片段:

for (MovieModel *movieModel in self.moviesInfo) {
        
        // ....
                
        // Set the keywords
        NSMutableArray<NSString *> *keywords = [NSMutableArray array];
        NSArray *movieCategroies = [movieModel.category componentsSeparatedByString: @", "];
        for (NSString *category in movieCategroies) {
            [keywords addObject: category];
        }
        
        NSArray *stars = [movieModel.stars componentsSeparatedByString: @", "];
        for (NSString *star in stars) {
            [keywords addObject: star];
        }
        
        attributeSet.keywords = keywords;
    }

你應(yīng)該知道, 電影的分來這個(gè)屬性在 MoviesData.plist 中被描述為了一個(gè)字符串, 每一個(gè)分類之間使用了 逗號 進(jìn)行分割. 所以我們有比較通過 逗號 來將這個(gè)字符串分割成一個(gè)個(gè)單獨(dú)的分類, 并將它們保存在一個(gè)數(shù)組中, 然后我們使用了一個(gè) for...in 循環(huán)將它們添加到了關(guān)鍵詞數(shù)組中(keywords). 然后我們利用同樣的步驟, 將電影的演員陣容也添加到了關(guān)鍵詞數(shù)組中.

上面代碼片段中, 最重要的一行代碼是最后一行: 我們將關(guān)鍵詞數(shù)組( keywords ) 設(shè)置給了 attributeSet 對象的 keywords 屬性. 如果你寫代碼的時(shí)候忘記了這一行, 那也就是說, 在用戶使用 Spotlight 搜索的時(shí)候, 將不會出現(xiàn)任何有關(guān)你 App 的內(nèi)容了, 切記!!!

接下來我們來初始化 CSSearchableItem 對象, 看下面代碼片段:

for (MovieModel *movieModel in self.moviesInfo) {

    // ....
    
    // Create the searchable item
    CSSearchableItem *searchableItem = [[CSSearchableItem alloc] initWithUniqueIdentifier:[NSString stringWithFormat: @"com.liguoan.CoreSpotlightDemo.%@", @(index)] domainIdentifier: @"Movies" attributeSet: attributeSet];
}

上面代碼片段中的構(gòu)造器函數(shù)包含了三個(gè)參數(shù):

  • uniqueIdentifier: 該參數(shù)標(biāo)記了這個(gè) CSSearchableItem 對象在 Spotlight 中的唯一標(biāo)識符. 你可以以你自己喜歡的方式來拼接這個(gè)標(biāo)識符. 不過有一個(gè)小細(xì)節(jié)需要記住: 在示例代碼中, 我們?yōu)闃?biāo)識符拼接了當(dāng)前遍歷出來電影的下標(biāo)(index), 因?yàn)檫^一會兒我們需要用到這個(gè)下標(biāo)來展示詳情頁面. 在標(biāo)識符中添加一個(gè)標(biāo)志, 來確定當(dāng)前的數(shù)據(jù), 這是一個(gè)非常好的方法. 過一會兒你就知道這樣做的好處了.
  • domainIdentifier: 利用這個(gè)參數(shù)來將你的 CSSearchableItem 對象進(jìn)行分組.
  • attributeSet: 這個(gè)參數(shù)就是剛才我們創(chuàng)建的 CSSearchableItemAttributeSet 對象.

到目前為止, 我們還剩下最后一步操作, 也就是調(diào)用 Core Spotlight 相應(yīng)的 API 來對數(shù)據(jù)進(jìn)行索引操作.

for (MovieModel *movieModel in self.moviesInfo) {

    // ...
        
    // Index the searchable item
    [[CSSearchableIndex defaultSearchableIndex] indexSearchableItems: @[searchableItem]
                                                       completionHandler:^(NSError * _Nullable error) {
                                                           
                                                       }];
        
    index++;
}

OK, 我們已經(jīng)將 - (void)setupSearchableContent 方法的實(shí)現(xiàn)部分寫完了, 我們需要在 ViewDidLoad 中調(diào)用一下該方法:

#pragma mark - Life Cycles
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self setupNavigationBar];
 
    [self setupTableView];
    
    [self setupSearchableContent];
}

接下來運(yùn)行我們得示例程序, 然后退出, 然后在 Spotlight 中使用我們剛才設(shè)置的關(guān)鍵詞進(jìn)行搜索, 我們會發(fā)現(xiàn), 搜索結(jié)果已經(jīng)在 Spotlight 中展示了出來, 當(dāng)我們點(diǎn)擊任意一個(gè)搜索結(jié)果, 實(shí)例程序?qū)詣?dòng)被打開, 很帥對吧?

圖片出自: AppCoda

進(jìn)入詳情頁面

現(xiàn)在我們已經(jīng)可以從 Spotlight 中搜索到應(yīng)用程序中的電影數(shù)據(jù)了, 不過我們還可以做得更完美. 到目前為止, 點(diǎn)擊 Spotlight 搜索結(jié)果, 會自動(dòng)打開示例程序, 并且顯示默認(rèn)的 MovieListViewController. 但是我們最終的目標(biāo)是當(dāng)用戶點(diǎn)擊搜索結(jié)果后自動(dòng)啟動(dòng)示例程序, 并直接進(jìn)入詳情頁面顯示電影的詳情信息.

這個(gè)最終目標(biāo)聽起來可能有些困難和復(fù)雜, 不過很快你就會看到, 其實(shí)并沒有那么復(fù)雜, 真的很簡單. 我們主要的工作就是復(fù)寫 UIKitrestoreUserActivityState: 方法, 來操作 Spotlight 中選中的搜索結(jié)果. 在這個(gè)方法中, 我們首先要從 identifier 中獲取用戶選中電影的下標(biāo)(還記得在上一部分, 我們創(chuàng)建 identifier 時(shí)后面拼接的 index 么?), 然后通過下標(biāo), 在 moviesInfo 數(shù)組中拿到對應(yīng)的數(shù)據(jù)模型, 最后將數(shù)據(jù)模型傳遞給 MovieDetailViewController 來進(jìn)行顯示.

restoreUserActivityState: 方法中包含了一個(gè) NSUserActivity 參數(shù). NSUserActivity 對象中包含了一個(gè) userInfo 字典, 這個(gè)字典中就包含了在 Spotlight 中選擇的搜索結(jié)果的 identifier. 我們看下面代碼片段:

- (void)restoreUserActivityState:(NSUserActivity *)activity {
    if ([activity.activityType isEqualToString: CSSearchableItemActionType]) {
        NSDictionary *userInfo = activity.userInfo;
        if (userInfo && userInfo.allKeys.count) {
            NSString *movieIdentifier = userInfo[CSSearchableItemActivityIdentifier];
            _selectedMovieIndex = [[[movieIdentifier componentsSeparatedByString: @"."] lastObject] integerValue];
            [self performSegueWithIdentifier: @"idSegueShowMovieDetails" sender: self];
        }
    }
}

從上面代碼塊中可以看到, 我們首先需要判斷的是 CSSearchableItemActionType 類型. 實(shí)話實(shí)說, 在我們這個(gè)示例程序中, 判斷 CSSearchableItemActionType 類型并不是必須的, 但是在工作的項(xiàng)目中, 假設(shè)你的應(yīng)用程序會操作很多 NSUserActivity 對象, 那么此時(shí)千萬不要忘記判斷這個(gè)類型(例如: Handoff 中也會使用到 NSUserActivity). 在 userInfo 中的 identifier 是一個(gè)字符串, 一旦我們獲取到了這個(gè)字符串, 我們先使用 . 符號將它分割成一個(gè)數(shù)組, 然后獲取數(shù)組中的最后一個(gè)元素, 也就是所謂的下標(biāo)(不明白的同學(xué)請往回看, 看我們拼接 identifier 的那個(gè)代碼片段), 我們將下標(biāo)通過 _selectedMovieIndex 成員變量保存下來, 最后執(zhí)行 segue.

現(xiàn)在切換到 AppDelegate.m 文件中, 在這里我們需要實(shí)現(xiàn)一個(gè)代理方法. 這個(gè)代理方法將會在 Spotlight 搜索結(jié)果點(diǎn)擊后被調(diào)用, 在這個(gè)方法中, 我們的任務(wù)就是調(diào)用剛剛實(shí)現(xiàn)的這個(gè)方法, 來看代碼片段:

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler {
    
    if ([userActivity.activityType isEqualToString: CSSearchableItemActionType]) {
        UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
        if ([navigationController isKindOfClass: [UINavigationController class]]) {
            UIViewController *bottomViewController = [navigationController.viewControllers firstObject];
            [bottomViewController restoreUserActivityState: userActivity];
        }
    }
    
    return YES;
}

在上面這個(gè)代碼片段中, 我們同樣先判斷了 CSSearchableItemActionType 類型, 接下來獲取的是 windowrootViewController, 再獲取到 MovieListViewController, 然后調(diào)用我們剛才實(shí)現(xiàn)的方法. 除了這種方法意外, 你還可以使用 NSNotificationCenter 來達(dá)到同樣的效果.

OK, 到這里我們的示例程序就完成了, Command + R 將程序跑起來試一下吧.

圖片出自: AppCoda


李國安說:

如果您在文章中看到了錯(cuò)誤 或 誤導(dǎo)大家的地方, 請您幫我指出, 我會盡快更改

如果您有什么疑問或者不懂的地方, 請留言給我, 我會盡快回復(fù)您

如果您覺得本文對您有所幫助, 您的喜歡是對我最大的鼓勵(lì)

如果您有好的文章, 可以投稿給我, 讓更多的 iOS Developer 在簡書這個(gè)平臺能夠更快速的成長

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末沙咏,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子班套,更是在濱河造成了極大的恐慌肢藐,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吱韭,死亡現(xiàn)場離奇詭異吆豹,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)理盆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門痘煤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人猿规,你說我怎么就攤上這事衷快。” “怎么了坎拐?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵烦磁,是天一觀的道長养匈。 經(jīng)常有香客問我哼勇,道長都伪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任积担,我火速辦了婚禮陨晶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘帝璧。我一直安慰自己先誉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布的烁。 她就那樣靜靜地躺著褐耳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪渴庆。 梳的紋絲不亂的頭發(fā)上铃芦,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機(jī)與錄音襟雷,去河邊找鬼刃滓。 笑死,一個(gè)胖子當(dāng)著我的面吹牛耸弄,可吹牛的內(nèi)容都是我干的咧虎。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼计呈,長吁一口氣:“原來是場噩夢啊……” “哼砰诵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起捌显,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤胧砰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后苇瓣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體尉间,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年击罪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了哲嘲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡媳禁,死狀恐怖眠副,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情竣稽,我是刑警寧澤囱怕,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布霍弹,位于F島的核電站,受9級特大地震影響娃弓,放射性物質(zhì)發(fā)生泄漏典格。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一台丛、第九天 我趴在偏房一處隱蔽的房頂上張望耍缴。 院中可真熱鬧,春花似錦挽霉、人聲如沸防嗡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蚁趁。三九已至,卻和暖如春实胸,著一層夾襖步出監(jiān)牢的瞬間他嫡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工童芹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留涮瞻,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓假褪,卻偏偏與公主長得像署咽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子生音,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 作者:AppCoda宁否,原文鏈接,原文日期:2015-12-22譯者:BigbigChai缀遍;校對:walkingwa...
    梁杰_numbbbbb閱讀 922評論 1 6
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,071評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理慕匠,服務(wù)發(fā)現(xiàn),斷路器域醇,智...
    卡卡羅2017閱讀 134,651評論 18 139
  • 慣例台谊,啰嗦一番。 嗯譬挚,這副畫其實(shí)很早之前就開始畫了的锅铅, 但途中有很多小插曲啊,把彩鉛借給別人了减宣,遇上部門游了盐须,以及...
    betteryr閱讀 337評論 4 5
  • 文/孤鳥差魚 一無所有的人 還在擺闊 你別叫醒他
    孤鳥差魚閱讀 426評論 3 6