MAUIマイグレーションメモ
XAML
xmlns="http://xamarin.com/schemas/2014/forms"
を
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
に一括置換
xmlns:sv="clr-namespace:AiForms.Renderers;assembly=SettingsView"
を
xmlns:sv="clr-namespace:AiForms.Settings;assembly=SettingsView"
に一括置換
xmlns:extra="clr-namespace:AiForms.Dialogs.Abstractions;assembly=AiForms.Dialogs"
を
xmlns:extra="clr-namespace:AiForms.Dialogs;assembly=AiForms.Maui.Dialogs"
に一括置換
C# コード
using Xamarin.Forms を消す
Color
定数がColorsに変わったのでColor.RedなどはColors.Redというふうに変換する
PlatformSpecific
usingを以下に変更
using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific or AndroidSpecific;
using Platform = Microsoft.Maui.Controls.PlatformConfiguration;
Android
Maii ColorをAndroid Colorに
ToPlatform()
を使う。
Maui Colorを ColorStateListに変換
ToDefaultColorStateList();
iOS
Releaseビルドでこける
System.ExecutionEngineException: Attempting to JIT compile method
とかのエラーでこける場合はプロジェクトファイルのiOSリリースのプロパティグループに
<UseInterpreter>true</UseInterpreter>
を追加する。
色をUIColorに
ToUIColorが非推奨に代わりにToPlatform()
を使う。
ImageSourceをUIImageに
以下の拡張メソッドがある
var result = await ImageSource.GetPlatformImageAsync(MauiContext);
var icon = result.Value; // UIImageを取り出す
Control
Colorプロパティ
Color値のプロパティに文字列(#FF00FFなど)をBindしても無効になる。
Forms時代は自動で変換してくれたがMAUIではしてくれないので別途Converterなどが必要。
Xaml上から直接入力した文字列やStaticResource/DynamicResourceで指定したものは有効。
BoxView
CornerRadiusはBackgroundColorを設定していると効かない。代わりにColorを設定する。
Frame
Androidは何かバグってる(サイズ計算がおかしい)ので代わりにBorderを使う方が良い。
がBorderはBorderで画像のクリップができない問題があるので画像使う場合はFrameを使うしか無い。
Border
Frameに代わる境界線用のコントロール。
ただFrameに書いたようなクリップの問題やBorderに角丸を設定してPaddingを設定すると子要素の角も削られる問題もある。
TabbedPage
iOS
実体
Microsoft.Maui.Controls.Handlers.Compatibility.TabbedRenderer
カスタマイズする場合はTabbedPageとTebbedRendererのサブクラスを作成しそれをHandler登録する。
handlers.AddHandler(typeof(MyTabbedPage), typeof(MyTabbedPageRenderer));
Android
実体
src/Controls/src/Core/Platform/Android/TabbedPageManager.cs
src/Controls/src/Core/HandlerImpl/TabbedPage/TabbedPage.Android.cs
何故かAndroidはTabbedPageのpartial classとしてPlatform実装も書かれている。
カスタマイズ例
public partial class MyTabbedPage: TabbedPage
{
public MyTabbedPage()
{
#if ANDROID
this.HandlerChanged +=MyTabbedPage_HandlerChanged;
this.HandlerChanging += MyTabbedPage_HandlerChanging;
#endif
}
}
public partial class MyTabbedPage
{
BottomNavigationView _bottomNavigationView;
BottomNavigationView BottomNavigationView => _bottomNavigationView ??= GetBottomNavigationView();
private void MyTabbedPage_HandlerChanged(object sender, EventArgs e)
{
// ここでBottomNavigationViewをあれこれ頑張る
}
private void MyTabbedPage_HandlerChanging(object sender, HandlerChangingEventArgs e)
{
if (e.OldHandler == null)
{
return;
}
// 後始末
}
BottomNavigationView GetBottomNavigationView()
{
// ReflectionでBottomNavigationViewを取得する
var tabbedPageManagerInfo = typeof(TabbedPage).GetField("_tabbedPageManager", BindingFlags.Instance | BindingFlags.NonPublic);
var manager = tabbedPageManagerInfo.GetValue(this);
var bottomNaviInfo = manager.GetType().GetField("_bottomNavigationView", BindingFlags.Instance | BindingFlags.NonPublic);
return bottomNaviInfo.GetValue(manager) as BottomNavigationView;
}
この方法の場合はHandlerを上書きする必要は無い。
ビルド・発行
iOSのdotnet publishの注意事項
/p:CodesignKey="Apple Distribution: HOGE CO.,LTD. (XXXXXXX)"
みたいにCodesignKeyにカンマが含まれると実行できないのでカンマは%2cに置き換える必要あり
/p:CodesignKey="Apple Distribution: HOGE CO.%2cLTD. (XXXXXXX)"
不具合
Gridの子要素の高さが無視される
7.0.81/7.0.100 SDK 7.0.200 で確認。
→7.0.300 で解消済み
以下のような場合GridのPaddingの上下の値が無視される現象がある。
<Grid>
<Grid Padding="30">
<Label Text="Text" />
</Grid>
</Grid>
ネストしていなくてもGridにPaddingが設定されてる場合に発生するかも?
対策は親のGridにRowDefinitions="Auto"
を指定すること。
SafeAreaの設定がうまくいかない
SafeAreaをオフにした時にうまく反映されない。
SafeAreaを使わずに領域一杯を使いたい時は工夫が必要。
ScrollView
iOSで子のStackLayoutなどの子要素を動的に追加したり削除した時にサイズが変更されない。Xamairn.Formsの頃は問題なく更新されてた。
ScrollView+Layoutの代わりにCollectionViewを使って回避する。
Border / Frame
FrameはAndroidでサイズがバグっててまともにレイアウトできないので、Xamarinのままだと大きくレイアウトが崩れる。基本的にBorderを使うことで解決。
ただしBorderはBorderでiOSの方でコンテンツをClipできない問題がある。
画像を丸くクリップするような形で使う場合はiOSはFrame、AndroidはBorderみたいな分岐で対応するしかなさそう。
↓こういうWrapper作って暫定対応した。
public class RoundImage:ContentView
{
public static BindableProperty SourceProperty = BindableProperty.Create(
nameof(Source),
typeof(ImageSource),
typeof(RoundImage),
default(ImageSource),
defaultBindingMode: BindingMode.OneWay
);
public ImageSource Source{
get { return (ImageSource)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
Image _image;
Frame _frame;
Border _border;
public RoundImage()
{
_image = new Image();
_image.Aspect = Aspect.AspectFill;
_image.SetBinding(WidthRequestProperty, new Binding("WidthRequest", source: this));
_image.SetBinding(HeightRequestProperty, new Binding("HeightRequest", source: this));
_image.SetBinding(Image.SourceProperty, new Binding("Source", source: this));
// MAUIの不具合による暫定対応。
// iOSのBorderでClipできない問題とAndroidでFrameでLayoutが崩れる問題
#if IOS
_frame = new Frame();
_frame.Padding = new Thickness();
_frame.HasShadow = false;
_frame.BorderColor = Colors.Transparent;
_frame.IsClippedToBounds = true;
_frame.SetBinding(WidthRequestProperty, new Binding("WidthRequest", source: this));
_frame.SetBinding(HeightRequestProperty, new Binding("HeightRequest", source: this));
_frame.Content = _image;
Content = _frame;
#elif ANDROID
_border = new Border();
_border.Padding = new Thickness();
_border.Stroke = Colors.Transparent;
_border.StrokeThickness = 0;
_border.SetBinding(Border.WidthRequestProperty, new Binding("WidthRequest", source: this));
_border.SetBinding(Border.HeightRequestProperty, new Binding("HeightRequest", source: this));
_border.Content = _image;
Content = _border;
#endif
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if(propertyName == WidthRequestProperty.PropertyName)
{
#if IOS
_frame.CornerRadius = (float)WidthRequest / 2f;
#elif ANDROID
_border.StrokeShape = new RoundRectangle
{
CornerRadius = new CornerRadius(WidthRequest / 2d)
};
#endif
}
}
}
Border その2
BorderとContentの間に謎のスペース。
Borderの線と内容をぴったりさせたくても出来ない。今のところ回避策なし。
Effectなどでレイアウトに直接線を設定するようにしたりすることで代替は可。
回避例 ボーダーを設定できるGrid
MediaPicker
iOSでの向きが正しくない問題はXamarinを継承。
Xamarinの時と同様にXam.Plugin.Mediaの6.0.0(pre)以降を使うしかない。
ImageButton
AndroidでPaddingを設定するとサイズが正しく表示されない。
Padding0で使う分には問題ないがPaddingでタップ領域を多めに取っていた場合などは使えない。
回避策はImageButtonは使わずにContentViewでImageをWrapしてTapGestureを登録する方法に置き換える。
またはOnPlatformマークアップを使ってWidthRequestとHeightRequestにiOSは画像サイズ、Androidは画像サイズ+Paddingサイズを指定する。
Prism.Maui
Appクラスの罠
ドキュメントのどこにも書いていないがMauiでもAppクラスはPrismApplicationを継承するように変更しないと動かない。
public partial class App : PrismApplication
<prism:PrismApplication xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="http://prismlibrary.com"
x:Class="Sample.App">
</prism:PrismApplication>
モーダル遷移の方法
最初のセグメントにUseModalNavigationをつけないと機能しない。完全に罠。
Nugetで公開されているバージョン(8.1.273-pre )では何やってもモーダル遷移ができないので注意
await navigationService
.CreateBuilder()
.AddSegment<NaviViewModel>(config =>
{
config
.UseModalNavigation(true)
.AddParameter(KnownNavigationParameters.Animated, animated);
})
.AddSegment<HogeViewModel>()
.AddParameter(ParametersBase.ParameterKey, parameters)
.NavigateAsync();
Builder使わない場合
navigationService.NavigateAsync("NaviPage?useModalNavigation=True/HogePage");
ボーダーを設定できるGrid
public partial class BorderGrid: Grid
{
public static BindableProperty BorderColorProperty = BindableProperty.Create(
nameof(BorderColor),
typeof(Color),
typeof(BorderGrid),
default(Color),
defaultBindingMode: BindingMode.OneWay
);
public Color BorderColor{
get { return (Color)GetValue(BorderColorProperty); }
set { SetValue(BorderColorProperty, value); }
}
public static BindableProperty BorderWidthProperty = BindableProperty.Create(
nameof(BorderWidth),
typeof(double),
typeof(BorderGrid),
default(double),
defaultBindingMode: BindingMode.OneWay
);
public double BorderWidth{
get { return (double)GetValue(BorderWidthProperty); }
set { SetValue(BorderWidthProperty, value); }
}
public static BindableProperty BorderRadiusProperty = BindableProperty.Create(
nameof(BorderRadius),
typeof(double),
typeof(BorderGrid),
default(double),
defaultBindingMode: BindingMode.OneWay
);
public double BorderRadius{
get { return (double)GetValue(BorderRadiusProperty); }
set { SetValue(BorderRadiusProperty, value); }
}
protected override void OnHandlerChanged()
{
base.OnHandlerChanged();
UpdateBorder();
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if(propertyName == BorderColorProperty.PropertyName ||
propertyName == BorderWidthProperty.PropertyName ||
propertyName == BorderRadiusProperty.PropertyName)
{
if(Handler != null)
{
UpdateBorder();
}
}
}
}
public partial class BorderGrid
{
void UpdateBorder()
{
var handler = Handler as IPlatformViewHandler;
handler.PlatformView.Layer.CornerRadius = (float)BorderRadius;
handler.PlatformView.Layer.BorderWidth = (float)BorderWidth;
handler.PlatformView.Layer.BorderColor = BorderColor.ToCGColor();
handler.PlatformView.ClipsToBounds = true;
}
}
public partial class BorderGrid
{
GradientDrawable _border;
void UpdateBorder()
{
var handler = Handler as IPlatformViewHandler;
var context = handler.PlatformView.Context;
var view = handler.PlatformView;
_border ??= new GradientDrawable();
var width = (int)context.ToPixels(BorderWidth);
var radius = (int)context.ToPixels(BorderRadius);
_border.SetStroke(width, BorderColor.ToPlatform());
_border.SetCornerRadius(radius);
view.SetPadding(width, width, width, width);
view.ClipToOutline = true;
view.Background = _border;
}
}