Xamarin.FormsのWebアプリでJavascriptからC#を呼び出す方法

前回の『Xamarin.FormsでオフラインWEBアプリを作成する方法』ではWebアプリの基本的な作成まででしたので、それを拡張する形でC#側のコードをWeb側(Javascript)から実行できるようにします。
プロジェクト作成までの手順はほぼ同じですので省略します。前回を確認してください。

事前準備

今回は「NativeCallWebApp」という名前でプロジェクトを作成しています。
※プロジェクト名は好きに変更して、読み替えてください。
newproject
前回のプロジェクトのコード「BaseUrl.cs」をNamespaceに気を付けて修正しながら、iOS側とAndroid側に作成またはコピーします。

共通処理[NativeCallWebApp>NativeCallWebApp.cs]

	public interface IBaseUrl { string Get(); }

Android側[NativeCallWebApp.Droid>BaseUrl.cs]

[assembly: Dependency(typeof(BaseUrl))]
namespace NativeCallWebApp.Droid
{
    public class BaseUrl : IBaseUrl
    {
        public string Get()
        {
            return "file:///android_asset/webdir/";
        }
    }
}

iOS側[NativeCallWebApp.iOS>BaseUrl.cs]

[assembly: Dependency(typeof(BaseUrl))]
namespace NativeCallWebApp.iOS
{
    public class BaseUrl : IBaseUrl
    {
        public string Get()
        {
            return NSBundle.MainBundle.BundlePath + "/webdir";
        }
    }
}

確認用HTMLコンテンツ作成

実行確認用に2ページのHTMLページを作成します。
page-a.html

<html>
	<head>
		<title>page A</title>
	</head>
	<body style="background-color:#ff0;">


<h2>page A</h2>


		<a href="page-b.html">Link page B</a>
		<a href="javascript:window.location.href='page-b.html';">location.href page B</a>
		<a href="javascript:window.open('page-b.html','_blank');">window.open page B</a>
		
		<a href="NativeCallWebApp://link/">Link appC#</a>
		<a href="NativeCallWebApp://link_blank/" target="_blank">Link targetblank appC#</a>
		<a href="javascript:window.location.href='NativeCallWebApp://location.href/';">location.href appC#</a>
	</body>
</html>

page-b.html

<html>
	<head>
		<title>page B</title>
	</head>
	<body style="background-color:#0ff;">


<h2>page B</h2>


		<a href="page-a.html">Link page A</a>
	</body>
</html>

Androidアプリ側は作成したHTMLを「NativeCallWebApp.Droid」内の「Assets」フォルダ内に「webdir」フォルダを作り、その中に入れます。
入れたHTMLファイルのビルドアクションを「None」から「AndroidAsset」に変更するのを忘れないでください。
iOSアプリ側は「NativeCallWebApp.iOS」内の「Resources」フォルダ内に「webdir」フォルダを作り、同じものを追加します。
こちらもビルドアクションが「BundleResource」になっていることを確認してください。

カスタムWebViewのコード追加

Android側ではカスタムレンダラーを利用しますので、Androidのプラットフォームに空のクラスで「CustomWebView.cs」を作成して、次のように修正します。今回はWebViewRenderer側だけではなく、WebViewClient側でも処理をする必要があります。

Android側[NativeCallWebApp.Droid>CustomWebView.cs]

using System;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using NativeCallWebApp;
using NativeCallWebApp.Droid;

[assembly: ExportRenderer(typeof(CustomWebView), typeof(CustomWebViewRenderer))]
namespace NativeCallWebApp.Droid
{
	public class CustomWebViewRenderer : WebViewRenderer
	{
		protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
		{
			base.OnElementChanged(e);
			if (Control == null) return;

			Control.Settings.DomStorageEnabled = true;
			Control.Settings.JavaScriptEnabled = true;

			//javascriptのfileスキームへのアクセス許可
			Control.Settings.AllowFileAccessFromFileURLs = true;
			Control.Settings.AllowUniversalAccessFromFileURLs = true;

			//Xamarin.Formのコントロール(CustomWebView)
			CustomWebView _formsWebView = (CustomWebView)e.NewElement;
			//ネイティブコントロール(Android.Webkit.WebView)
			Android.Webkit.WebView _droidWebView = this.Control;

			//Xamarin.FormのWebViewではNavigateのイベントを拾えないためWebViewClientを上書きする
			_droidWebView.SetWebViewClient(new CustomWebViewClient(_formsWebView));
		}
	}

	public class CustomWebViewClient : Android.Webkit.WebViewClient
	{
		private readonly CustomWebView _formsWebView;

		public CustomWebViewClient(CustomWebView formsWebView)
		{
			_formsWebView = formsWebView;
		}

		public override bool ShouldOverrideUrlLoading(Android.Webkit.WebView view, string url)
		{
			if (url != null) { 
				Console.WriteLine(url);

				//イベントをURLで判断してネイティブ処理実行
				if (url.Contains("nativecallwebapp://")) {
					
					Application.Current.MainPage.DisplayAlert("open url schemes", url, "ok");

					//Web処理のキャンセル
					view.StopLoading();
				}

			}
			return false;
		}
	}
}

Custom Url Schemeの設定

iOS側は、カスタムWebスキームを利用します。
基本的に設定だけですので、簡単です。
「info.plist」をクリックすると、設定画面が表示されます。
infoplist1
下のタブを「Advanced」タブに切り替えます。
infoplist2
「URL Types」の項目がありますので、その中の「URL Schemes」に小文字で「nativecallwebapp」と入力して、保存します。
infoplist3

次に、「AppDelegate.cs」を修正します。
AppDelegateクラスの中にOpenUrlイベントを追加します。

iOS側[NativeCallWebApp>AppDelegate.cs(修正)]

		public override bool OpenUrl(UIApplication application, NSUrl url, string sourceApplication, NSObject annotation)
		{
			//イベントをURLで判断して処理
			if (url != null)
			{
				//イベントをURLで判断してネイティブ処理実行
				if (url.AbsoluteString.Contains("nativecallwebapp://"))
				{
				global::Xamarin.Forms.Application.Current.MainPage.DisplayAlert("open url schemes", url.AbsoluteString, "ok");
				}
			}
			return false;
		}

WebViewの開始ページを設定するコード追加

前回と同様に処理しますが、わかりやすくなるように少しイベントの呼び出し方を変更しています。
また、最初のHTML呼び出しのページも「page-a.html」に変更します。

共通部分[NativeCallWebApp>NativeCallWebApp.cs(修正)]

namespace NativeCallWebApp
{
	public class CustomWebView : WebView { }
	public interface IBaseUrl { string Get(); }

	public class App : Application
	{
		public App()
		{
			CustomWebView FormsWebView = new CustomWebView { Source = GetHTML() };

			// The root page of your application
			var content = new ContentPage
			{
				Title = "NativeCallWebApp",
				Content = FormsWebView
			};
			MainPage = content;
		}

		private HtmlWebViewSource GetHTML()
		{
			HtmlWebViewSource html = new HtmlWebViewSource
			{
				Html = @"<!DOCTYPE html>
						 <meta http-equiv=""refresh"" content=""0;URL=./page-A.html""/>
						 </html>",
				BaseUrl = DependencyService.Get<IBaseUrl>().Get()
			};
			return html;
		}
	}
}

動作確認

これで完了しましたので、スタートアッププロジェクトを各デバイスに変更して動作を確認します。

Android側
droidtest

iOS側
iostest

これで、うまくパラメーターを判断させてC#の処理を実行することが可能です。
サンプルでは個々の端末で処理していますが、共通部分側に分岐処理を追加して呼び出してもいいでしょう。

サンプルコードはこちら(GitHub)

対応方法の中身について

iOS側は自分自身のアプリをCustum Web Schemeで呼び出すことで、ネイティブ側のOpenUrlイベントからURLを使ったパラメーターの内容が取得できます。
Android側で同様の処理を行おうとしたところ、アクティブ状態ではCustum Web Schemeでイベントが上がらないことが判明。急きょWebViewのイベントで処理を探しましたが、WebClientにしか該当イベントのShouldOverrideUrlLoadingがないため、このような形での処理方法での実装となっています。
一応Custum Web Schemeの形式で、どちらの端末にもパラメーターを渡すことが出来ますので、中身の処理形態は異なりますが、ひとまずはこれで十分でしょう。

また、Xamarin.Forms.WebViewとAndroid.webkit.WebViewが同じ名前でありますので、どちらのWebViewなのかわからなくなる時があって大変です。修正する場合は注意してください。(中身のイベントが全く異なります。というか、Xamarin.FormsのWebFormが貧弱すぎる!)

課題

本当はHybridWebViewをうまく使えば簡単だったのかもしれませんが、こちらはいまのところ完成しておらず、一応試した感じではJsonなどの依存パッケージも増えるしXamarin.Forms的にはいまいちだったので、シンプルに内部実装にしました。

参考サイト

Xamarin.Forms Facebookアプリの作成 (軽量 Facebook.SDK for Xamarin.Forms )
【Xamarin .iOS】【C#】自アプリの URL Scheme で起動して引数を受け取る

Search index:
[Xamarin.Forms] Javascript Call C# function
[Xamarin.Forms] Calling C# Method from Javascript

この記事をシェアする
Tweet about this on Twitter
Twitter
Share on Facebook
Facebook
0

Comments

Comments