コンテンツ
こちらの続き。
Visual Studio2022と、.NET 8が正式リリースされました。
ので、Xamarin.Formsアプリを、新規作成したMAUIプロジェクトに移行していく。
そこで修正が必要だったり、詰まったところをまとめていく。
なお、2024年1月時点のお話です。
Device系は置換が必要
これに限らず、ちょこちょこ置換が必要なものは多い。
ので、とりあえず一例。
BeginInvokeOnMainThread関数
Microsoft.Maui.Controls.Application.Current.Dispatcher.Dispatchに置き換えるだけ。
StartTimer関数
Application.Current.Dispatcher.StartTimerに置き換えるだけ。
Device.RuntimePlatform関数
Microsoft.Maui.Devices.DeviceInfo.Platformに置き換えるだけ。
地図系
地図の操作自体は公式でしっかり解説されてて、移行することはいっぱいあるけど、そんなに大変じゃない感じ。
Pinの外観を変えたい
これは公式に書いてないが、下記のやり方でいけました!ありがたや!
https://vladislavantonyuk.github.io/articles/Customize-map-pins-in-.NET-MAUI/
あと、CreateMauiAppの修正でちょっとはまった。
UseMauiMaps→ConfigureMauiHandlersの順が大事っぽい。
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
// Mapを利用する
.UseMauiMaps()
// カスタム用レンダラーを利用する
.UseMauiCompatibility()
#if ANDROID
// Android用クラスインスタンスで生成
.UseMauiApp<Droid.HogeAppDroid>()
// Activityインスタンスを渡す
.ConfigureLifecycleEvents(events =>
{
events.AddAndroid(android => android
.OnCreate((activity, bundle) => Droid.HogeAppDroid.OnCreate(activity, bundle))
);
})
// Android用カスタムコントロールのレンダラーを登録
.ConfigureMauiHandlers((handlers) =>
{
handlers.AddHandler(typeof(Microsoft.Maui.Controls.Maps.Map), typeof(Droid.CustomMapHandler));
})
#elif IOS
// iOS用クラスインスタンスで生成
.UseMauiApp<iOS.HogeAppIOS>()
// iOS用カスタムコントロールのレンダラーを登録
.ConfigureMauiHandlers((handlers) =>
{
handlers.AddHandler(typeof(Microsoft.Maui.Controls.Maps.Map), typeof(iOS.CustomMapHandler));
})
#endif
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
#if DEBUG
builder.Logging.AddDebug();
#endif
return builder.Build();
}
ただ、後からラベルだけ変えるとかはできないっぽい?
変えたきゃPinのインスタンスごと作り直せばいける。
移動は表示してから?
位置の移動処理を実装しておいても、エラーにはならないけど、map.IsVisible = Falseの状態だとAndroidは効果がなかった。
プロパティの変更イベントハンドラを実装して、map.IsVisibleがTrueに変更されたら、別スレッドで1秒ほど待機→移動って処理にして回避。なんでや。
住所情報
以前は文字列で1発でとれたけど、Placemarkクラスの各プロパティを連結する必要がある。
これがAndroidとiOSでもかなり取れ方が違うっぽい。
Androidは同じ情報が複数プロパティに反映されがち。
分けて実装する必要がありそうだった。
/// <summary>
/// 住所更新(ラベル)
/// </summary>
private async Task UpdateAddress( Location pos)
{
string address = "";
// 住所文字処理
try
{
// 住所文字列の取得
IEnumerable<Placemark> addresses = await Geocoding.Default.GetPlacemarksAsync(pos.Latitude, pos.Longitude);
Placemark placemark = addresses?.FirstOrDefault();
// 結果をとる。
if (placemark != null)
{
// AndroidとiOSで結果が異なるので作り方もわける
// 郵便番号はいらない
if (!string.IsNullOrEmpty(placemark.CountryName))
{
// 国名はつける(日本だけ削除)
address = placemark.CountryName.Replace("日本", "");
}
OID
// 郵便番号はいらない
address += placemark.AdminArea // 都道府県
+ placemark.Locality // 市区町村
+ placemark.SubLocality // サブの局所性
+ placemark.Thoroughfare; // 町名
// 丁目
// 四国の海だとCountryNameとFeatureNameに"日本"が設定されて、他のプロパティは空なので
// AdminAreanoの空チェックも行う
if (!string.IsNullOrEmpty(placemark.AdminArea) && !string.IsNullOrEmpty(placemark.SubThoroughfare))
{
// 数字が連結されるならハイフンを挟む
if (IsIntParseOneChar(address, false) && IsIntParseOneChar(placemark.SubThoroughfare, true))
{
address += "‐";
}
address += placemark.SubThoroughfare;
}
// 号
if (!string.IsNullOrEmpty(placemark.AdminArea) && !string.IsNullOrEmpty(placemark.FeatureName))
{
// 数字が連結されるならハイフンを挟む
if (IsIntParseOneChar(address, false) && IsIntParseOneChar(placemark.FeatureName, true))
{
address += "‐";
}
address += placemark.FeatureName;
}
address += placemark.AdminArea // 都道府県
+ placemark.Locality // 市区町村
+ placemark.Thoroughfare; // 町名&丁目
// 番地以降
// 四国の海だとCountryNameとFeatureNameに"日本"が設定されて、他のプロパティは空なので
// AdminAreanoの空チェックも行う
if (!string.IsNullOrEmpty(placemark.AdminArea) && !string.IsNullOrEmpty(placemark.SubThoroughfare))
{
// 数字が連結されるならハイフンを挟む
if (IsIntParseOneChar(address, false) && IsIntParseOneChar(placemark.SubThoroughfare, true))
{
address += "‐";
}
address += placemark.SubThoroughfare;
}
// FeatureNameはSubLocality、Thoroughfare、SubThoroughfareがnullの場合
if (string.IsNullOrEmpty(placemark.SubLocality) && string.IsNullOrEmpty(placemark.Thoroughfare) &&
string.IsNullOrEmpty(placemark.SubThoroughfare) && !string.IsNullOrEmpty(placemark.FeatureName))
{
// 数字が連結されるならハイフンを挟む
if (IsIntParseOneChar(address, false) && IsIntParseOneChar(placemark.FeatureName, true))
{
address += "‐";
}
address += placemark.FeatureName;
}
}
}
catch (Exception ex)
{
DebugLog.Log(ex.Message);
address = "";
}
// ピンを作り直してラベルに住所反映したり・・・
}
/// <summary>
/// 数字始まりまたは数字終わりかチェック
/// </summary>
/// <param name="str">チェックする文字列</param>
/// <param name="isFirst">先頭をチェックするならtrue、末尾ならfalse</param>
/// <returns>数字であればtrue</returns>
private bool IsIntParseOneChar(string str, bool isFirst)
{
// 空ならfalse
if (string.IsNullOrEmpty(str))
{
return false;
}
// 先頭または末尾1文字をチェック
string check = "";
if (isFirst)
{
check = str.Substring(0, 1);
}
else
{
check = str.Substring(str.Length - 1, 1);
}
// Strings.StrConv(check, VbStrConv.Narrow, 0)は使えないので半角に置き換えは手作業で行う
check = check.Replace("0", "0").Replace("1", "1").Replace("2", "2").Replace("3", "3").Replace("4", "4")
.Replace("5", "5").Replace("6", "6").Replace("7", "7").Replace("8", "8").Replace("9", "9");
// 判定
return int.TryParse(check, out _);
}
レイアウトまわり
AbsoluteLayoutにすべきか、Gridにすべきか
レイアウトが崩れたら、まず怪しむべき原因はこれな気がする。
重要
https://learn.microsoft.com/ja-jp/dotnet/maui/user-interface/layouts/absolutelayout?view=net-maui-8.0
HorizontalOptions プロパティと VerticalOptions プロパティは、AbsoluteLayout の子には影響しません
全部、ちゃんと子要素のサイズも指定するか、Gridに置き換えるかは、ケースバイケースな気がする。
GridはGridで怪しくて、列や行の高さをプログラムから変更するコードを書くと、Debug版では動くのに、Release版ではアプリが吹っ飛んだ。
また、特定の端末ではGridの隙間を0にしているのに微妙に空いてしまったり。
画像を配置して厳密に指定したいなら、AbsoluteLayoutの方が良い気がする。
あと、確証はないけど、OnSizeAllocatedをoverrideして、base.OnSizeAllocatedの呼び出しタイミングは最初よりは諸々処理した後が良さげ?
透明なラベルはGridを跨ぐべからず
タップ用に透明なラベルを配置して、ボタンの画像を正常時・タップ中で切り替えたかった。
が、Gridの行や列を跨いで配置すると、透明ラベルが表示しているテキストの領域に引きづられる = つまりTextが空文字だとラベル領域が消えた。
これが、iOSやAndroid 9では起こらず、手持ちのAndroid 13で、配置したボタンの一部でしか出ない。なんでや。
Aspect=”AspectFill”が効くようになった?
Aspect=”AspectFill”(アスペクト比を固定する)を選択してるのに、Xamarinは効果がなかったっぽい?
MAUIではちゃんと適用されたことがきっかけで、画像サイズがいまいちだったものが見つかった。
ステータスバーの色
XamarinはiOSのステータスバーもホームバーも無視して突き抜けてた。
けど、これが土台のViewの背景色が反映されつつ、突き抜けなくなった。
でも、ステータスバーの色はむしろ突き抜けててほしい。
Community.Toolkit.Maui.CoreをNugetからインストールして、プログラムから変更するようにした。
/// <summary>
/// ステータスバーにアプリ上部の色を適用する
/// </summary>
private void updateStatusBarColor()
{
// 失敗してもエラーにはしない
try
{
CommunityToolkit.Maui.Core.Platform.StatusBar.SetColor(Color.FromRgb(0x66, 0x66, 0x66));
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Android.App.AlertDialogの色(未解決)
Android.App.AlertDialog.Builderを使ってダイアログを出すとき、今までは背景のみ白を指定して、問題なかった。
それが、Xperia Ace Ⅱだと背景もボタンが白くて見えなくなっちゃった。
じゃあ、指定もメンドイし、Styleの指定をやめて、デフォルトで行こう。
Pixel6で、背景もボタンも黒くて見えない。なんでや。
なので、ボタンは黒を明示的に指定するようStyle.xmlを修正。
こちらを参考にさせてもらいました。
https://qiita.com/hanaaaa/items/dd58ab6946d8ff4a0994
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<style name="MyAlertDialogStyle" parent="Theme.AppCompat.Light.Dialog.Alert">
<item name="buttonBarPositiveButtonStyle">@style/PositiveButtonStyle</item>
<item name="buttonBarNegativeButtonStyle">@style/NegativeButtonStyle</item>
<item name="android:background">@android:color/white</item>
</style>
<!-- AlertDialogPositiveButtonStyle -->
<style name="PositiveButtonStyle" parent="Widget.AppCompat.ButtonBar.AlertDialog">
<item name="android:textColor">@android:color/black</item>
<item name="backgroundTint">@android:color/white</item>
</style>
<!-- AlertDialogrNegativeButtonStyle -->
<style name="NegativeButtonStyle" parent="Widget.AppCompat.ButtonBar.AlertDialog">
<item name="android:textColor">@android:color/black</item>
<item name="android:textColor">@android:color/white</item>
</style>
</resources>
これで、Xperia Ace Ⅱだと白背景+ボタン文字が緑に、Pixel6だと白背景+ボタン文字が黒に。統一せぇ。
そしてXperia Aceで背景もボタンが白くて見えなくなっちゃった。なんなの…。
起動&クラッシュ系
イベントのBeginInvoke(context, intent, null, null)
コンパイルエラーにはならないんだけど、実行すると吹っ飛ぶ。
こちらを参照。
https://learn.microsoft.com/ja-jp/dotnet/core/porting/net-framework-tech-unavailable
https://devblogs.microsoft.com/dotnet/migrating-delegate-begininvoke-calls-for-net-core/
Task.Run(() => { イベント名?.Invoke(context, intent); });に置き換えるだけ。
アプリのストレージを削除すると起動できなくなる
これはDebug版だけで起こる。
「Fast Deployment の使用」を無効にするか、一度アンインストールして配置し直すこと。
https://stackoverflow.com/questions/42336546/xamarin-android-application-crashed-after-clear-data-in-settings
iPhoneでのデバッグは、権限を付与してから?
ちゃんと裏は取ってない。
iPhoneでデバッグ実行するとき、一度起動して、必要な権限はすべて許可しておかないと、デバッグ起動してもすぐにアプリが落ちる気がする。
他にもニッチなIT関連要素をまとめていますので、よければ一覧記事もご覧ください。