.NET 6の現状を把握し、具体的な移行方法を学ぶ連載。今回は、.NET MAUIの概要とXamarn.Formsからの改良ポイントについてまとめる。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
2022年5月23日(米国時間)、.NET MAUIがGA(一般提供)されました。Microsoftは「.NET MAUIは、.NET Multi-platform App UIの略称で、モバイル、タブレット、デスクトップにまたがるネイティブデバイスのアプリケーションを構築するフレームワークです」と紹介しています。
.NET MAUI自体はGAされましたが、2022年6月26日の原稿執筆では開発環境「Visual Studio」は、Windows版も、Mac版も最新のプレビュー版での対応です。こちらは程なく安定版のVisual Studioでも対応されるでしょう。
.NET MAUIはXamarin.Formsから全く違う製品名に変わりましたが、何が違うのでしょうか? 分かりやすくいえば、.NET MAUIはXamarin.Formsの改良版です。本稿では、.NET MAUIはXamarin.Formsと比べて、どこが改良されたのか、具体的に解説します。
Xamarin.Formsと.NET MAUIの主な違いを表でまとめました。
Xamarin.Forms | .NET MAUI | |
---|---|---|
プロジェクトの構造 | 非SDKスタイル(Franken-proj) プラットフォームごとに個別のプロジェクトを使用 |
SDKスタイル 単一のプロジェクトで、複数プラットフォームをターゲットにできる |
ツールチェーン | .NET Framework | .NET CLI |
リソース管理 | プラットフォームごとに個別管理 プラットフォーム固有のデバイスの解像度に応じたイメージを準備する必要がある |
単一のプロジェクト内で一元管理 SVGを準備すれば各プラットフォームの解像度のPNGに変換でされる |
スタートアップ | 独自(Appクラス) | Generic Host対応 |
ホットリロード | 完全なXAMLホットリロード (SDK 5.xおよびVisual Studio 2019 16.9以降) |
完全な.NETホットリロード 完全なXAMLホットリロード |
UIコントロールアーキテクチャ | レンダラー | ハンドラー |
以降、.NET MAUIの改良点の中で、特に注目のポイントを紹介します。
.NET MAUIのプロジェクトは下図のようになっています。
Xamarin.Formsのような「単一の共有プロジェクト+複数のプラットフォーム固有プロジェクト」の構成ではなく、単一のプロジェクトで、複数プラットフォームをターゲットにできるようになりました。
Xamarin.Formsは登場当時、画期的なフレームワークでした。ですが、利用が進むにつれてアーキテクチャに起因する問題点も顕在化しました。
下図のようにレンダラーは共有プロジェクトのUIコントロール実装に依存しています。
Xamarin.Formsからの最大の変更点は、レンダラーアーキテクチャからハンドラーアーキテクチャへの変更です。ハンドラーアーキテクチャの採用によってプラットフォーム固有のレンダリングの責務を、抽象化UIフレームワークの実装から分離しました。
下図のようにハンドラーはインタフェースにのみ依存し、抽象化UIコントロールの実装には依存しません。
また、共有コード内で複数のプラットフォームのネイティブUIコントロールを直接操作できます。
これによって、以下に述べるようなアドバンテージが生まれました。
Generic Hostは、アプリの起動やシャットダウンのようなライフタイム管理や、アプリの構成やロギング、DI(Dependency Injection:依存性の注入)といった、アプリのビジネスロジック自体とは関係ない、基盤となる機能をカプセル化するオブジェクトです。ビジネスロジックとアプリの基盤に関わる機能を分離できるので、クリーンな構造にすることができます。
Generic Hostの詳細はこちらをご覧ください。
.NET MAUIではGeneric Hostに対応したので、スタートアップの処理がXamarin.Formsと大きく異なります。ここからは、.NET MAUIのスタートアップコードのサンプルを見ていきます。なお以下のコードは、説明するために調整されたものです。ベストプラクティスではないのでご注意ください。
まずは、コード全体です。Generic Host内では、次の4つを行っています。
スタートアップコードはMauiProgram.csに記述します。
using MauiUICustomizeSample.Controls; using Microsoft.Maui.Handlers; using Microsoft.Maui.Platform; namespace MauiUICustomizeSample; public static class MauiProgram { public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<App>() // 起動するアプリクラスの指定 .ConfigureFonts(fonts => // リソースフォルダ内のフォントの登録 { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); }); // Dependency injection : AppSlell クラスをDIコンテナに登録 builder.Services.AddTransient<AppShell>(); // 全てのLabelのカスタマイズ LabelHandler.Mapper.AppendToMapping(nameof(IView.Background), (handler, view) => { if (view is Label) { #if IOS handler.PlatformView.BackgroundColor = Colors.MediumSpringGreen.ToPlatform(); #elif ANDROID handler.PlatformView.SetBackgroundColor(Colors.MediumSpringGreen.ToPlatform()); #endif } }); // 特定のインスタンスのButtonのカスタマイズ ButtonHandler.Mapper.AppendToMapping(nameof(IView.Background), (handler, view) => { if (view is MyButton) { #if IOS handler.PlatformView.BackgroundColor = Colors.LightCoral.ToPlatform(); handler.PlatformView.SetTitleColor(Colors.White.ToPlatform(), UIKit.UIControlState.Normal); handler.PlatformView.Layer.CornerRadius = 7; #elif ANDROID handler.PlatformView.SetBackgroundColor(Colors.LightCoral.ToPlatform()); #endif } }); return builder.Build(); } }
個別の処理について詳説します。
・MAUI用のGeneric Hostのビルダーを作成
MauiAppのCreateBuilderメソッドでMauiAppBuilderのインスタンスが作成されます。
var builder = MauiApp.CreateBuilder();
・起動するアプリクラス(このサンプルではAppクラス)を指定
MauiAppBuilderのUseMauiAppメソッドの型パラメーターに起動するアプリクラスの型(App)を指定します。
builder.UseMauiApp<App>()
AppクラスはApp.xaml.csで定義されています。
namespace MauiUICustomizeSample; public partial class App : Application { public App(AppShell appShell) { InitializeComponent(); MainPage = appShell; } }
・フォントの登録
プロジェクト内の「/Resources/Fonts」内に配置されたフォントを登録します。
MauiAppBuilderのConfigureFontsメソッド内のデリゲートで登録します。
.ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); });
・AppSlellクラスをDIコンテナに登録
AppSlellクラスをDIコンテナに登録し、インスタンスが注入されるようにします。
builder.Services.AddTransient<AppShell>();
これによって、App.xaml.csのコンストラクタのappShellパラメーターにインスタンスが注入されます。
namespace MauiUICustomizeSample; public partial class App : Application { public App(AppShell appShell) { InitializeComponent(); MainPage = appShell; } }
・ハンドラーを利用したUIのカスタマイズ
サンプルでは、「特定の種類のUIコントロール全てをカスタマイズする場合」「あるUIコントロールの特定のインスタンスのみをカスタマイズする場合」の2つのパターンでUIをカスタマイズしています。両者とも、Xamarin.Formsと比較して簡単にカスタマイズできます。
・特定の種類のUIコントロール全てをカスタマイズする場合
Labelコントロール全ての背景色をMediumSpringGreenに変更します。
// 全てのLabelのカスタマイズ LabelHandler.Mapper.AppendToMapping(nameof(IView.Background), (handler, view) => { if (view is Label) { #if IOS handler.PlatformView.BackgroundColor = Colors.MediumSpringGreen.ToPlatform(); #elif ANDROID handler.PlatformView.SetBackgroundColor(Colors.MediumSpringGreen.ToPlatform()); #endif } });
AppendToMappingのActionデリゲート内でUIコントロールの型がLabelの場合、カスタマイズしたい色を設定します。handler.PlatformViewには、iOSの場合はUILabelが、Androidの場合はAppCompatTextViewが割り当てられています。そのため、それぞれのネイティブUIコントロールのプロパティを変更することで、色も変更できます。設定する色はToPlatformメソッドによって、それぞれのプラットフォームのネイティブカラーに変換されます。
ここで注目なのは、たった数行のコードで共有コード内のUILabel.BackgroundColorや、AppCompatTextView.SetBackgroundColorといったプロパティやメソッドが使用できることです。これをXamarin.Formsでするには、各プラットフォームプロジェクト内でクラスを定義し定型的なコードを書く必要があったので、簡単になっているのが分かります。
・あるUIコントロールの特定のインスタンスのみをカスタマイズする場合
こちらでは、Buttonコントロール特定のインスタンスの背景色をLightCoralに変更します。
ButtonのサブクラスMyButtonを作成します。
namespace MauiUICustomizeSample.Controls { public class MyButton : Button { public MyButton() { } } }
MauiProgram.csでMyButtonに対してカスタマイズを適用します。
// 特定のインスタンスの Button のカスタマイズ ButtonHandler.Mapper.AppendToMapping(nameof(IView.Background), (handler, view) => { if (view is MyButton) { #if IOS handler.PlatformView.BackgroundColor = Colors.LightCoral.ToPlatform(); handler.PlatformView.SetTitleColor(Colors.White.ToPlatform(), UIKit.UIControlState.Normal); handler.PlatformView.Layer.CornerRadius = 7; #elif ANDROID handler.PlatformView.SetBackgroundColor(Colors.LightCoral.ToPlatform()); #endif } });
AppendToMappingのActionデリゲート内で、UIコントロールの型がMyButtonの場合、カスタマイズしたい色を設定します。
handler.PlatformViewには、iOSの場合はUIButtonが、Androidの場合はMaterialButtonが割り当てられるので、それぞれのネイティブUIコントロールのプロパティを変更することで、色を変更できます。設定する色はToPlatformメソッドによって、それぞれのプラットフォームのネイティブカラーに変換されます。
カスタマイズしたコントロールをXAML内で使用するには、「xmlns:button="clr-namespace:MauiUICustomizeSample.Controls"」のように名前空間を指定します。
MyButtonのみ背景色がカスタマイズされます。
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:button="clr-namespace:MauiUICustomizeSample.Controls" x:Class="MauiUICustomizeSample.MainPage"> <ScrollView> <VerticalStackLayout Spacing="25" Padding="30,0" VerticalOptions="Center"> <Image Source="dotnet_bot.png" SemanticProperties.Description="Cute dot net bot waving hi to you!" HeightRequest="200" HorizontalOptions="Center" /> <Label Text="Hello, MAUI!" SemanticProperties.HeadingLevel="Level1" FontSize="32" HorizontalOptions="Center" /> <Label Text="Welcome to.NET Multi-platform App UI" SemanticProperties.HeadingLevel="Level2" SemanticProperties.Description="Welcome to dot net Multi platform App UI" FontSize="16" HorizontalOptions="Center" /> <HorizontalStackLayout Spacing="10" HorizontalOptions="Center"> <Button x:Name="CounterBtn" WidthRequest="120" Text="Click me" SemanticProperties.Hint="Counts the number of times you click" Clicked="OnCounterClicked" HorizontalOptions="Start" /> <button:MyButton x:Name="CustomizedBtn" WidthRequest="120" Text="Customized" HorizontalOptions="End" /> </HorizontalStackLayout> </VerticalStackLayout> </ScrollView> </ContentPage>
iOSでの実行結果は、下図のようになります。ラベルの全体の背景色と、右側のボタンの背景色がカスタマイズされています。
ハンドラーを使用したコントロールのカスタマイズの詳細は、こちらをご覧ください。
本稿で説明したように、.NET MAUIでは、Xamarin.Formsの面倒だった部分が扱いやすくなり、直感的でシンプルなコードで記述できるように改善されました。また、アセンブリスキャンやリフレクションなど、コストの高い処理を回避することで起動速度が向上しました。つまり「.NET MAUI=よりシンプルで使いやすくなったXamarin.Forms」といえます。
.NET MAUIは今後も強化され続けるとアナウンスされています。まずは強力に生まれ変わった.NET MAUIを体験してみてはいかがでしょうか。
Copyright © ITmedia, Inc. All Rights Reserved.