UnityからSFSafariViewController/Chrome Custom Tabsを開く

#■概要
iOS/Androidアプリ開発においてUIWebViewが事実上非推奨になり、iPhoneの場合は「[**SFSafariViewController**](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller)」、Androidの場合は「[**Chrome Custom Tabs**](https://developer.chrome.com/multidevice/android/customtabs)」の利用が推奨されています。しかし僕が調べた範囲では、それらをUnityから利用する方法がまとまった情報として存在していなかったため、以下にまとめました。

SFSafariViewController自体の情報は、iOS9からの機能ということもありとても充実していますが、サンプルコードのほとんどがSwiftで書かれており、UnityのC#からはSwiftコードを直接呼び出せないためそのままでは参考にならなかったり、Chrome Custom Tabsもそれ自体の情報は沢山あるのですが、利用するためにはUnityのレガシーなビルドシステムからはアクセスできないbuild.gradleへの追記が必要だったりと、なかなか一筋縄では行かないのが現状です。

ただし実際にそれらを呼び出すために必要な操作はそんなに複雑ではないので、下記を参考にサクッと実装してみましょう。
#■UnityからSFSafariViewControllerを呼び出す

##1. SFSafariViewControllerを呼び出す関数をObjective-Cで記述する
"**SafariView.mm**"と名前をつけたテキストファイルを"**Assets/Plugins/iOS/**"直下に作成し、次のように記述します。

```objc:SafariView.mm
#import <SafariServices/SafariServices.h>

extern UIViewController* UnityGetGLViewController();

extern "C"
{
	void launchUrl(const char *url)
	{
		// Unityが今表示しているViewControllerのインスタンスを取得
		UIViewController *uvc = UnityGetGLViewController();

		// C#側から渡されたC文字列を元に、NSURLオブジェクトを生成
		NSURL *URL = [NSURL URLWithString:[[NSString alloc] initWithUTF8String:url]];

		// 生成したURLから、SFSafariViewControllerオブジェクトを生成
		SFSafariViewController *sfvc = [[SFSafariViewController alloc] initWithURL:URL];

		// 生成したSFSafariViewControllerオブジェクトを起動
		[uvc presentViewController:sfvc animated:YES completion:nil];
	}
}
```
１行目はSFSafariViewControllerを利用するために必要なヘッダのimportなので、特に疑問はないと思います。

３行目の"**UnityGetGLViewController()**"という関数は、Unityが標準で用意してObjective-C側に公開してくれているものなので、externしておけばビルド時にリンクされます。

７行目の「**launchUrl**」という関数名は好きに変えてかまいません。むしろグローバルに定義されてしまうので、頭にアプリ名を付けるなどしてユニークな名前にしておいた方がいいでしょう（例：MyAppLaunchUrl）。

あとはコメントのとおりです。
##2. 必要なフレームワークをビルドに含める設定をする
上記で作成した"**SafariView.mm**"をUnityエディタ上で選択すると、Inspectorに下図のような「追加でビルドに含めるフレームワーク」を選択するためのチェックボックスが表示されます。

![Clipboard01.png](https://qiita-image-store.s3.amazonaws.com/0/14502/ad13598c-c315-f6c3-7c90-1b3594ba36b8.png)

この中の「**Rarely used frameworks**」というサブカテゴリの中にある「**SafariServices**」にチェックを入れて下さい。

これでネイティブ側の準備は完了です。
##3. Objective-Cの関数をC#から呼び出す
"**SafariView.cs**"と名前をつけたテキストファイルを"**Assets**"フォルダ以下の適当なところに作成し、次のように記述します。

```csharp:SafariView.cs
using System.Runtime.InteropServices;
using UnityEngine;

public static class SafariView
{
#if UNITY_IOS
	[DllImport ("__Internal")]
	extern static void launchUrl(string url);
#endif

	public static void LaunchURL(string url)
	{
#if UNITY_EDITOR
		Application.OpenURL(url);
#elif UNITY_IOS
		launchUrl(url);
#endif
	}
}
```

DllImportを使ったネイティブコードの呼び出しは、[公式マニュアルにも記載されている](https://docs.unity3d.com/ja/current/Manual/NativePlugins.html)ので特に説明は必要ないと思います。ただしネイティブ側の関数名を「launchUrl」以外にしたのなら、C#側もそれに合わせるのを忘れないでください。

また、エディタで実行している時はSafariViewを呼び出すことはできないので、Unity組み込みの関数でブラウザを起動するようにしています。

ここまでできたら、例えばボタンを押した時の処理などに

```csharp
SafariView.LaunchURL(@"https://www.google.co.jp");
```

のように記述すれば、SFSafariViewControllerを利用してウェブページを開くことができます。

#■UnityからChrome Custom Tabsを呼び出す
##1. Google公式のPlayServicesResolverを導入する
概要でも触れた通り、Chrome Custom Tabsの利用には、本来build.gradleへの追記が必要です。しかしUnityの標準的なAndroid向けビルドでは、build.gradleに何かを追記するような処理はできません。そのため事前に必要なライブラリを抽出して、"**Assets/Plugins/Android/**"以下に配置しておく必要があります。

この操作をUnity上で簡単に行うためのエディタ拡張が、Googleから公式に配布されている"**PlayServicesResolver (unity-jar-resolver)**"です。

https://github.com/googlesamples/unity-jar-resolver

上記のGitHubリポジトリから"**play-services-resolver-x.x.x.unitypackage**"(x.x.xの部分はバージョン番号)というUnityパッケージを取ってきて、プロジェクトにインポートしてください。パッケージをインポートすると、"**Assets/PlayServicesResolver/Editor/**"以下にいくつかDLLが配置されます。

![Clipboard03.png](https://qiita-image-store.s3.amazonaws.com/0/14502/c3f9cbbb-f8dc-a5ff-3f0e-1eab89ecf3c8.png)
##2. Chrome Custom Tabsの依存ライブラリを記述する
インポートしたdllと同じフォルダに"**ResolveDependencies.cs**"というテキストファイルを作り、以下のように記述します（ちなみにファイル名は適当でかまいませんし、保存する場所もdllと同じである必要はありません）。

```csharp:ResolveDependencies.cs
using UnityEditor;
using Google.JarResolver;

[InitializeOnLoad]
public static class ResolveDependencies
{
	static ResolveDependencies()
	{
		var svcSupport = PlayServicesSupport.CreateInstance(
			"GooglePlayGames", EditorPrefs.GetString("AndroidSdkRoot"), "ProjectSettings"
		);
		svcSupport.DependOn("com.android.support", "customtabs", "23.0.0");
	}
}
```

上記スクリプトで重要なのは最後の行です。例えばネイティブ向けの説明でbuild.gradleに

```
dependencies {
    ...
    compile "com.android.support:customtabs:23.0.0"
}
```

と記述せよとあった場合、「:」で区切られている部分を分解して、**svcSupport.DependOn**関数に引数として渡してやります。
##3. 依存ライブラリを配置する
PlayServicesResolverのインポートに成功していると、**UnityのAssetsメニュー**に「**Play Services Resolver**」という項目が追加されています。

![Clipboard02.png](https://qiita-image-store.s3.amazonaws.com/0/14502/22b3ec96-1b9c-0941-a8b0-d8f31a02bd70.png)

そこから「**Android Resolver** > **Resolve Client Jars**」を選択すると、依存関係のあるライブラリが抽出され、"**Assets/Plugins/Android/**"以下に配置されます（この時、ビルド設定のビルド先プラットフォームをAndroidに設定しておくことを忘れないでください）。

今回の場合は以下の２つのライブラリが配置されていればOKです。

![Clipboard04.png](https://qiita-image-store.s3.amazonaws.com/0/14502/6713793c-756c-1022-d6e6-06ec7fce6a0e.png)
##4. CustomTabsIntentをC#から呼び出す
あとは必要な機能をUnity/C#側から呼び出すだけなので、"**CustomTabs.cs**"と名前をつけたテキストファイルを"**Assets**"フォルダ以下の適当なところに作成し、次のように記述します。

```csharp:CustomTabs.cs
using UnityEngine;

public static class CustomTabs
{
	public static void LaunchURL(string url)
	{
#if UNITY_EDITOR
			Application.OpenURL(url);
#elif UNITY_ANDROID
			using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
			using (var activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity"))
			using (var intentBuilder = new AndroidJavaObject("android.support.customtabs.CustomTabsIntent$Builder"))
			using (var intent = intentBuilder.Call<AndroidJavaObject>("build"))
			using (var uriClass = new AndroidJavaClass("android.net.Uri"))
			using (var uri = uriClass.CallStatic<AndroidJavaObject>("parse", url))
				intent.Call("launchUrl", activity, uri);
#endif
	}
}
```

Androidの場合は特殊なことをしない限りはC#から直接Javaのオブジェクトを操作できます。取得したオブジェクトの解放忘れがないように、適宜usingステートメントを使いましょう。また上記ではスペースの関係上ブロック指定の中括弧を省いていますが、必ずカッコで括るようにしましょう。

もう１つ注意点として、Javaのインナークラスは「.」ではなく「\$」で区切って呼び出すことを忘れないでください。このスクリプトの例だと"*android.support.customtabs.CustomTabsIntent\$Builder*"としている部分です。スクリプトを改造しようとして、例えば[Uriを直接parse](https://developer.android.com/reference/android/net/Uri.html#parse(java.lang.String))するのではなく、[Uri.Builder](https://developer.android.com/reference/android/net/Uri.Builder.html)を使って細かく設定しようとした時、間違って

```csharp
using (var uriBuilder = new AndroidJavaObject("android.net.Uri.Builder"))
```

と書いてしまうと**ClassNotFoundException**が飛んできます。正しくは

```csharp
using (var uriBuilder = new AndroidJavaObject("android.net.Uri$Builder"))
```

です（僕はこれを忘れてて小一時間ハマりました…）。

尚このスクリプトでは、エディタで実行している時はCustomTabsを呼び出すことはできないので、Unity組み込みの関数でブラウザを起動するようにしています。

ここまでできたら、例えばボタンを押した時の処理などに

```csharp
CustomTabs.LaunchURL(@"https://www.google.co.jp");
```

のように記述すれば、ChromeCustomTabsを利用してウェブページを開くことができます。
#謝辞
最後になりましたが、このページを書くにあたって参考にさせて頂いた偉大な先駆者様達の記事へリンクを張り、感謝の気持ちとさせていただきます。ありがとうございました。

**SFSafariViewController using Objective-C**
https://stackoverflow.com/questions/33593670/sfsafariviewcontroller-using-objective-c

**UnityのiOSプラグインを作成するときに毎回ぐぐってることをメモ((φ(･д･｡)**
http://qiita.com/yyama2/items/0be98070c29d144b6927

**UnityでGooglePlayServicesを入れるにはPlayServicesResolver(unity-jar-resolver)が便利**
http://qiita.com/tkyaji/items/b838c97228f99f194bcd