「.NET MAUI(Multi-platform App UI)」とは――Xamarn.Formsからの改良ポイント.NET 6移行入門(6)

.NET 6の現状を把握し、具体的な移行方法を学ぶ連載。今回は、.NET MAUIの概要とXamarn.Formsからの改良ポイントについてまとめる。

» 2022年07月28日 05時00分 公開
[鈴木友宏NTTデータ先端技術株式会社]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

.NET MAUIリリース

 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の主な違いを表でまとめました。

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のプロジェクト構成

 .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は、アプリの起動やシャットダウンのようなライフタイム管理や、アプリの構成やロギング、DI(Dependency Injection:依存性の注入)といった、アプリのビジネスロジック自体とは関係ない、基盤となる機能をカプセル化するオブジェクトです。ビジネスロジックとアプリの基盤に関わる機能を分離できるので、クリーンな構造にすることができます。

 Generic Hostの詳細はこちらをご覧ください。


.NET MAUIのスタートアップコードの例

 .NET MAUIではGeneric Hostに対応したので、スタートアップの処理がXamarin.Formsと大きく異なります。ここからは、.NET MAUIのスタートアップコードのサンプルを見ていきます。なお以下のコードは、説明するために調整されたものです。ベストプラクティスではないのでご注意ください。

 まずは、コード全体です。Generic Host内では、次の4つを行っています。

  • 起動するアプリクラスの指定
  • フォントの登録
  • Dependency Injection
  • ハンドラーを利用したUIのカスタマイズ

 スタートアップコードは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();
    }
}
MauiProgram.cs

 個別の処理について詳説します。

・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;
    }
}
App.xaml.cs

・フォントの登録

 プロジェクト内の「/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;
    }
}
App.xaml.cs

・ハンドラーを利用した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
            }
        });
MauiProgram.cs

 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()
        {
        }
    }
}
MyButton.cs

 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
            }
        });
MauiProgram.cs

 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>
MainPage.xaml

 iOSでの実行結果は、下図のようになります。ラベルの全体の背景色と、右側のボタンの背景色がカスタマイズされています。

 ハンドラーを使用したコントロールのカスタマイズの詳細は、こちらをご覧ください。

まとめ

 本稿で説明したように、.NET MAUIでは、Xamarin.Formsの面倒だった部分が扱いやすくなり、直感的でシンプルなコードで記述できるように改善されました。また、アセンブリスキャンやリフレクションなど、コストの高い処理を回避することで起動速度が向上しました。つまり「.NET MAUI=よりシンプルで使いやすくなったXamarin.Forms」といえます。

 .NET MAUIは今後も強化され続けるとアナウンスされています。まずは強力に生まれ変わった.NET MAUIを体験してみてはいかがでしょうか。

Copyright © ITmedia, Inc. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。