Xamarin.FormsにはWebViewがある。
だからHybridページやリッチテキストなビューを作りたい。
しかし現状のXamarin.FormsのWebViewだと 非常に非力
ページの表示ができる程度です。
今回、iOSのカスタムURIのクリックをアプリ側でフックしようとしましたが
当然のごとくできませんでした。
ではどうするのか?
→Xamarin.Formsを拡張する。
Xamarin.Formsの拡張方法
PCL側
- Xamarin.FormsのWebViewを継承してWebViewExを作る。WebViewExにはページ遷移イベントをプラットフォーム側から通知できるようにする
- Viewに作ったWebViewExを埋め込む
using Xamarin.Forms;
using System.Reactive.Subjects;
namespace TestApp.Core.Controls
{
public class WebViewEx : WebView
{
Subject<HandleStartedMessage> _handleStarted = new Subject<HandleStartedMessage>();
public IObservable<HandleStartedMessage> HandleStarted { get { return _handleStarted; } }
public void RaiseHandleStarted(HandleStartedMessage message)
{
_handleStarted.OnNext(message);
}
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TestApp.Views.TestView"
xmlns:local="clr-namespace:TestApp.Controls;assembly=TestApp">
<ContentPage.Content>
<local:WebViewEx x:Name="webView" />
</ContentPage.Content>
</ContentPage>
コツはxmlns:localでしょうか。
ここでXaml上でlocal:Hogeという形でカスタムコントロールを使えるようにしています
Xamlあんまり詳しくないので分かんないですが、C#的にはusing local=TestApp.Controls;ということをしてるんじゃないかなと推測しています。
iOS側
PCLで作ったカスタムコントロールに対してレンダラを実装してあげる必要があります。
- PCLのWebViewExに対応するレンダラを作成する
- レンダラのNativeなコントロールでのクリック通知をイベントで渡す
using System;
using Xamarin.Forms.Platform.iOS;
using Xamarin.Forms;
using TestApp.Controls;
using TestApp.Touch.Controls;
using MonoTouch.UIKit;
using MonoTouch.Foundation;
[assembly: ExportRenderer (typeof (WebViewEx), typeof (WebViewExRenderer))]
namespace TestApp.Touch.Controls
{
public class WebViewExRenderer : WebViewRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
ShouldStartLoad = HandleShouldStartLoad;
}
}
bool HandleShouldStartLoad (UIWebView webView, NSUrlRequest request, UIWebViewNavigationType navigationType)
{
var webViewEx = (WebViewEx)Element;
webViewEx.RaiseHandleStarted(new HandleStartedMessage(){ Uri = new Uri(request.Url.ToString())});
return true;
}
}
}
……なんか、継承関係とかがXamarin公式ドキュメントと違うんだが
まぁ、ドキュメントに「よりよい形を模索中」って書いてたし、仕方ないね。
Android側
おそらくiOS側と同じように作れば良さそうです。
こちらは公式ドキュメントに書かれてる通り、ControlにNativeのWebViewが入っているので、SetWebClientでカスタムクライアントぶっ込めば多分いける。。。はず。
(筆者はAndroid4系の実機持ってませんorz エミュレータ? まともに動きません。。。)
結論
- Xamarin.Formsはネイティブのコントロールの機能が欲しいときに簡単にいじれる
- カスタムしたコントロールをXaml上に書ける
- かゆいところに手が届くwrite once run everywhereが可能
ちなみに、githubにはxamarin.forms.labs( https://github.com/XForms/Xamarin-Forms-Labs )という便利ライブラリがあります。
カスタムコントロール自作する前にlabsを見てみる方がいいと思います。
備考
ところで、完全に新しいコントロールを作りたい時はどうすればいいのでしょう?
例えば
- iOSで出たばかりのコントロールを使いたい。
- Androidは機能的には存在するが複数コントロールの組み合わせで実現可能。
- アプリケーションドメイン的にはiOSのコントロールがちょうど良いので、iOSのコントロール相当のカスタムコントロールをformsに追加したい
- よって、iOSは1:1、Androidは1:Nの形のバインディングが欲しいケース
どうするんだろ?