LoginSignup
9
13

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-06-28

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

どうするんだろ?

9
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
13