ネイティブ埋め込み

サンプルを参照します。 サンプルを参照する

通常、.NET Multi-platform App UI (.NET MAUI) アプリには、Grid などのレイアウトを含むページや、Button などのビューを含むレイアウトが含まれます。 ページ、レイアウト、ビューはすべて、Element から派生します。 ネイティブ埋め込みを使用すると、.NET for Android、.NET for iOS、.NET for Mac Catalyst、WinUI ネイティブ アプリで Element から派生するすべての .NET MAUI コントロールを使用できます。

ネイティブ アプリで .NET MAUI コントロールを使用するプロセスは次のとおりです。

  1. ネイティブ埋め込みアプリをブートストラップする拡張メソッドを作成します。 詳細については、「拡張メソッドの作成」を参照してください。
  2. .NET MAUI UI と依存関係を含む .NET MAUI 単一プロジェクトを作成します。 詳細については、「.NET MAUI 単一プロジェクトの作成」を参照してください。
  3. ネイティブ アプリを作成し、その中で .NET MAUI のサポートを有効にします。 詳細については、「.NET MAUI のサポート を有効にする」を参照してください。
  4. UseMauiEmbedding 拡張メソッドを呼び出して .NET MAUI を初期化します。 詳細については、「.NET MAUI の初期化」を参照してください。
  5. .NET MAUI UI を作成し、ToPlatformEmbedding 拡張メソッドを使用して適切なネイティブ型に変換します。 詳細については、「.NET MAUI コントロール の使用」を参照してください。

Note

ネイティブ埋め込みを使用しても、.NET MAUI のデータ バインディング エンジンは引き続き機能します。 ただし、ページ ナビゲーションはネイティブ ナビゲーション API を使用して実行する必要があります。

拡張メソッドを作成する

.NET MAUI コントロールを使用するネイティブ アプリを作成する前に、まず .NET MAUI クラス ライブラリ プロジェクトを作成し、Platforms フォルダーとその Class1 クラスを削除する必要があります。 次に、次のコードを含む EmbeddedExtensions という名前のクラスをそれに追加します:

using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Maui.Platform;

#if ANDROID
using PlatformView = Android.Views.View;
using PlatformWindow = Android.App.Activity;
using PlatformApplication = Android.App.Application;
#elif IOS || MACCATALYST
using PlatformView = UIKit.UIView;
using PlatformWindow = UIKit.UIWindow;
using PlatformApplication = UIKit.IUIApplicationDelegate;
#elif WINDOWS
using PlatformView = Microsoft.UI.Xaml.FrameworkElement;
using PlatformWindow = Microsoft.UI.Xaml.Window;
using PlatformApplication = Microsoft.UI.Xaml.Application;
#endif

namespace Microsoft.Maui.Controls;

public static class EmbeddedExtensions
{
    public static MauiAppBuilder UseMauiEmbedding(this MauiAppBuilder builder, PlatformApplication? platformApplication = null)
    {
#if ANDROID
        platformApplication ??= (Android.App.Application)Android.App.Application.Context;
#elif IOS || MACCATALYST
        platformApplication ??= UIKit.UIApplication.SharedApplication.Delegate;
#elif WINDOWS
        platformApplication ??= Microsoft.UI.Xaml.Application.Current;
#endif

        builder.Services.AddSingleton(platformApplication);
        builder.Services.AddSingleton<EmbeddedPlatformApplication>();
        builder.Services.AddScoped<EmbeddedWindowProvider>();

        // Returning null is acceptable here as the platform window is optional - but we don't know until we resolve it
        builder.Services.AddScoped<PlatformWindow>(svc => svc.GetRequiredService<EmbeddedWindowProvider>().PlatformWindow!);
        builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IMauiInitializeService, EmbeddedInitializeService>());
        builder.ConfigureMauiHandlers(handlers =>
        {
            handlers.AddHandler(typeof(Window), typeof(EmbeddedWindowHandler));
        });

        return builder;
    }

    public static IMauiContext CreateEmbeddedWindowContext(this MauiApp mauiApp, PlatformWindow platformWindow, Window? window = null)
    {
        var windowScope = mauiApp.Services.CreateScope();

#if ANDROID
        var windowContext = new MauiContext(windowScope.ServiceProvider, platformWindow);
#else
        var windowContext = new MauiContext(windowScope.ServiceProvider);
#endif

        window ??= new Window();

        var wndProvider = windowContext.Services.GetRequiredService<EmbeddedWindowProvider>();
        wndProvider.SetWindow(platformWindow, window);
        window.ToHandler(windowContext);

        return windowContext;
    }

    public static PlatformView ToPlatformEmbedded(this IElement element, IMauiContext context)
    {
        var wndProvider = context.Services.GetService<EmbeddedWindowProvider>();
        if (wndProvider is not null && wndProvider.Window is Window wnd && element is VisualElement visual)
            wnd.AddLogicalChild(visual);

        return element.ToPlatform(context);
    }

    private class EmbeddedInitializeService : IMauiInitializeService
    {
        public void Initialize(IServiceProvider services) =>
            services.GetRequiredService<EmbeddedPlatformApplication>();
    }
}

これらの拡張メソッドは Microsoft.Maui.Controls 名前空間内にあり、各プラットフォームでネイティブ埋め込みアプリをブートストラップするために使用されます。 拡張メソッドは、.NET MAUI ライブラリ プロジェクトにも追加する必要のある EmbeddedPlatformApplicationEmbeddedWindowHandlerEmbeddedWindowProvider 型を参照します。

次のコードは、EmbeddedPlatformApplication クラスを示しています。このクラスは、EmbeddedExtensions クラスと同じ .NET MAUI ライブラリ プロジェクトに追加する必要があります:

#if ANDROID
using PlatformApplication = Android.App.Application;
#elif IOS || MACCATALYST
using PlatformApplication = UIKit.IUIApplicationDelegate;
#elif WINDOWS
using PlatformApplication = Microsoft.UI.Xaml.Application;
#endif

namespace Microsoft.Maui.Controls;

internal class EmbeddedPlatformApplication : IPlatformApplication
{
    private readonly MauiContext rootContext;
    private readonly IMauiContext applicationContext;

    public IServiceProvider Services { get; }
    public IApplication Application { get; }

    public EmbeddedPlatformApplication(IServiceProvider services)
    {
        IPlatformApplication.Current = this;

#if ANDROID
        var platformApp = services.GetRequiredService<PlatformApplication>();
        rootContext = new MauiContext(services, platformApp);
#else
        rootContext = new MauiContext(services);
#endif

        applicationContext = MakeApplicationScope(rootContext);
        Services = applicationContext.Services;
        Application = Services.GetRequiredService<IApplication>();
    }

    private static IMauiContext MakeApplicationScope(IMauiContext rootContext)
    {
        var scopedContext = new MauiContext(rootContext.Services);
        InitializeScopedServices(scopedContext);
        return scopedContext;
    }

    private static void InitializeScopedServices(IMauiContext scopedContext)
    {
        var scopedServices = scopedContext.Services.GetServices<IMauiInitializeScopedService>();

        foreach (var service in scopedServices)
            service.Initialize(scopedContext.Services);
    }
}

次のコードは、EmbeddedWindowHandler クラスを示しています。このクラスは、EmbeddedExtensions クラスと同じ .NET MAUI ライブラリ プロジェクトに追加する必要があります:

using Microsoft.Maui.Handlers;

#if ANDROID
using PlatformWindow = Android.App.Activity;
#elif IOS || MACCATALYST
using PlatformWindow = UIKit.UIWindow;
#elif WINDOWS
using PlatformWindow = Microsoft.UI.Xaml.Window;
#endif

namespace Microsoft.Maui.Controls;

internal class EmbeddedWindowHandler : ElementHandler<IWindow, PlatformWindow>, IWindowHandler
{
    public static IPropertyMapper<IWindow, IWindowHandler> Mapper =
        new PropertyMapper<IWindow, IWindowHandler>(ElementHandler.ElementMapper)
        {
        };

    public static CommandMapper<IWindow, IWindowHandler> CommandMapper =
        new CommandMapper<IWindow, IWindowHandler>(ElementHandler.ElementCommandMapper)
        {
        };

    public EmbeddedWindowHandler() : base(Mapper)
    {
    }

    protected override PlatformWindow CreatePlatformElement() =>
        MauiContext!.Services.GetRequiredService<PlatformWindow>() ??
        throw new InvalidOperationException("EmbeddedWindowHandler could not locate a platform window.");
}

次のコードは、EmbeddedWindowProvider クラスを示しています。このクラスは、EmbeddedExtensions クラスと同じ .NET MAUI ライブラリ プロジェクトに追加する必要があります:

#if ANDROID
using PlatformWindow = Android.App.Activity;
#elif IOS || MACCATALYST
using PlatformWindow = UIKit.UIWindow;
#elif WINDOWS
using PlatformWindow = Microsoft.UI.Xaml.Window;
#endif

namespace Microsoft.Maui.Controls;

public class EmbeddedWindowProvider
{
    WeakReference<PlatformWindow?>? platformWindow;
    WeakReference<Window?>? window;

    public PlatformWindow? PlatformWindow => Get(platformWindow);
    public Window? Window => Get(window);

    public void SetWindow(PlatformWindow? platformWindow, Window? window)
    {
        this.platformWindow = new WeakReference<PlatformWindow?>(platformWindow);
        this.window = new WeakReference<Window?>(window);
    }

    private static T? Get<T>(WeakReference<T?>? weak) where T : class =>
        weak is not null && weak.TryGetTarget(out var target) ? target : null;
}

.NET MAUI 単一プロジェクトの作成

.NET MAUI コントロールを使用するネイティブ アプリを作成する前に、以前に作成した .NET MAUI クラス ライブラリ プロジェクトと同じソリューションに .NET MAUI アプリ プロジェクトを追加する必要があります。 .NET MAUI アプリ プロジェクトには、ネイティブ埋め込みアプリで再利用する予定の UI が格納されます。 ソリューションに新しい .NET MAUI アプリ プロジェクトを追加した後、次の手順を実行します:

  1. プロジェクトから [プロパティ] フォルダーを削除します。

  2. Platforms フォルダーをプロジェクトから削除します。

  3. プロジェクトから Resources/AppIcon フォルダーを削除します。

  4. プロジェクトから Resources/raw フォルダーを削除します。

  5. プロジェクトから Resources/Splash フォルダーを削除します。

  6. プロジェクトから AppShell クラスを削除します。

  7. App クラスを変更して、それで MainPage プロパティが設定されないようにします:

    public partial class App : Application
    {
        public App()
        {
            InitializeComponent();
        }
    }
    
  8. プロジェクトから MainPage クラスを削除します。

  9. プロジェクト ファイルを変更して、$(TargetFrameworks) ビルド プロパティが net8.0 に設定され、$(OutputType) ビルド プロパティが削除されるようにします:

    <PropertyGroup>
      <TargetFrameworks>net8.0</TargetFrameworks>
    
      <RootNamespace>MyMauiApp</RootNamespace>
      <UseMaui>true</UseMaui>
      <SingleProject>true</SingleProject>
      <ImplicitUsings>enable</ImplicitUsings>
      <Nullable>enable</Nullable>
    
      ...
    </PropertyGroup>
    
  10. CreateMauiApp メソッドが戻る前に呼び出される省略可能な Action<MauiAppBuilder> 引数を受け入れるように、MauiProgram クラス内のメソッドを変更します:

    public static MauiApp CreateMauiApp(Action<MauiAppBuilder>? additional = null)
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
            });
    
        #if DEBUG
            builder.Logging.AddDebug();
        #endif
    
        additional?.Invoke(builder);
        return builder.Build();
    }
    

この時点で、必要な .NET MAUI UI (依存関係やリソースを含む) をプロジェクトに追加し、プロジェクトが正しくビルドされていることを確認する必要があります。

.NET MAUI のサポートを有効にする

.NET for Android、.NET for iOS、.NET for Mac Catalyst、または WinUI アプリの Element から派生する .NET MAUI コントロールを使用するには、前に作成した .NET MAUI クラス ライブラリ プロジェクトと同じソリューションにネイティブ アプリ プロジェクトを追加する必要があります。 次に、プロジェクト ファイルの最初の <PropertyGroup> ノードで $(UseMaui) ビルド プロパティと $(MauiEnablePlatformUsings) ビルド プロパティを true に設定して、ネイティブ アプリのプロジェクト ファイルで .NET MAUI のサポートを有効にする必要があります:

<PropertyGroup>
    ...
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>

    <UseMaui>true</UseMaui>
    <MauiEnablePlatformUsings>true</MauiEnablePlatformUsings>  
</PropertyGroup>

.NET for Mac Catalyst アプリの場合は、$(SupportedOSPlatformVersion) ビルド プロパティを 14.0 以上に設定する必要もあります。

<PropertyGroup>
    ...
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>

    <SupportedOSPlatformVersion>14.2</SupportedOSPlatformVersion>
    <UseMaui>true</UseMaui>
    <MauiEnablePlatformUsings>true</MauiEnablePlatformUsings>  
</PropertyGroup>

WinUI アプリの場合は、$(EnableDefaultXamlItems) ビルド プロパティを false に設定する必要もあります:

<PropertyGroup>
    ...
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>

    <UseMaui>true</UseMaui>
    <MauiEnablePlatformUsings>true</MauiEnablePlatformUsings>    
    <EnableDefaultXamlItems>false</EnableDefaultXamlItems>
</PropertyGroup>

これにより、既に定義されている InitializeComponent メソッドに関するビルド エラーが受信されなくなります。

次に、$(PackageReference) ビルド項目を Microsoft.Maui.Controls NuGet パッケージと Microsoft.Maui.Controls.Compatiblity NuGet パッケージのプロジェクト ファイルに追加します:

<ItemGroup>
    <PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
    <PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" />
</ItemGroup>

.NET MAUI を初期化する

ネイティブ アプリ プロジェクトで .NET MAUI コントロールを構築する前に、.NET MAUI を初期化する必要があります。 それを初期化するタイミングの選択は、主にアプリ フローで最も便利なタイミングによって異なります。これは起動時または .NET MAUI コントロールが構築される直前に実行できます。 ここで説明する方法は、アプリの初期 UI が作成されたときに .NET MAUI を初期化することです。

通常、ネイティブ アプリ プロジェクトで .NET MAUI を初期化するパターンは次のとおりです:

  • MauiApp オブジェクトを作成します。
  • MauiApp オブジェクトから MauiContext オブジェクトを作成します。

Android では通常、MainActivity クラスの OnCreate オーバーライドが、アプリの起動に関連するタスクを実行する場所となります。 次のコード例は、MainActivity クラスで初期化される .NET MAUI を示します。

namespace MyNativeEmbeddedApp.Droid
{
    [Activity(Label = "@string/app_name", MainLauncher = true, Theme = "@style/AppTheme")]
    public class MainActivity : Activity
    {
        public static readonly Lazy<MauiApp> MauiApp = new(() =>
        {
            var mauiApp = MauiProgram.CreateMauiApp(builder =>
            {
                builder.UseMauiEmbedding();
            });
            return mauiApp;
        });

        public static bool UseWindowContext = true;

        protected override void OnCreate(Bundle? savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            // Ensure .NET MAUI app is built before creating .NET MAUI views
            var mauiApp = MainActivity.MauiApp.Value;

            // Create .NET MAUI context
            var mauiContext = UseWindowContext
                ? mauiApp.CreateEmbeddedWindowContext(this) // Create window context
                : new MauiContext(mauiApp.Services, this);  // Create app context

            ...              
        }
    }
}

iOS および Mac Catalyst では、AppDelegate クラスの FinishedLaunching オーバーライドを変更してメイン ビュー コントローラーを作成する必要があります:

namespace MyNativeEmbeddedApp.iOS
{
    [Register("AppDelegate")]
    public class AppDelegate : UIApplicationDelegate
    {
        public override UIWindow? Window { get; set; }

        public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
        {
            Window = new UIWindow(UIScreen.MainScreen.Bounds);
            var vc = new MainViewController();
            Window.RootViewController = vc;
            Window.MakeKeyAndVisible();
            return true;
        }
    }
}

.NET MAUI は、メイン ビュー コントローラーの ViewDidLoad メソッドで初期化できます:

using Microsoft.Maui.Platform;

namespace MyNativeEmbeddedApp.iOS
{
    public class MainViewController : UIViewController
    {
        UIWindow GetWindow() =>
            View?.Window ??
            ParentViewController?.View?.Window ??
            MainViewController.MauiApp.Value.Services.GetRequiredService<IUIApplicationDelegate>().GetWindow() ??
            UIApplication.SharedApplication.Delegate.GetWindow();

        public static readonly Lazy<MauiApp> MauiApp = new(() =>
        {
            var mauiApp = MauiProgram.CreateMauiApp(builder =>
            {
                builder.UseMauiEmbedding();
            });
            return mauiApp;
        });

        public static bool UseWindowContext = true;

        public override void ViewDidLoad()
        {
            base.ViewDidLoad();

            // Ensure app is built before creating .NET MAUI views
            var mauiApp = MainViewController.MauiApp.Value;

            // Create .NET MAUI context
            var mauiContext = UseWindowContext
                ? mauiApp.CreateEmbeddedWindowContext(GetWindow()) // Create window context
                : new MauiContext(mauiApp.Services);               // Create app context

            ...
        }
    }
}

Windows では、MainWindow クラスは通常、UI 関連のアプリのスタートアップ タスクを実行する場所です:

namespace MyNativeEmbeddedApp.WinUI
{
    public sealed partial class MainWindow : Microsoft.UI.Xaml.Window
    {
        public static readonly Lazy<MauiApp> MauiApp = new(() =>
        {
            var mauiApp = MauiProgram.CreateMauiApp(builder =>
            {
                builder.UseMauiEmbedding();
            });
            return mauiApp;
        });

        public static bool UseWindowContext = true;

        public MainWindow()
        {
            this.InitializeComponent();

            // Ensure .NET MAUI app is built before creating .NET MAUI views
            var mauiApp = MainWindow.MauiApp.Value;

            // Create .NET MAUI context
            var mauiContext = UseWindowContext
                ? mauiApp.CreateEmbeddedWindowContext(this) // Create window context
                : new MauiContext(mauiApp.Services);        // Create app context

            ...
        }
    }
}

この例では、MauiApp オブジェクトは遅延初期化を使用して作成されます。 UseMauiEmbedding 拡張メソッドが MauiAppBuilder オブジェクトで呼び出されます。 そのため、ネイティブ アプリ プロジェクトには、この拡張メソッドを含む作成した .NET MAUI クラス ライブラリ プロジェクトへの参照を含める必要があります。 その後、MauiContext オブジェクトから MauiApp オブジェクトが生成され、bool がコンテキストのスコープ元を決定します。 MauiContext オブジェクトは、.NET MAUI コントロールをネイティブ型に変換するときに使用します。

.NET MAUI コントロールを使用する

ネイティブ アプリで .NET MAUI が初期化されたら、ネイティブ アプリのレイアウトに .NET MAUI UI を追加できます。 これは、UI のインスタンスを作成し、ToPlatformEmbedded 拡張メソッドを使用して適切なネイティブ型に変換することで実現できます。

Android では、ToPlatformEmbedded 拡張メソッドは .NET MAUI コントロールを Android View オブジェクトに変換します:

var mauiView = new MyMauiContent();
Android.Views.View nativeView = mauiView.ToPlatformEmbedded(mauiContext);

この例では、ContentView から派生したオブジェクトが Android View オブジェクトに変換されます。

Note

ToPlatformEmbedded 拡張メソッドは、前に作成した .NET MAUI クラス ライブラリにあります。 そのため、ネイティブ アプリ プロジェクトには、そのプロジェクトへの参照を含める必要があります。

その後、View オブジェクトをネイティブ アプリのレイアウトに追加できます:

rootLayout.AddView(nativeView, new LinearLayout.LayoutParams(MatchParent, WrapContent));

iOS および Mac Catalyst では、ToPlatformEmbedded 拡張メソッドは .NET MAUI コントロールを UIView オブジェクトに変換します:

var mauiView = new MyMauiContent();
UIView nativeView = mauiView.ToPlatformEmbedded(mauiContext);
nativeView.WidthAnchor.ConstraintEqualTo(View.Frame.Width).Active = true;
nativeView.HeightAnchor.ConstraintEqualTo(500).Active = true;

この例では、ContentView 派生オブジェクトが UIView オブジェクトに変換されてから、やりとりを可能にするためにそのオブジェクトに幅と高さの制約が設定されます。

Note

ToPlatformEmbedded 拡張メソッドは、前に作成した .NET MAUI クラス ライブラリにあります。 そのため、ネイティブ アプリ プロジェクトには、そのプロジェクトへの参照を含める必要があります。

その後、UIView オブジェクトをビュー コントローラーのビューに追加できます:

stackView.AddArrangedSubView(nativeView);

さらに、.NET MAUI の ToUIViewController 拡張メソッドを使用して、.NET MAUI ページをUIViewController に変換できます:

MyMauiPage myMauiPage = new MyMauiPage();
UIViewController myPageController = myMauiPage.ToUIViewController(mauiContext);

この例では、ContentPage から派生したオブジェクトを UIViewController に変換しています。

Windows では、ToPlatformEmbedded 拡張メソッドは .NET MAUI コントロールを FrameworkElement オブジェクトに変換します:

var mauiView = new MyMauiContent();
FrameworkElement nativeView = myMauiPage.ToPlatformEmbedded(mauiContext);

この例では、ContentView から派生したオブジェクトが FrameworkElement オブジェクトに変換されます。 その後、FrameworkElement オブジェクトを WinUI ページのコンテンツとして設定できます。

その後、FrameworkElement オブジェクトをネイティブ アプリのレイアウトに追加できます:

stackPanel.Children.Add(nativeView);

重要

エラーが発生しないようにするには、デバッグ構成でネイティブ埋め込みアプリを実行する前に、XAML ホット リロードを無効にする必要があります。