読者です 読者をやめる 読者になる 読者になる

NSBlogger

意識高いブログ

WWDC2015で発表されたiOS9 Search APIsまとめ

iOS9

Search APIs

iOS9から「Search APIs」というのが登場します。これはSpotlightで色んな情報を検索でき、Spotlightからアプリにスムーズに遷移できるものです。たとえば、「東京駅」と入力すると地図アプリが推奨され、タップすると現在地から東京駅までのルート案内が表示されたり。WWDC2015のデモでは、「ポテト」と入力すると、料理アプリのレシピページへ直接遷移するものがありました。この機能を使うためにはネイティブアプリ側でいくつか設定をする必要がありますので、以下に紹介しますね。

WWDC2015

下記の動画が参考になります。
Introducing Search APIs - WWDC 2015 - Videos - Apple Developer
Seamless Linking to Your App - WWDC 2015 - Videos - Apple Developer

また、iOS9のアップデートページも合わせて読むとよいです。
developer.apple.com

これらの情報から探り探り使ってみました。一部動かなかったものもあるので、間違っていたら教えて下さい。

Search APIsでできること

  • Spotlightから検索→アプリで詳細を表示
  • Spotlightから検索→検索一覧から電話/メールなどを直接起動
  • Spotlight/Safariで検索→おすすめアプリとして表示
  • Safariでリンクを踏むとアプリをシームレスに起動

f:id:Kamekiti:20150618190459p:plain
Spotlightから色々検索できてアプリと連携できるというわけです。新規ユーザの囲い込みやアプリの使用率にも影響してくるので対応必須ですね。

f:id:Kamekiti:20150618190520g:plain
さらにSafariも強化されていて、リンクを踏むとアプリでその詳細が表示できます。URL Schemeでアプリを起動するのとは違い、URLはhttp/httpsのままです。
アプリをダウンロードしているユーザにはアプリへ移動、アプリをダウンロードしていないユーザにはそのままWebページを表示といった出し分けができます。Smart App Bannerもシームレスなアプリ起動に対応されていました。

Search APIsを使うために

NSUserActivity

NSUserActivityはiOS8/OSX Yosemiteから登場したクラスで、Hand Offに使われます。Hand OffはMacで作業していた内容をそのままiPhoneで継続して作業するといった、別デバイス間の作業をシームレスにつないでくれるものです。
Hand Offについては下記が参考になります。

Search APIsを利用する際にも、NSUserActivityが必要になります。
f:id:Kamekiti:20150618190644p:plain
このようにNSUserActivityを作ることでCSSearchableIndexに登録させることができます。
Spotlightで検索できる対象はCSSearchableIndexに集約されるようです。

@property (nonatomic, strong) NSUserActivity *userActivity;
 self.userActivity = [[NSUserActivity alloc]initWithActivityType:@"com.example.hogehoge"];
 self.userActivity.title = @"Search APIs Demo";
 self.userActivity.keywords = [NSSet setWithArray:@[@"Search", @"API", @"hogehoge"]];
 self.userActivity.userInfo = @{@"id":@"hogehoge"};
 self.userActivity.webpageURL = [NSURL URLWithString:@"http://example.com/"];
 self.userActivity.eligibleForSearch = YES;
 [self.userActivity becomeCurrent];

こんな感じでNSUserActivityを作ることができます。eligibleForSearchを有効にすることでSpotlightの結果に表示されるようになります。(なぜかうまく動作せず…)NSUserActivityをpropertyとして持たせると動きました!指定したキーワードで検索すると対象アプリが表示されます。expirationDateを設定すると、検索対象期間を設けることができますよ。
f:id:Kamekiti:20150618190711p:plain
設定するとSpotlightこのように表示されます。

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity
 restorationHandler:(void(^)(NSArray *restorableObjects))restorationHandler {
    NSString *activityType = userActivity.activityType;
    if ([activityType isEqual:@"com.example.hogehoge"]) {
        return YES;
    }
    return NO;
}

appDelegateでapplication:continueUserActivity:restorationHandler:メソッドを実装しましょう。Spotlightでタップされた後、このメソッドが呼ばれます。

<key>NSUserActivityTypes</key>
<array>
    <string>com.example.hogehoge</string>
</array>

もしかするとInfo.plistに上記のような記述が必要かもしれません。いかんせん、現状うまく動作確認ができず…。
f:id:Kamekiti:20150618190756p:plain
eligibleForPublicIndexingを有効にすることで、Appleが管理するCloud Indexにもその情報を登録させることができます。
f:id:Kamekiti:20150618190808p:plain
たとえば、多くのユーザが「花火」と入力して「花火大会2015」というアプリを起動していたとしましょう。AppleのCloud Index内で「花火」というキーワードに対して人気が高いアプリと認識されると、「花火大会2015」をインストールしていないユーザにも検索結果に「花火大会2015」が表示されるのだと思います。そのため、requiredUserActivityKeys(?)またはwebpageURLの設定が必須の模様。

CoreSpotlight

CoreSpotlightはiOS9から登場します。先ほどと同様にSpotlightで表示するアイテムを登録することができます。違いとしては、登録したアイテムの削除や変更をすることができる点です。
また、NSUserActivityにはPublic Indexのオプションがありましたが、CoreSpotlightはプライベートでしか利用できません。CoreSpotelightではユーザごとにパーソナライズされたデータを表示させたいときに使えますね。

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

CoreSpotlightとMobileCoreServicesというフレームワークが必要なのでインポート。

CSSearchableItemAttributeSet *attributeSet = [[CSSearchableItemAttributeSet alloc]initWithItemContentType:(NSString *)kUTTypeImage];
attributeSet.title = @"こんにちは";
attributeSet.contentDescription = @"Search APIsのデモです。";
attributeSet.keywords = [NSArray arrayWithObjects:@"Search", @"API", @"Demo", nil];

UIImage *image = [UIImage imageNamed:@"demoIcon"];
NSData *imageData = [NSData dataWithData:UIImagePNGRepresentation(image)];
attributeSet.thumbnailData = imageData;

CSSearchableItem *item = [[CSSearchableItem alloc]initWithUniqueIdentifier:@"com.example" domainIdentifier:@"spotlight.demo" attributeSet:attributeSet];
[[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:@[item] completionHandler: ^(NSError * __nullable error) {
    if (!error)
    NSLog(@"Search item indexed");
}];

f:id:Kamekiti:20150618190850p:plain
このように検索対象に新規アイテムの追加ができました。画像がなぜか表示されない…。→iOS9 beta2でBug fixされました。

[[CSSearchableIndex defaultSearchableIndex] deleteSearchableItemsWithDomainIdentifiers:@[@"spotlight.demo"] completionHandler:^(NSError * __nullable error) {
}];

登録したアイテムは削除することも可能です。domainIdentifierを指定して削除したり、uniqueIdentifierを指定することもできます。
CoreSpotlightは、ユーザの状況に応じて変化するようなアイテムを登録するのがよいかと思われます。たとえば、買い物系のアプリでユーザの閲覧履歴に入っている商品をCoreSpotlightを通して登録するなど。アプリ内で扱っているパーソナライズされた情報を登録しておけば、ユーザにとってもSpotlightが非常に便利な検索ツールになりますね。

Web Markup

ApplebotがWebサイトをクロールして情報をCloud Indexにためるそうです。
f:id:Kamekiti:20150618190926p:plain
その結果、Spotlightから検索したときに、Web上の情報を表示させることが可能だそう。Twitter CardやFacebook App Link、MicrodataJSON-LDに対応しているようです。
f:id:Kamekiti:20150618190936p:plain
Spotlightから直接音楽や動画を再生したり、電話をかけたり、ルート案内をさせることが可能。ネイティブアプリだけでなく、Web側も対応も必要になってきますね。こちらもNSUserActivityと同様にCloud Index内の人気度によって表示順などが決まります。

Universal Link

iOS9端末でWWDC2015のWebサイトで試してみるとわかるのですが、WWDCアプリを入れていると、動画ページのリンクを開くとそのままアプリへ遷移します。
f:id:Kamekiti:20150618190946g:plain
URLSchemeでの起動はアプリが切り替わるアニメーションがありましたが、Universal LinkではシームレスにUINavigationControllerのpushのような感じでアプリへ移ります(iOS9標準のアプリ遷移がこのような動きになりました)。もちろん、アプリが入っていないユーザにはそのままSafariでWebサイトが閲覧できます。
こちらのブログにUniversal Linkに対応しているアプリ一覧がまとめられています。
www.jackivers.me

サーバ側の設定方法

apple-app-site-association」という名前のJSONファイルをドキュメントルートに置きましょう。
https://developer.apple.com/apple-app-site-association
に実際のファイルが置いてあるので中身見ると分かりやすいかと思います。

{
    "applinks": {
        "apps": [],
        "details": {
            "YOUR_APP_ID.com.example": {
                "paths": [
                    "/videos/2015/*",
                    "/news/*"
                ]
            }
        }
    }
}

こんな感じに書きます。Hand Offを使う際にも同様の設定があるので下記のリンクが役立つかも。

WWDCiOS9から署名作業が必要ないと言っていました。

アプリ側の設定

TARGETのCapabilitiesにある「Associated Domains」を有効にします。「applinks:com.example」のように、プロトコルをapplinksにして指定します。
あとはapplication:continueUserActivity:restorationHandler:でアプリに遷移してきた際の処理を記述すれば完了。

Search APIsにおける注意事項

f:id:Kamekiti:20150618192059p:plain

  • 良いアプリとコンテンツを作ること
  • 内容が薄い結果はランクダウン、抑止される
  • 悪意のある実装はペナルティ
  • ユーザにいかに検索→閲覧してもらうかが大事
  • リッチな結果のほうが満足度が高い(サムネイル、内容、評価、アクションなどを使う)
  • 表示される画像は関連あるものを
  • ユーザが探している情報かどうか
  • 特定のアイテムに対するキーワードは3〜5語
  • 省略形?Sanfrancico→SFなどにも対応すること(?)
  • 結果の表示は1ステップで(ローディング画面やログイン画面をはさまないこと)

おわりに

まだあやふやなところが多いのでご指摘ください。