Code for History

"Code for History"はIT技術を歴史学上の問題の解決に使うコミュニティです。強調したいのは、我々にとってIT技術は「手段」であって「目的」ではありません。「目的」は歴史学上の問題を解決する事であって、必要であればITでない手段も活用します。常に最優先なのは、問題を解決することです。

iOSで複数アプリ間のちょっとおもしろい連携

iOSアプリで他のアプリと連携するには、URLスキームを利用する。

[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"http://www.example.com/"]];

とすれば、httpスキームに対応したSafariが呼び出されるし、

[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"hogehoge://fuga"]];

とすれば、hogehogeスキームに対応させたアプリに情報fugaを引き渡せる。

ただしこれ、hogehogeスキームに対応しているアプリがインストールされていなければ動作しない。
スキームに対応したアプリがインストールされていれば連携させるが、インストールされていなければ他の動作をさせたいような場合があると思う。
そのような時どうするか考えてみたのだが、スキームに対応したアプリが存在する⇒対応したアプリが起動⇒呼び出し元はバックグラウンドに移行する、というのを利用すれば、以下のような方法が使えそう。

//  CBURLOpener.h

#import <Foundation/Foundation.h>

@interface CBURLOpener : NSObject
{
    BOOL   goneBackGround;
}

+(CBURLOpener*)defaultOpener;
-(void)openURL:(NSURL*)aURL otherwise:(NSURL*)bURL interval:(NSTimeInterval)aInterval;
-(void)openOtherwiseURL:(NSURL*)bURL;
-(void)setGoneBackGround;

@end
//  CBURLOpener.m

#import <UIKit/UIKit.h>
#import "CBURLOpener.h"

static CBURLOpener* s_self = nil;

@implementation CBURLOpener

+(CBURLOpener*)defaultOpener
{
    if (!s_self) {
        s_self = [[CBURLOpener alloc] init];
    }
    
    return s_self;
}

-(void)openURL:(NSURL*)aURL otherwise:(NSURL*)bURL interval:(NSTimeInterval)aInterval
{
    //aURLを呼び出し、aInterval後に呼び出されていなければbURLを呼び出す
    goneBackGround = NO;
    [[UIApplication sharedApplication] openURL:aURL];
    if (bURL) {
        [self performSelector:@selector(openOtherwiseURL:) withObject:[bURL retain] afterDelay:aInterval];
    }
}

-(void)openOtherwiseURL:(NSURL*)bURL
{
    if (!goneBackGround) {
        [[UIApplication sharedApplication] openURL:bURL];
    }
    [bURL release];
}

-(void)setGoneBackGround
{
    goneBackGround = YES;
}

@end
//  アプリのMyAppDelegate.m

#import "MyAppDelegate.h"
#import "CBURLOpener.h"

@implementation MyAppDelegate

  ......

-(void)applicationDidEnterBackground:(UIApplication *)application
{
    //バックグラウンド処理に移行した事を通知
    [[CBURLOpener defaultOpener] setGoneBackGround];
}

  ......

@end

上記サンプルでは、aURLを呼び出して、一定時間バックグラウンドに移行しなければ、aURLの呼び出しに失敗したものと判定してbURLを呼び出す、という処理をしている。
が、別に代替URLの呼び出しでなくとも、「インストールされていないようです」といったメッセージボックスの表示でも、何でも可能だと思う。

利用シーンとしては、連携可能な自社アプリとURLスキーム連携⇒インストールされていなければiTunesに飛ばす、みたいな用途が考えられる。
普通の意味での連携だけでなく、「こちらのアプリも購入すれば、InAppPurchase要素無料サービス!」みたいなキャンペーンにも使えそう。

URLスキームを使った連携の応用としては、あらかじめコード内に仕込まれた連携だけでなく、動的に連携を追加する事もできそうだ。
例えばGPSログ機能がついたアプリは、専業のものから地図系/ナビ系アプリのおまけ機能としてついているものまでたくさんある。
けど、複数のアプリで同じログ機能を動作させるのって、きわめて無駄。
でも今は連携の方法がないから、A地図の上でこれまでの移動ログがみたい、同時にBナビの上でもこれまでの移動ログがみたいと思えば、両方でGPSログ機能を動かすしかない*1

が、「各アプリが持つGPSログ受け取り用URLスキーム」の「登録を受け付ける」URLスキームを持ったGPSログアプリがあれば、他のアプリの連携用スキームを受け取って、「GPSログを○×アプリに送る」ボタンを動的生成するような、そんな機能が実現できる。
さすがにリアルタイムにバックグラウンドから次々新たな測位点が送られてくるような連携は無理だけど、ここまでのGPSログを送る機能は十分可能だ。
もちろん各アプリ側では、もしスキームが無反応であれば、GPSログアプリのダウンロード用iTunesに飛ばしてもらう形にしてもらって。

少し前に、iOSAndroidインテントに似た機能を実現するオープンソースライブラリをどこかで見かけたのだが*2、似たような原理を利用しているのかも。

*1:或いは、サーバを介するか、完全事後にGPXファイル等で連携するか。

*2:再発見できなかったので載せていないが、見つかれば後々追記

© Code for History