目次
- 目次
- .NET MAUI リリース
- Xamarin.Forms と .NET MAUIの主な違い
- .NET MAUI のプロジェクト構成
- Xamarin.Forms のアーキテクチャーに起因する問題点
- .NET MAUI の最大の変更点
- .NET MAUI のスタートアップコードの例
- まとめ
.NET MAUI リリース
2022年5月23日(現地時間)ついに .NET MAUI が GAされました。Microsoft は「.NET MAUI は、.NET Multi-platform App UI の略称で、モバイル、タブレット、デスクトップにまたがるネイティブデバイスのアプリケーションを構築するためのフレームワークです。」と紹介しています。
2022年6月26日現在では、残念なことに .NET MAUI 自体は GA されたのですが、開発環境である 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 の主な違いをまとめてみました。色々違いがありますが、.NET 6 対応になりさまざまな点で改良されています。
Xamarin.Forms | .NET MAUI | |
プロジェクトの構造 | 非 SDK スタイル(Franken-proj) プラットフォームごとに個別のプロジェクトを使用 |
SDK スタイル 単一のプロジェクトで、複数プラットフォームをターゲットにできる |
ツールチェーン | .NETFramework | .NET CLI |
リソース管理 | プラットフォームごとに個別管理 プラットフォーム固有のデバイスの解像度に応じたイメージを準備する必要がある |
単一のプロジェクト内で一元管理 SVG を準備すれば各プラットフォームの解像度のPNGに変換でされる |
スタートアップ | 独自(App クラス) | Generic Host 対応 |
ホットリロード | 完全な XAML ホットリロード (SDK5.x および Visual Studio 2019 16.9以降) |
完全な .NET ホットリロード 完全な XAML ホットリロード |
UI コントロールアーキテクチャ | レンダラー | ハンドラー |
次に、.NET MAUI の改良点の中で特に注目したいポイントをご紹介します。
.NET MAUI のプロジェクト構成
.NET MAUI のプロジェクトは以下のようになっています。Xamarin.Forms のような「単一の共有プロジェクト + 複数のプラットフォーム固有プロジェクト」の構成ではなく、単一のプロジェクトで、複数プラットフォームをターゲットにできるようになりました。
Xamarin.Forms のアーキテクチャーに起因する問題点
Xamarin.Forms は登場当時、画期的なフレームワークでした。ですが、利用が進むにつれてアーキテクチャーに起因する問題点も顕在化していきました。
Xamarin.Forms のレンダラーアーキテクチャー
以下の図のようにレンダラーは共有プロジェクトの UI コントロール実装に依存しています。
Xamarin.Forms レンダラーアーキテクチャーの問題点
- レンダラーが Xamarin.Forms の UI コンポーネントと密結合している。
- アセンブリスキャンとリフレクションというコストの大きい処理を使用して UI コントロールのレンダラーを取得するため遅い。
- 共有プロジェクトからネイティブ UI コントロールをカスタマイズする場合、たった1つのプロパティの変更であっても、共有プロジェクトとネイティブプロジェクトにまたがるレンダリングの仕組みを理解した上で、定型的な多くのコードを記述する必要があり、非常に手間がかかる。
.NET MAUI の最大の変更点
Xamarin.Forms からの最大の変更点は、レンダラーアーキテクチャーからハンドラーアーキテクチャーへの変更です。ハンドラーアーキテクチャの採用によってプラットフォーム固有のレンダリングの責務を、抽象化 UI フレームワークの実装から分離しました。
.NET MAUI で導入されたハンドラーアーキテクチャー
以下の図のようにハンドラーはインターフェースにのみ依存し、抽象化 UI コントロールの実装には依存しません。
また、共有コード内で複数のプラットフォームのネイティブ UI コントロールを直接操作できます。複数プラットフォーム対応のために条件付きコンパイルを使用しなければならないのは美しくないですが、残念ながらこれより良い手段は現在のところありません。
これによって以下に述べるようなアドバンテージが生まれました。
.NET MAUI ハンドラーアーキテクチャーのアドバンテージ
- ハンドラーはアセンブリスキャンが不要となり速度が向上した。
- 共有コード内でもでもネイティブ UI コントロールに直接アクセスしてプロパティを変更できる。
- ハンドラーは、スタートアップコードの Generic Host 内に直接記述も可能で、簡単に実装および使用できるため、特定のコントロールまたはアプリ全体で使用されるすべてのコントロールのカスタマイズが簡単に実装できる。
(参考)Generic Host
Generic Host は、アプリの起動やシャットダウンのようなライフタイム管理やアプリの構成やロギング、DIのようなアプリのビジネスロジック自体とは関係ない基盤となる機能をカプセル化するオブジェクトです。これによって、アプリの基盤に関わる機能とビジネスロジックを分離できクリーンな構造になります。
Generic Host の詳細はこちらをご覧ください。
docs.microsoft.com
.NET MAUI のスタートアップコードの例
.NET MAUI では Generic Host に対応したため、スタートアップの処理が Xamarin.Forms とは大きく異なっています。
では、.NET MAUI のスタートアップコードのサンプルを見ていきましょう。
なお、以下コードは説明のために調整されたものですので、ベストプラクティスではありませんのでご注意ください。
まずは、コード全体を見ていきましょう。
Generic Host 内では、
- 起動するアプリクラスの指定
- フォントの登録
- Dependency injection
- ハンドラーを利用した UI のカスタマイズ
を行なっています。
スタートアップコードはMauiProgram.cs
に記述します。
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
で定義されています。
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
パラメータにインスタンスが注入されます。
App.xaml.cs
namespace MauiUICustomizeSample; public partial class App : Application { public App(AppShell appShell) { InitializeComponent(); MainPage = appShell; } }
ハンドラーを利用した UI のカスタマイズ
サンプルでは、「特定の種類の UI コントロール全てをカスタマイズする場合」と「ある UI コントロールの特定のインスタンスのみをカスタマイズする場合」の2つのパターンの UI カスタマイズを行なっています。両者とも Xamarin.Forms の場合と比較して驚くほど簡単にカスタマイズができるようになっています。
特定の種類の UI コントロール全てをカスタマイズする場合
こちらでは、Label
コントロール全ての背景色をMediumSpringGreen
に変更します。
MauiProgram.cs
// 全ての 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
を作成します。
MyButton.cs
namespace MauiUICustomizeSample.Controls { public class MyButton : Button { public MyButton() { } } }
MauiProgram.cs
でMyButton
に対してカスタマイズを適用します。
MauiProgram.cs
// 特定のインスタンスの 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
のみ背景色がカスタマイズされます。
MainPage.xaml
<?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 での実行結果は以下のようになります。
ラベルの全体の背景色と、右側のボタンの背景色がカスタマイズされています。
ハンドラーを使用したコントロールのカスタマイズの詳細はこちらをご覧ください。
docs.microsoft.com
まとめ
これまでご説明しましたように、.NET MAUI では、Xamarin.Forms の面倒だった部分が非常に扱いやすくなり、直感的でシンプルなコードで記述できるよう改善されました。また、アセンブリスキャンやリフレクションなどコストの高い処理を回避することで起動速度も向上しています。
つまり「 .NET MAUI = よりシンプルで使いやすくなった Xamarin.Forms」と言えます。というわけで、早速使ってみましょう!