iOSアプリ間連携の実装に x-callback-url を使う

はじめに

モバイルファースト室の @slightair です。 クックパッドが提供しているiOSアプリには、連携して機能するものがあります。

買い物リストアプリを例に挙げると、クックパッドアプリのレシピ画面からレシピに使われている材料を買い物リストアプリに登録することができます。

f:id:Slightair:20141113101333p:plain

この機能は、x-callback-url という仕様に沿って実装しています。 x-callback-url は別のアプリの呼び出しや情報の受け渡しに使うカスタムURLスキームの形式を定義するものです。 この仕様に沿って実装することで、他のアプリから呼び出せる処理や必要なパラメータをきれいにまとめることができます。

この記事では x-callback-url を用いたアプリ間連携の実装について説明します。

カスタムURLスキーム

iOSアプリで他のアプリに遷移しつつなにかしらの情報を渡すにはカスタムURLスキームを使う事になると思います。 遷移先のアプリでは叩かれたURLを受け取れるので、URLをパースして渡ってきた値を解釈したり、次にどのような処理をするか決めることができます。

どのような形式のURLを受け付けるかはアプリそれぞれで定義することになります。 URLで表現できるものであればなんでもよいのですが、ここで好き勝手に形式を決めてしまうと、よほどうまくやらない限りいつか破綻してしまうでしょう。

x-callback-url

クックパッドでは、アプリ間連携を x-callback-url という仕様に沿って実装しています。 この仕様では、パラメータをURLに含める形式を定めています。 x-callback-url では、他のアプリに渡す値だけではなく、他のアプリに委譲した処理の結果を受け取るためのパラメータも決められています。

具体的には以下の様な形式です。

[scheme]://[host]/[action]?[x-callback parameters]&[action parameters]

action に遷移先のアプリに委譲したい処理名を、action parameters には action に必要なパラメータを指定します。 x-callback parameters には、遷移先のアプリで表示するために使う遷移元のアプリ名(x-source)、成功時に遷移元のアプリに戻るためのURL(x-success) などを指定します。 詳しくは x-callback-url の仕様を読んでみてください。

x-callback-url は Google Chrome でも使われているようです。 https://developer.chrome.com/multidevice/ios/links#using-the-x-callback-url-registration-scheme

このページに書かれている仕様に沿ってアプリを実装すると、Webページを開く際にChromeを使い、ユーザがページの閲覧を終えたら元のアプリに戻ってくるような動きをさせることができます。

実装

x-callback-url の仕様に沿ったアプリ間連携を簡単に実装するためのライブラリ InterAppCommunication があります。 クックパッドではこのライブラリをラップし、他のクックパッドのアプリの機能を簡単に呼び出せるようにして社内共通ライブラリに組み込んでいます。

呼び出し側の実装例

InterAppCommunication を使うと、他のアプリの機能を呼び出すときには以下のように書けます。

    IACClient *client = [IACClient clientWithURLScheme:@"url-scheme"];

    [client performAction:@"action"
               parameters:@{
                             ...
                            }
                onSuccess:^(NSDictionary *params) {
                    ...
                }
                onFailure:^(NSError *error){
                    ...
                }
     ];

受け側の実装例

application:didFinishLaunchingWithOptions: でcallbackURLSchemeとアクションに対応する処理を書くデリゲートをセットします。

    [IACManager sharedManager].callbackURLScheme = @"url-scheme";
    [IACManager sharedManager].delegate = <IACDelegateに準拠したクラスのインスタンス>;

application:openURL:sourceApplication:annotation: で IACManager のインスタンスメソッド handleOpenURL: を使い、呼び出された url をわたします。

    if ([[url scheme] isEqualToString:@"url-scheme"]) {
        return [[IACManager sharedManager] handleOpenURL:url];
    }

上記2つのコードは、呼び出し側で処理が終わった後に元のアプリに戻ってくる場合にも必要です。

IACManager の delegate に設定するインスタンスのクラスで supportsIACAction:performIACAction:parameters:onSuccess:onFailure: を定義します。

- (BOOL)supportsIACAction:(NSString *)action
{
    NSArray *supportedActions = @[@"action1", @"action2", ...];
    return [supportedActions containsObject:action];
}

- (void)performIACAction:(NSString *)action
              parameters:(NSDictionary *)parameters
               onSuccess:(IACSuccessBlock)success
               onFailure:(IACFailureBlock)failure
{
    if ([action isEqualToString:@"action1"]){
        // action1 の処理
        // 結果に応じて success/failure block を呼ぶ
    }
    ...
}

ライブラリが x-callback-url の仕様に沿ったURLを解析して、パラメータやsuccess/failureブロックに展開してくれます。

おわりに

クックパッドで公開しているアプリのアプリ間連携の実装の話をしました。 別のアプリに遷移して、再びアプリに戻るような動きがなかったとしても、x-callback-url のような仕様に合わせたほうがきれいに実装できます。 自前でURLクエリをパースする処理を書いたり、独自の形式を考えるより楽だと思います。

スムーズなアプリ間連携を実装して、ユーザーに気持ちのよい体験を届けられるとうれしいですね。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/