Xamarin.FormsでWebViewのURLの遷移イベントをフックする

Xamarin.FormsにはWebViewがある。
だからHybridページやリッチテキストなビューを作りたい。
しかし現状のXamarin.FormsのWebViewだと **非常に非力**
ページの表示ができる程度です。
今回、iOSのカスタムURIのクリックをアプリ側でフックしようとしましたが
当然のごとくできませんでした。

ではどうするのか？

**→Xamarin.Formsを拡張する。**

# Xamarin.Formsの拡張方法

## PCL側

1. Xamarin.FormsのWebViewを継承してWebViewExを作る。WebViewExにはページ遷移イベントをプラットフォーム側から通知できるようにする
2. Viewに作ったWebViewExを埋め込む

```WebViewEx.cs
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);
        }

    }
}
```
```WebView.xaml
<?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で作ったカスタムコントロールに対してレンダラを実装してあげる必要があります。

1. PCLのWebViewExに対応するレンダラを作成する
2. レンダラのNativeなコントロールでのクリック通知をイベントで渡す

```WebViewExRenderer.cs
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の形のバインディングが欲しいケース

どうするんだろ？