iOS开发之Core Spotlight实战

tags:开发随笔

很早就知道iOS9有了新的Search API,不过一直都没有机会使用。今天刚好想在MarkDisk 中加入搜索功能,于是快速看了一下,并且成功实施。最终的效果如下:

spotlight搜索

概述

iOS 9 Search API概述

iOS 9 提供了两种索引方式:

  • 私有设备索引(private on-device index)。每一台iOS设备都有一个私有的索引,这个索引从来不与Apple或者在设备间共享。私有设备索引上条目只有用户可以搜到。此索引可以通过[CSSearchableIndex defaultSearchableIndex]得到。
  • Apple的服务器端索引。服务器索引只存储网站上被恰当的标志过的数据。

iOS9的新Search API主要有以下三点:

  • NSUserActivity类中提供了新的方法和属性来创建针对用户活动的索引项,这些活动包括如打开了导航点,或创建并查看内容等事件。
  • Core Spotlight框架,供应用在私有设备索引上创建相关内容的索引,并实现指向应用内的深层链接。
  • Web标记,让应用相关的网页内容变得可被搜索,从而丰富用户的搜索体验。

除了核心Search API, Apple还推荐使用下面三种技术和Search API一起使用,来提高用户搜索体验:

  • 通用链接。iOS中9之后,使用基于标准的HTTP或HTTPS的通用链接,来取代自定义URL方案。通用链接可以支持所有用户,不管他们是否安装了应用(如果用户已经安装了应用,链接直接打开应用;如果他们没有安装应用,链接会用Safari打开你的网站)。这篇博文介绍了通用链接的概念。
  • 智能应用横幅。当用户使用Safari浏览器访问你的网站的时候,智能应用横幅广告可以打开你的应用,或有机会让用户下载你的应用。
  • Handoff。Handoff可以让用户可以从一台设备到另一台设备继续工作。

Core Spotlight 框架

Core Spotlight框架用来索引应用内的内容。它创建的索引存储在设备上,不与Apple共享,也不能被其他应用或者设备访问。
Apple的指南中特别提到Core Spotlight创建的索引最好在几千的数量级别之下。索引太多很有可能会带来性能问题。

索引的创建是通过两个类完成的:

  • CSSearchableItemAttributeSet:索引属性集合,也即是索引的内容本身。集合中可以存储以下属性:title, contentDescription, thumbnailData, rating, keywords.下面这张图显示了Spotlight是如何通过这些属性展示搜索结果的:
CSSearchableItemAttributeSet
  • CSSearchableItem:用来表示一个被索引的条目,通过来可以关联到应用内的记录。CSSearchableItem依赖于,它在构建的时候需要传入一个CSSearchableItemAttributeSet对象。

实战

目标

MarkDisk 是笔者开发的一款免费的iOS原生应用,它可以将iPhone变成一个运行HTTP服务的文件服务器,用户可以使用PC浏览器将文件(文档/图片/视频等)上传到iPhone中,并创建目录将文件管理起来。在这里有一个简单的介绍。

这次增强的目标是:让用户可以在iOS中通过文件名中包含的词语搜索MarkDisk中存储的文件,并且可以打开并浏览对应的文件。

分析

问题的关键是针对文件信息的索引的创建和管理。可以将这个任务分解为以下几个子任务:

  • 给文件创建索引。
  • 在文件改变时更新索引。因为MarkDisk暂时不支持文件剪切,问题变得简单,只需要考虑文件删除的情形即可。
  • 响应链接的打开。
    有两个问题需要注意:
  • ID问题。Spotlight需要给每一个CSSearchableItem设置一个唯一的ID。考虑到MarkDisk主要管理文件,我直接用文件的路径作为唯一ID,这样会更方便进行处理。考虑到每次应用升级时其绝对路径会改变,我采用文件相对于Documents目录的路径作为唯一ID。
  • 索引的内容。如前所叙,Spotlight可以记录标题(title), 描述(contentDescription),缩略图(thumbnailData), 等级(rating), 关键词(keywords),考虑到我只是想让用户搜索到应用内管理的文件,缩略图可以省掉(缺省情况下Spotlight会在搜索显示应用),标题可以直接用文件名,等级对我来说没有意义,关键词暂时也可以不用。

代码实现

引用spotlight所需要头文件

在需要的地方引用以下两个头文件

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

索引创建

FileItem类中加入以下方法,这个方法先创建一个CSSearchableItemAttributeSet,然后通过它创建CSSearchableItem,最后将CSSearchableItem放入设备中:

-(void) buildSearchableItem{
    CSSearchableItemAttributeSet*  attributeSet = [[CSSearchableItemAttributeSet alloc] initWithItemContentType:(NSString*)kUTTypeText];
    
    attributeSet.title = self.name;
    CSSearchableItem *item = [[CSSearchableItem alloc] initWithUniqueIdentifier:
                              [self indexPath]
                                                               domainIdentifier:@"markdisk.file" attributeSet:attributeSet];
    
    [[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:@[item] completionHandler:^(NSError * error) {
        if (error) {
            NSLog(@"buildSearchableItem Error:%@",error.localizedDescription);
            
        }
    }];
}

这里使用markdisk.file作为domain ID。

索引的删除

在文件被删除时,使用如下的代码根据其相对路径删除其对应的索引:

 [[CSSearchableIndex defaultSearchableIndex] deleteSearchableItemsWithIdentifiers:@[[_itemToRemove indexPath] ] completionHandler:^(NSError * _Nullable error) {
            if (error) {
                NSLog(@"%@", error.localizedDescription);
            }
        }];

这里[_itemToRemove indexPath]可以返回文件的相对路径。
deleteSearchableItemsWithIdentifiers需要传入一个数组,所以将一个索引放入数组中:@[[_itemToRemove indexPath] ]

CSSearchableIndex类提供了三个方法来删除索引:

- deleteAllSearchableItemsWithCompletionHandler:
- deleteSearchableItemsWithDomainIdentifiers:completionHandler:
- deleteSearchableItemsWithIdentifiers:completionHandler:

分别删除应用所创建的所有索引,按domain ID删除索引,按ID删除索引。

响应搜索结果

在用户选中搜索结果时,程序要打开对应的文件。
要实现这个目标,只需要在AppDelegate中实现continueUserActivity方法。
我的实现如下:

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler {
    if ([[userActivity activityType] isEqualToString:CSSearchableItemActionType]) {
    //获取唯一ID,在MarkDisk中,它即是文件的相对路径
        NSString *uniqueIdentifier = [userActivity.userInfo objectForKey:CSSearchableItemActivityIdentifier];
       //显示对应的文件,代码略 
    ...
    }
    return YES;
}

显示文件的效果如下:

在app中响应搜索

总结

本文总结了Spotlight的相关概念和使用方法。
MarkDisk新版本已经提交,应该一周左右可以上线。

参考资料

App Search Programming Guide
一篇介绍通用链接的文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,873评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,483评论 1 306
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,525评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,595评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,018评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,958评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,118评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,873评论 0 208
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,643评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,813评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,293评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,615评论 3 262
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,306评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,170评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,968评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,107评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,894评论 2 278

推荐阅读更多精彩内容