XamarinからNetMAUIへ移行中(3):地図移植(1):CustomPin

,

GeoDiveExaのメイン機能である地図の移植は、CustomPinや背景地図、デジタルズーム機能が1番の問題です。まずはCustomPinの移植を行いました。

1)Mapの移植

GeoDiveExaはMark位置を表すピンやターゲットを表すピンなど色々なタイプのピンを使用しており、背景地図も使用して居たのでXamarinの場合はCustomMapを作成しCustomRanderでiOS部分の実装を行なって居ましたがNetMAUIではCustomHandlerのみで大体の実装は出来そうです。開発環境のフォルダー構造を見ても実際のファイルを見ても随分スッキリしており、iOS部分も少なくなって居ますので、Androidへの移植もXamarinよりNetMAUIが簡単の様に見えます。

1−1)関連ファイルとクラス対比

Map関連ファイル対応表。一番大きな違いはCustomMapRendererCustomMapHandlerになる事です。またCustomMapHandlerを使う為に、MauiProgram.csにMapとCustomMapHandlerを使用する事を宣言して居ます。

XamarinのMap関連部分NetMAUIのMap関連部分
<CustomMap.cs>
CustomPin 実装
CustomMAP 実装
<CustomPin.cs>
CustomPin 実装
<CustomMapRenderer.cs>
CustomMKAnnotationView 実装
CustomTileOverlay 実装
CustomMapRenderer 実装
<CustomMapClass.cs>
CustomAnnotation 実装
CustomTileOverlay 実装
CustomMapHandler 実装
なし<MauiProgram.cs>
.UseMauiMaps() 実行
.ConfigureMauiHandlers(handlers =>
  { handlers.AddHandler
<Microsoft.Maui.Controls.Maps.Map,
CustomMapHandler>();
}); 実行
  

2)CustomPinの移植(1)

XamarinのCustomPinをNetMAUIに移植するには、CustomRendererをCustomHandlerにして。。。と書いてありますが、今一つピンときません。なのでGeminiに聞いてみます。

<geminiの回答例>
Xamarin.Formsから.NET MAUIに移行されたのですね。.NET MAUIでは、より柔軟にカスタムピンを作成できます。以下に、.NET MAUIでCustomPinを実装する際のサンプルコードと解説をいくつかご紹介します。

C#
public class CustomPin : Pin
{
    public CustomPin(string label, double latitude, double longitude, string pinImage)
    {
        Label = label;
        Position = new Position(latitude, longitude);
        IconImageSource = pinImage;
    }
}

[assembly: ExportRenderer(typeof(CustomPin), typeof(CustomPinRenderer))]
namespace YourNamespace.iOS
{
    public class CustomPinRenderer : ViewRenderer<CustomPin, MKAnnotationView>
    {
        protected override void OnElementChanged(ElementChangedEventArgs<CustomPin> e)
        {
            base.OnElementChanged(e);

            if (e.NewElement != null)
            {
                var annotationView = (MKAnnotationView)Control;
                annotationView.Image = UIImage.FromFile("custom_pin.png");
                annotationView.CanShowCallout = true;

                annotationView.Selected += (sender, args) =>
                {
                    var customPin = (CustomPin)Element;
                    // ピンがタップされた時の処理
                    // 例: メッセージを表示
                    var alert = UIAlertController.Create("ピンタップ", $"ピンラベル: {customPin.Label}", UIAlertAction.Cancel);
                    UIApplication.SharedApplication.KeyWindow.RootViewController.PresentViewController(alert, true, null);
                };
            }
        }
    }
}

これをそのまま実装してもエラーばかりです。回答のソースはXamarin時代の物も混じっている様な感じですね。PositionがあるしassemblyRendererもあります。いくつか聞いてみても有用なコードサンプルは出てきませんので、通常のGoogle検索で参考となるサイトやサンプルを探しましたが、右往左往するばかりで、同じ様な人々が多く居ました。結局使えたのは以下のサイトのみでした(=有料のパッケージはいくつかありました)。

Customize map pins in .NET MAUI

CustomPinを作成するためのキー項目は以下の4項目でした。

キー項目内容と感想
1)カスタムマップハンドラーを登録する
例)
var builder = MauiApp.CreateBuilder();
builder.UseMauiApp<App>().UseMauiMaps();
builder.ConfigureMauiHandlers(handlers => { handlers.AddHandler<Microsoft.Maui.Controls.Maps.Map, CustomMapHandler>(); });
マップの動作イベントを変更してPinの画像を設定するので、そのハンドラーを設定しないと、CustomMapHandler動作自体ができない。この点が検索してもAIに聞いてもなかなか出てこないので、みんな苦労している点なのでしょう。
2)Pinにイメージ情報を付加する為にCustomPinを作成する
例)
public class CustomPin : Pin { public static readonly BindableProperty ImageSourceProperty = BindableProperty.Create(nameof(ImageSource), typeof(ImageSource), typeof(CustomPin);

public ImageSource? ImageSource { get => (ImageSource?)GetValue(ImageSourceProperty); set => SetValue(ImageSourceProperty, value); } }
イメージ情報(ImageSource)を追加したCustomPinを作成するのは普通の事として納得いきます。
3)Map.PinsにCustomPinを追加すると、PinsのPropertyChangedイベントが自動的に実行されるで、それを受け取る為にPropertyMapperをCustomMapHandlerに追加する。
例)例ではMap.Pinsの変更イベントで関数MapPinsを実行する様にしています。
public static readonly IPropertyMapper<IMap, IMapHandler> CustomMapper = new PropertyMapper<IMap, IMapHandler>(Mapper) { [nameof(IMap.Pins)] = MapPins, };
ここも嵌るポイントですね。特に設定せずともMap.Pinsのイベントが実行されるなんて、どこに書いてあるのか?(=知った後に調べると確かにありましたが、PropertyChangedイベントが発生するとは書いてありません。。。が、常識なのですね)。
このイベントが最初からあるので、特別に作る必要もないのが、迷わせる点ですね。
後は、このイベントを横取りしてMapPinsを実行します。
4)PinsのPropertyChangedをCustomMapHandlerのMapPinsで判定して、イメージを設定する事によりカスタムピンを作成する。MapPinsで、map.Pinsのデータ(CustomPin)を取得して、後は自由にiOSのMap用のMKAnnotationを作成すればカスタムピンの出来上がりです。

この方法でCustomPinを作成してGeoDiveExaで使用しているPinの描画ができる様になりました。

3)CustomPinの移植(2)

CustomPinは一応できましたが、上記のフローではPins=List<CustomPin>の変更イベントを使用してカスタムピンを作成している為に、毎回全部のMKAnnotationを消してから作成しているフローなので非効率でした。
GeoDiveExaの動作ではCustomPinの追加と、以前作成したCustomPinの交換のみです(=削除もありますが、これは通常動作で可能ですから考慮しません)。

Pinの追加交換に使用できる様なプロパティーがIMapにないか???と調べましたが、使えそうな物が有りませんでした。

なので、「常識の独自のプロパティを作成して、PropertyChangedイベントを発生させて、CustomMapHandlerで横取り」する事にします(=これは、NetMAUIのお作法に則っている方法とは言えないと思いますが、もっと根源的な常識なので良しとしましょう)。

CustomPinの修正内容
1)MapViewModelにAddNewPinReplacePinを作成するPropertyChangedイベントを発生させる変数を作成します。
2)AddNewPinReplacePinのsetにOnPropertyChangedを追加する。
例)
private CustomPin _addNewPin; public CustomPin AddNewPin { get => _addNewPin; set { if (_addNewPin != value) { _addNewPin = value; OnPropertyChanged(); } } }
変数に値を設定したらPropertyChangedイベント実行します。
3)CustomMapHandlerでAddNewPinReplacePinのイベントを取得できるように、MapViewModelのPropertyChangedイベントをCustomMapHandlerのOnMapViewModelPropertyChangedに対応づける。
例)
// CustomPin用 MapViewModel.PropertyChanged += OnMapViewModelPropertyChanged;
常識のプロパティーチェンジイベントをとにかくCustomMapHandler内で処理できる様に設定します。
4)OnMapViewModelPropertyChangedでPropertyNameを比較して、AddNewPinならMapAddNewPinを実行する。
ReplacePinならMapReplacePinを実行する。
これで1つのCustomPinの追加や、変更を自由に行えますので効率的です。
勝手にProperty作って、自由に機能拡張する=何でもできそう

4)9月初めの移植地図画面

移動Pinも同じ様に表示される様になりました(=左:Xamarin版、右:NetMAUI版)。

移動Pin表示

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

PAGE TOP