iOS10 bata 向けに改めてwebviewとnativeアプリ連携手法を考えてみた。

iOS10 bata版向けにアプリをビルドすると今まで動いていたiframeでカスタムURLスキームを叩く部分が反応しなくなったので、
修正対応していったらこのような実装になったのでメモ

※iframeでURLスキームを叩く例の参考サイト
>このブログの最後あたりにiframeを使用してカスタムURLスキームを叩いている例があります。
>>iPhoneのネイティブ機能をWebViewから呼び出す方法（１）
>>http://blog.engineer.adways.net/archives/10172788.html


※Xcode新バージョンでとかの何か条件があるようですが、iOS10 betaの初期の段階からiframeからURLスキームを叩いても反応しなかったので今後は使えなくなっていく可能性が高いです。　
※僕はiOSアプリ作れないのでサンプル実装例は実機でテストしていません。
　もしテストしてくださった方がいればコメントなどしていただけるととても助かります。

## 経過報告メモ 
### (2016/09/12 追記)
　iOS10 GM版でも上記と同様の挙動でした。

### (2016/11/17 追記)
　Xcode6.2 ビルドでも同様の挙動でした。
　なおiOS10との比較の為、同ビルドのアプリをインストールした旧OS(iOS8)は
　逆に修正前と同様の方式※でないと動かないようでした。
　(※iframeでURLスキームを叩く方式)
　OSごとにnativeAdaptor._callnativeMethod()関数内でURLの叩き分け処理が必要かもしれません。

### (2017/01/10 追記)
上記に対応できるようにするため、iOSバージョン分岐処理を追加しました。

# サンプル実装例 その1

単にアプリIDを表示するシンプルな画面です。
[デモ@サンプルアプリ #1](http://tachimachi.hmkt.me/170110_hybrid_app_sample_01/appinfo.html)

## 実行例
![実行例](https://qiita-image-store.s3.amazonaws.com/0/66209/82de83da-3fb2-0485-fff6-4742273aa587.png)


### このサンプル実装例の前提条件
- 下記のシーケンス図通りに挙動をするNATIVEアプリで閲覧していることとします。
- NATIVEアプリから返却されるアプリIDの値は仮に『1234』とします。

### シーケンス図
![シーケンス図_1.png](https://qiita-image-store.s3.amazonaws.com/0/66209/ec5cd18c-741a-b7f9-490b-15b125c9e147.png)


### NATIVEアプリの処理
***
#### アプリID取得
次のURLがリクエストされたら
``` nateve-app://getAppId({}) ```

NATIVEアプリは以下のjavascriptを実行
``` webviewAdaptor.getAppId('1234');```

***

### HTML
```appinfo.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>サンプルアプリ #1</title>
<script type="text/javascript" src="js/nativeAdaptor.js"></script>
<script type="text/javascript" src="js/jquery-1.11.3.min.js"></script>
<script type="text/javascript" src="js/page/appinfo.js"></script>

</head>
<body>
<h2>サンプルアプリ #1</h2>

<p>アプリID：　<span id="app_id"></span>　</p>

</body>
```

### 画面処理ロジック
DOMContentLoadedイベント(HTMLドキュメントの読み込みと解析が完了)まで
カスタムURLスキームを受け付けないのでjQueryのreadyイベントで処理を実行しています。

```appinfo.js
$(function(){

	//NATIVEアプリからアプリID取得した際に実行される処理を記述
	webviewAdaptor.callback.onGetAppId = function(appId){
		$('#app_id').text(appId);
	};
	
	//NATIVEアプリにアプリIDを要求
	nativeAdaptor.getAppId();

});
```

 
### 共通クラス
ネイティブアプリ連携用の処理は共通クラスとして一括管理しています。

```nativeAdaptor.js
/**
 * WEBアプリからNATIVEアプリの機能を叩くクラス
 */
var nativeAdaptor = (function(){

	var isOldFuncCall = false;
	if(navigator.userAgent.indexOf('iPhone OS') != -1){
		var iosStr = (navigator.userAgent).match(/OS (\d+)_(\d+)?/);
		var varsion = parseInt(iosStr[1]);
		if(varsion <= 9){
			isOldFuncCall = true;
		}
	}

	function _callnativeMethod(method){
		var requestUrl = 'native-app://' + method;
		if(isOldFuncCall){
			var iframe = document.createElement('IFRAME');
			iframe.setAttribute('src', requestUrl);
			document.documentElement.appendChild(iframe);
			iframe.parentNode.removeChild(iframe);
			iframe = null;
		} else {
			window.location.href = requestUrl;
		}
	}

	return {

		/**
		 * アプリユーザーID取得 要求
		 */
		getAppId:function(){

			_callnativeMethod('getAppId({})');

			console.log('W → N','nativeAdaptor.getAppId');

		}

	}

})();

/**
 * NATIVEアプリからのコールバッククラス
 */
var webviewAdaptor = (function(){
	return {

		callback:{
			 onGetAppId:function(appId){}
		},

		/**
		 * アプリユーザーID取得 コールバック
		 * @param {string} appId アプリID
		 */
		getAppId:function(appId){
			this.callback.onGetAppId(appId);

			console.log('N → W','webviewAdaptor.getAppId('+appId+')');

		}

	}
})();
```


# サンプル実装例 その2

前述の『サンプル実装例 その1』 に　バージョン表示機能を追加したパターンです。
[デモ@サンプルアプリ #2](http://tachimachi.hmkt.me/170110_hybrid_app_sample_02/appinfo.html)

## 実行例
![実行例](https://qiita-image-store.s3.amazonaws.com/0/66209/a5031fd2-76a6-5c7b-a022-718e5c9fdfe2.png)


### このサンプル実装例の前提条件
- サンプル実装例1 で動作したNATIVEアプリにバージョン表示機能を追加したものとします。
- NATIVEアプリから返却されるバージョンの値は仮に『0.0.7』とします。

### NATIVEアプリの処理
***
#### アプリID取得
次のURLがリクエストされたら
``` nateve-app://getAppId({}) ```

NATIVEアプリは以下のjavascriptを実行
``` webviewAdaptor.getAppId('1234');```

***

#### アプリバージョン取得
次のURLがリクエストされたら
``` nateve-app://getAppVersion({}) ```

NATIVEアプリは以下のjavascriptを実行
``` webviewAdaptor.getAppVersion('0.0.7'); ```
***


### HTML
```appinfo.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>サンプルアプリ #2</title>
<script type="text/javascript" src="js/nativeAdaptor.js"></script>
<script type="text/javascript" src="js/jquery-1.11.3.min.js"></script>
<script type="text/javascript" src="js/page/appinfo.js"></script>

</head>
<body>
<h2>サンプルアプリ #2</h2>

<p>アプリID　 ：　<span id="app_id"></span>　</p>
<p>バージョン：　<span id="app_version"></span>　</p>

</body>
```

### 画面処理ロジック
複数の連携処理を即時実行すると1つの処理にしか反応しなくなる事があるので
その場合は実行タイミングに間隔(最低でも25ms以上)を入れて対応しています。

```appinfo.js
$(function(){

	//NATIVEアプリからアプリID取得した際に実行される処理を記述
	webviewAdaptor.callback.onGetAppId = function(appId){
		$('#app_id').text(appId);
	};

	//NATIVEアプリからアプリバージョン取得した際に実行される処理を記述
	webviewAdaptor.callback.onGetAppVersion = function(appVersion){
		$('#app_version').text(appVersion);
	};

	//NATIVEアプリにアプリIDを要求
	nativeAdaptor.getAppId();

	//即時実行するとネイティブアプリが反応できないようなので、少し間をあけて実行
	setTimeout(function(){

		//NATIVEアプリにアプリバージョンを要求
		nativeAdaptor.getAppVersion();

	}, 50);


});
```

 
### 共通クラス
```nativeAdaptor.js
/**
 * WEBアプリからNATIVEアプリの機能を叩くクラス
 */
var nativeAdaptor = (function(){

	var isOldFuncCall = false;
	if(navigator.userAgent.indexOf('iPhone OS') != -1){
		var iosStr = (navigator.userAgent).match(/OS (\d+)_(\d+)?/);
		var varsion = parseInt(iosStr[1]);
		if(varsion <= 9){
			isOldFuncCall = true;
		}
	}

	function _callnativeMethod(method){
		var requestUrl = 'native-app://' + method;
		if(isOldFuncCall){
			var iframe = document.createElement('IFRAME');
			iframe.setAttribute('src', requestUrl);
			document.documentElement.appendChild(iframe);
			iframe.parentNode.removeChild(iframe);
			iframe = null;
		} else {
			window.location.href = requestUrl;
		}
	}

	return {
		
		/**
		 * アプリユーザーID取得 要求
		 */
		getAppId:function(){
			_callnativeMethod('getAppId({})');
			console.log('W → N','nativeAdaptor.getAppId()');
		},

		/**
		 * アプリバージョン取得 要求
		 */
		getAppVersion:function(){
			_callnativeMethod('getAppVersion({})');
			console.log('W → N','nativeAdaptor.getAppVersion()');
		}
	}

})();

/**
 * NATIVEアプリからのコールバッククラス
 */
var webviewAdaptor = (function(){
	return {
		
		callback:{
			 onGetAppId:function(appId){}
			,onGetAppVersion:function(appVersion){}
		},

		/**
		 * アプリユーザーID取得 コールバック
		 * @param {string} appId アプリID
		 */
		getAppId:function(appId){
			this.callback.onGetAppId(appId);
			console.log('N → W','webviewAdaptor.getAppId('+appId+')');
		},

		/**
		 * アプリバージョン取得 コールバック
		 * @param {string} appVersion アプリバージョン
		 */
		getAppVersion:function(appVersion){
			this.callback.onGetAppVersion(appVersion);
			console.log('N → W','webviewAdaptor.getAppVersion('+appVersion+')');
		}

	}
})();
```