[Xamarin.Forms]NativeのカスタムビューをXamlから使う

## はじめに

Xamarin.Formsで、iOSのUIViewを拡張したビューを使いたかったのですが、やり方がよくわからなかったので手順を残しておきます。


## 用意するクラス

必要なクラスは下記リストのとおり。

- プラットフォーム共通部分
    - カスタムView (Viewの子クラス)

- 各プラットフォーム (iOS & Android)
    - カスタムView (iOSならUIViewの子クラス)
    - カスタムRenderer




## 実装

### プラットフォーム共通部分

Viewの子クラスとして作成します。
単純にするためにプロパティなどは何もなしにしています。

```csharp:CustomView.cs
using Xamarin.Forms;

namespace CustomView.Forms {
    public class CustomView : View {
    }
}
```

定義したカスタムビューをXamlで使ってみます。

```xml:MainPage.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"
             xmlns:local="clr-namespace:CustomView.Forms"
             x:Class="CustomView.Forms.MainPage">
    <StackLayout>
        <StackLayout.Margin>
            <!-- iOSはトップに 20 の余白が必要 -->
            <OnPlatform x:TypeArguments="Thickness">
                <On Platform="iOS">0, 20, 0, 0</On>
                <On Platform="Android">0, 0, 0, 0</On>
            </OnPlatform>
        </StackLayout.Margin>

        <Label Text="Custom View Sample" HorizontalOptions="Center" />
        <local:CustomView
            HorizontalOptions="FillAndExpand"
            VerticalOptions="FillAndExpand" />
    </StackLayout>
</ContentPage>
```

```local:CustomView``` のところでカスタムビューを使っています。


### iOS

#### カスタムビュー

Native側では、```Draw```メソッドをオーバーライドして好きなように描画します。
ここでは、[公式のサンプル](https://developer.xamarin.com/guides/ios/platform_features/graphics_animation_ios/core_graphics/)を参考に図形を描画してみます。

```csharp:iOS/NativeCustomView.cs
using UIKit;
using CoreGraphics;

namespace CustomView.Forms.iOS {
    public class NativeCustomView : UIView {
        public override void Draw(CGRect rect) {
            base.Draw(rect);

            using (CGContext g = UIGraphics.GetCurrentContext()) {
                // calc points
                float height = (float)rect.Height / 2;
                float centerX = (float)rect.Width / 2;
                CGPoint p1 = new CGPoint(centerX - height / 2, height / 2 + height);
                CGPoint p2 = new CGPoint(centerX, height / 2);
                CGPoint p3 = new CGPoint(centerX + height / 2, height / 2 + height);

                //set up drawing attributes
                g.SetLineWidth(10);
                UIColor.Blue.SetFill();
                UIColor.Red.SetStroke();

                //create geometry
                var path = new CGPath();
                path.AddLines(new CGPoint[]{
                    p1,
                    p2,
                    p3
                });
                path.CloseSubpath();

                //add geometry to graphics context and draw it
                g.AddPath(path);
                g.DrawPath(CGPathDrawingMode.FillStroke);
            }
        }
    }
}
```

#### カスタムレンダラー

Nativeのカスタムビューとプラットフォーム共通のカスタムビューを結びつけるためのRendererを用意します。
Rendererという名前ですが、どちらかというとNativeと共通部分の橋渡し的な役割だと思います。

（プラットフォーム共通の）Viewの初期化時に ```OnElementChanged``` が呼ばれるので（複数回呼ばれることもあるらしいので注意）、そこでNativeのカスタムビューを初期化します。

```csharp:iOS/NativeCustomRenderer.cs
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

[assembly: ExportRenderer(typeof(CustomView.Forms.CustomView), typeof(CustomView.Forms.iOS.NativeCustomRenderer))]
namespace CustomView.Forms.iOS {
    public class NativeCustomRenderer : ViewRenderer<CustomView, NativeCustomView> {
        NativeCustomView customView; // この例では必要ないが、あとで必要になったときのため。

        protected override void OnElementChanged(ElementChangedEventArgs<CustomView> e) {
            base.OnElementChanged(e);

            if (Control == null) {
                customView = new NativeCustomView();  // Nativeカスタムビューの生成
                SetNativeControl(customView);  // 生成したビューをNativeコントロールとしてセット
            }
            if (e.OldElement != null) {
                // Unsubscribe
                // 何かイベントをSubscribeしているときには、必ずここでUnsubscribeしておく。
            }
            if (e.NewElement != null) {
                // Subscribe
                // 何かイベントをSubscribeしたいときには、ここでSubscribeする。
            }        
        }
    }
}
```

大事なのは、

```csharp
[assembly: ExportRenderer(typeof(CustomView.Forms.CustomView), typeof(CustomView.Forms.iOS.NativeCustomRenderer))]
```

の行で、これで共通のカスタムビューとNativeのレンダラーを結びつけます。
ExportRendererの引数は ExportRenderer(typeof(**プラットフォーム共通のカスタムビュークラス**), typeof(Nativeのカスタム**レンダラー**クラス)) なので間違えないように注意。

また、ViewRendererの型パラメーターのほうは、ViewRenderer<**プラットフォーム共通のカスタムビュークラス**, Nativeのカスタム**ビュー**クラス> なので混同しないように。

### Android

Androidも細かい違いはあるがiOSと同様なので、コードだけ載せておく。
図形の描画は、[公式サイト](https://developer.xamarin.com/recipes/android/other_ux/drawing/draw_2d_graphics/)を参考にした。

```csharp:Droid/NativeCustomView.cs
using Android.Views;
using Android.Content;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.Graphics.Drawables.Shapes;

namespace CustomView.Forms.Droid {
    public class NativeCustomView : View {
        private readonly ShapeDrawable _shape;

        public NativeCustomView(Context context) : base(context) {
            var paint = new Paint();
            paint.SetARGB(255, 200, 255, 0);
            paint.SetStyle(Paint.Style.Stroke);
            paint.StrokeWidth = 4;

            _shape = new ShapeDrawable(new OvalShape());
            _shape.Paint.Set(paint);
        }

        protected override void OnDraw(Android.Graphics.Canvas canvas) {
            base.OnDraw(canvas);

            int width = canvas.Width / 2;
            int left = (canvas.Width - width) / 2;
            int top = (canvas.Height - width) / 2;
            _shape.SetBounds(left, top, left + width, top + width);
            _shape.Draw(canvas);
        }
    }
}
```

#### カスタムレンダラー

```csharp:Droid/NativeCustomRenderer.cs
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Android.Content;

[assembly: ExportRenderer(typeof(CustomView.Forms.CustomView), typeof(CustomView.Forms.Droid.NativeCustomRenderer))]
namespace CustomView.Forms.Droid {
    public class NativeCustomRenderer : ViewRenderer<CustomView, NativeCustomView> {
        NativeCustomView customView; // この例では必要ないが、あとで必要になったときのため。

        public NativeCustomRenderer(Context context) : base(context) {
        }

        protected override void OnElementChanged(ElementChangedEventArgs<CustomView> e) {
            base.OnElementChanged(e);

            if (Control == null) {
                customView = new NativeCustomView(Context);
                SetNativeControl(customView);
            }
            if (e.OldElement != null) {
                // Unsubscribe
                // 何かイベントをSubscribeしているときには、必ずここでUnsubscribeしておく。
            }
            if (e.NewElement != null) {
                // Subscribe
                // 何かイベントをSubscribeしたいときには、ここでSubscribeする。
            }
        }
    }
}
```

## ソース

ソースは[こちら](https://github.com/JunSuzukiJapan/Xamarin.Forms.CustomView)。


## 参考

[Implementing a View](https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/custom-renderer/view/)