yotiky Tech Blog

とあるエンジニアの備忘録

[iOS]Deeplink(Custom URL Scheme)を使ってアプリからアプリを起動する

アプリから別アプリを起動するケースがあまり多くないのかまとまった情報はそれほど多くありません。
作業の覚書としてここに書き記しておきます。

目次

アプリからアプリを起動する

Deeplink(Custom URL Scheme)を使ってアプリAからアプリBを起動するには、

  1. アプリB(起動される側)にカスタムURLスキームを定義する
  2. アプリA(呼び出し側)に呼び出し処理を実装する
  3. アプリA(呼び出し側)で、定義したカスタムURLスキームを呼び出す宣言をする (iOS9以降)

が必要となります。

起動される側(アプリB)

まずURLスキームを定義します。
Xcodeで開いている場合はプロジェクト直下にある info.plist を開いて、Information Property List に以下の項目を定義します。

URL types
└Item 0
 ├URL Identifier : myHost
 └URL Schemes
  └Item 0 : myScheme

f:id:yotiky:20180521012513p:plain

これで、myScheme://myHostで起動する準備ができました。1 2

また、Unity上では URL Scheme のみ設定できるようになっています。
File > Build Settings > Player Settings > Inspector を開いて、iOSのタブに「Supported URL schemes」の設定があるのでURLスキームを追加します。

f:id:yotiky:20180521012516p:plain

呼び出し側(アプリA)

iOSで実装するには、ネイティブでプラグインを実装してUnity側からプラグインの関数を呼ぶのが簡単です。

プラグインの実装

iOSプラグインは、 Plugins\iOS に h ファイルと mm ファイルを追加するだけ。あとはビルド時に良しなにやってくれます。 実装ファイルだけで完結する場合は ヘッダーファイルは不要です。

f:id:yotiky:20180521012518p:plain

mm は、Objective-CC++のコードが混在したXcodeで使われるファイルです。Objective-C++なんて呼ばれたりもしますが、言語としては存在しないらしい。
extern "C" { } に記述した関数がUnity側から呼び出すインタフェースになります。
プレース内はCのコードで、名前マングリングをしてくれるらしい。(なんのこっちゃ)
おまじないとして覚えておきましょう。詳しく知りたい人は「名前マングリング」や「リンケージ指定」とかで検索すると良いかも。

サンプルは、URLスキームとiTunesAppIdを引数に渡すと、端末にURLスキームで起動できるアプリがある場合はアプリを、なければApp Storeを指定したアプリIDで開きます。
iOS 10 以降はcanOpenURL と openURL の仕様が変わっており引数が増えています。バージョン判定を行った上で処理を別けています。

@interface Launcher : NSObject
+(void)launch:(NSString*) urlStr itunesAppId : (NSString*) itunesAppId;
@end

@implementation Launcher
+(void)launch:(NSString*) urlStr itunesAppId : (NSString*) itunesAppId{
    NSURL *url = [NSURL URLWithString:urlStr];
    NSURL *storeurl = [NSURL URLWithString:[NSString stringWithFormat:@"itms-apps://itunes.apple.com/app/%@", itunesAppId]];
    
    if(floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max){
        // iOS10 以降
        if([[UIApplication sharedApplication] canOpenURL:url]){
            [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
        }else{
            [[UIApplication sharedApplication] openURL:storeurl options:@{} completionHandler:nil];
        }
    }else{
        // iOS9 以前
        if([[UIApplication sharedApplication] canOpenURL:url]){
            [[UIApplication sharedApplication] openURL:url];
        }else{
            [[UIApplication sharedApplication] openURL:storeurl];
        }
    }
}
@end

extern "C"{
    void launch(const char *url, const char *itunesAppId){
        NSString *urlString = [NSString stringWithCString:url encoding:NSUTF8StringEncoding];
        NSString *itunesAppIdString = [NSString stringWithCString:itunesAppId encoding:NSUTF8StringEncoding];
        
        [Launcher launch:urlString itunesAppId:itunesAppIdString];
    }
}

Unityからプラグインを呼び出す

続いてUnityから呼び出す処理を実装します。
配置したプラグインの extern で定義した関数をDLL関数として呼び出してあげましょう。

public class NewBehaviourScript : MonoBehaviour 
{
    public void OnClick1()
    {
        new MyPlugin().Launch("myScheme://myHost", "id0123456789");
    }
}

public class MyPlugin
{
    [DllImport("__Internal")]
    private static extern void launch (string url, string itunesAppId);

    public void Launch(string url, string itunesAppId)
        {
        launch (url, itunesAppId);
    }
}

URLスキームを呼び出す宣言 (iOS9以降)

iOS9以降では、info.plist にそのアプリが起動できるURLスキームを予め宣言しておく必要があります。
この宣言がないと、URLスキームが一致していてもアプリを起動することができません。
info.plist を開いて、Information Property List に LSApplicationQueriesSchemes の項目を定義します。

LSApplicationQueriesSchemes
├Item 0 : myScheme
└Item 1 : 必要な分だけ

f:id:yotiky:20180521012521p:plain

パラメータを渡す場合

Deeplinkで起動する際にパラメータを渡したい場合は、更に対応が必要になります。
今回はURLにパラメータを付けて渡す方法なので、呼び出し側(アプリA)はURLにパラメータを付けて呼び出すだけです。特に実装の必要はありません。起動される側(アプリB)に以下の実装を加えます。

  1. 起動時のUnityAppControllerをフックして値をUnity側へ橋渡しするプラグインを作成する
  2. Unity側からプラグインを呼び出して値を受け取る

プラグインの実装と呼び出し

プラグイン作成のポイントは、

  • UnityAppController を継承したControllerを定義し、openURLを実装する
  • staticな領域に値を退避する
  • IMPL_APP_CONTROLLER_SUBCLASS を使って定義したControllerをサブクラスとして登録
  • Unityから値を取り出せる関数を用意する

です。

前項の通り、作成したヘッダーファイルと実装ファイルは、Plugins/iOSの配下に置きます。

#import <Foundation/Foundation.h>
#import "UnityAppController.h"


// static な変数保持用
@interface IntentParameter : NSObject
+ (NSString*)getUrlString;
+ (void)setUrlString:(NSString*)str;
@end

@implementation IntentParameter
static id urlString = @"";
+(NSString*)getUrlString{
    return urlString;
}
+(void)setUrlString:(NSString*)str{
    urlString = [str copy];
}
@end


// openURLをフックする処理
@interface UrlHandlerAppController : UnityAppController
@end

@implementation UrlHandlerAppController
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
    
    [IntentParameter setUrlString:[url absoluteString]];
    
    return YES;
}
@end

IMPL_APP_CONTROLLER_SUBCLASS(UrlHandlerAppController);


// APIの定義
extern "C"{
    const char* getUrlString();
}

const char* getUrlString(){
    NSString *string = [IntentParameter getUrlString];
    const char *str = [string UTF8String];
    
    char* retStr = (char *)malloc(strlen(str) + 1);
    strcpy(retStr, str);
    return retStr;
}

Unity側から、extern で定義した関数をDLL関数として呼び出せば値が取り出せます。

public class NativeIntentParameterApi
{
    [DllImport("__Internal")]
    private static extern string getUrlString();

    public static string GetUrlString()
    {
    return getUrlString();
    }
}

プラグインでフックしたopenURLは、起動される側が起動していない場合はもちろん、既に起動していても、カスタムURLスキームを使ってアプリに遷移する時に呼び出されます。アプリ単体で起動した場合は値が取れません。


  1. Identifierは一意性を確保する識別子として定義されているが、カスタムURLスキームを使って起動する時はURLスキームのみで判定されておりあまり意味はないようです。(ここが不一致でもURLスキームが一致していれば起動する)

  2. Identifierについては、iOSは一意性を確保する識別子として逆DNS形式が慣習化されているのに対し、Androidは host という名前でDNS形式となるため注意が必要です。