Incorporação nativa

Procurar exemplo. Procurar no exemplo

Normalmente, um aplicativo do .NET Multi-platform App UI (.NET MAUI) inclui páginas que contêm layouts, como Grid, e layouts que contêm exibições, como Button. Páginas, layouts e exibições derivam de Element. A inserção nativa permite que os controles do .NET MAUI que derivam de Element sejam consumidos em aplicativos nativos .NET para Android, .NET para iOS, .NET para Mac Catalyst e WinUI.

O processo de consumo de um controle MAUI DO .NET em um aplicativo nativo se dá da seguinte forma:

  1. Crie métodos de extensão para inicializar seu aplicativo nativo inserido. Para obter mais informações, consulte Criar métodos de extensão.
  2. Crie um único projeto do MAUI do .NET que contenha sua interface do usuário do MAUI do .NET e quaisquer dependências. Para obter mais informações, consulte Criar um projeto único do MAUI do .NET.
  3. Crie um aplicativo nativo e habilite o suporte ao MAUI do .NET nele. Para obter mais informações, consulte a Política de suporte do MAUI do .NET.
  4. Inicialize o MAUI do .NET chamando o método de extensão UseMauiEmbedding. Para obter mais informações, consulte Inicializar o MAUI do .NET.
  5. Crie a interface de usuário do MAUI do .NET e converta-a no tipo nativo apropriado com o método de extensão ToPlatformEmbedding. Para obter mais informações, consulte Consumir os controles do MAUI do .NET.

Observação

Ao usar a inserção nativa, o mecanismo de associação de dados do MAUI do .NET ainda funciona. Porém, a navegação de página deve ser executada usando a API de navegação nativa.

Criar métodos de extensão

Antes de criar um aplicativo nativo que consome os controles do MAUI do .NET, primeiro você deve criar um projeto de biblioteca de classes do MAUI do .NET e excluir a pasta Plataformas e a classe Class1 dele. Em seguida, adicione uma classe a ela chamada EmbeddedExtensions que contém o seguinte código:

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>();
    }
}

Esses métodos de extensão localizam-se no namespace Microsoft.Maui.Controls e são usados para inicializar seu aplicativo nativo inserido em cada plataforma. Os métodos de extensão referenciam os tipos EmbeddedPlatformApplication, EmbeddedWindowHandlere EmbeddedWindowProvider que você também deve adicionar ao projeto de biblioteca do MAUI do .NET.

O código a seguir mostra a classe EmbeddedPlatformApplication, que deve ser adicionada ao mesmo projeto de biblioteca do MAUI do .NET que a classe EmbeddedExtensions:

#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);
    }
}

O código a seguir mostra a classe EmbeddedWindowHandler, que deve ser adicionada ao mesmo projeto de biblioteca do MAUI do .NET que a classe EmbeddedExtensions:

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

O código a seguir mostra a classe EmbeddedWindowProvider, que deve ser adicionada ao mesmo projeto de biblioteca do MAUI do .NET que a classe EmbeddedExtensions:

#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;
}

Criar um projeto único do MAUI do .NET

Antes de criar um aplicativo nativo que consome os controles do MAUI do .NET, você deve adicionar um projeto de aplicativo do MAUI do .NET à mesma solução que o projeto de biblioteca de classes do MAUI do .NET criado anteriormente. O projeto de aplicativo do MAUI do .NET armazenará a interface do usuário que você pretende reusar no seu aplicativo nativo inserido. Depois de adicionar um novo projeto de aplicativo do MAUI do .NET à solução, execute as seguintes etapas:

  1. Exclua a pasta Propriedades do projeto.

  2. Exclua a pasta Plataformas do projeto.

  3. Exclua a pasta Recursos/AppIcon do projeto.

  4. Exclua a pasta Recursos/bruto do projeto.

  5. Exclua a pasta Recursos/Splash do projeto.

  6. Exclua a classe AppShell do projeto.

  7. Modifique a classe App para que ela não defina a propriedade MainPage:

    public partial class App : Application
    {
        public App()
        {
            InitializeComponent();
        }
    }
    
  8. Exclua a classe MainPage do projeto.

  9. Modifique o arquivo do projeto para que a propriedade de compilação $(TargetFrameworks) seja definida como net8.0, e a propriedade de compilação $(OutputType) seja removida:

    <PropertyGroup>
      <TargetFrameworks>net8.0</TargetFrameworks>
    
      <RootNamespace>MyMauiApp</RootNamespace>
      <UseMaui>true</UseMaui>
      <SingleProject>true</SingleProject>
      <ImplicitUsings>enable</ImplicitUsings>
      <Nullable>enable</Nullable>
    
      ...
    </PropertyGroup>
    
  10. Modifique o método CreateMauiApp na classe MauiProgram para que ele aceite um argumento Action<MauiAppBuilder> opcional que é invocado antes que o método retorne:

    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();
    }
    

Neste ponto, você deve adicionar a interface do usuário do MAUI do .NET necessária ao projeto, incluindo dependências e recursos, e garantir que o projeto seja compilado corretamente.

Habilitar o suporte ao MAUI do .NET

Para consumir controles do .NET MAUI que derivam de Element em um aplicativo .NET para Android, .NET para iOS, .NET para Mac Catalyst ou WinUI, adicione seu projeto de aplicativo nativo à mesma solução do projeto de biblioteca de classes .NET MAUI criado anteriormente. Em seguida, você deve habilitar o suporte ao MAUI do .NET no arquivo de projeto do aplicativo nativo definindo as propriedades de compilação $(UseMaui) e $(MauiEnablePlatformUsings) para true no primeiro nó <PropertyGroup> no arquivo de projeto:

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

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

Quanto aos aplicativos .NET para Mac Catalyst, você também precisará definir a propriedade de build $(SupportedOSPlatformVersion) como, no mínimo, 14.0:

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

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

Quanto aos aplicativos WinUI, você também precisará definir a propriedade de compilação $(EnableDefaultXamlItems) como false:

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

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

Isso impedirá que você receba erros de compilação sobre o método InitializeComponent que já está sendo definido.

Em seguida, adicione os itens de compilação $(PackageReference) ao arquivo de projeto nos pacotes NuGet Microsoft.Maui.Controls e Microsoft.Maui.Controls.Compatiblity:

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

Inicializar o MAUI do .NET

O MAUI do .NET deve ser inicializado antes que um projeto de aplicativo nativo possa construir um controle do MAUI do .NET. Escolher quando inicializá-lo depende principalmente de quando ele é mais conveniente no fluxo do aplicativo – ele pode ser executado na inicialização ou pouco antes de um controle do MAUI do .NET ser construído. A abordagem descrita aqui é inicializar o MAUI do .NET quando a interface do usuário inicial do aplicativo é criada.

Normalmente, o padrão para inicializar o MAUI do .NET em um projeto de aplicativo nativo se dá da seguinte forma:

No Android, a substituição OnCreate na classe MainActivity normalmente é o local para executar tarefas relacionadas à inicialização do aplicativo. O exemplo de código a seguir mostra o MAUI do .NET sendo inicializado na classe MainActivity:

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

            ...              
        }
    }
}

No iOS e no Mac Catalyst, a substituição de FinishedLaunching na classe AppDelegate deve ser modificada para criar seu controlador de exibição principal:

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;
        }
    }
}

O MAUI do .NET pode ser inicializado no método ViewDidLoad no seu controlador de exibição principal:

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

            ...
        }
    }
}

No Windows, a classe MainWindow normalmente é o local para executar as tarefas de inicialização de aplicativo relacionadas à interface do usuário:

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

            ...
        }
    }
}

Neste exemplo, o objeto MauiApp é criado usando a inicialização lenta. O método de extensão UseMauiEmbedding é invocado no objeto MauiAppBuilder. Portanto, seu projeto de aplicativo nativo deve incluir uma referência ao projeto de biblioteca de classes do MAUI do .NET que você criou contendo esse método de extensão. Assim, um objeto MauiContext é criado no objeto MauiApp, com um determinando bool, do local de onde o contexto tem escopo. O objeto MauiContext será usado ao converter controles os MAUI do .NET nos tipos nativos.

Consumir os controles do MAUI do .NET

Depois que o MAUI do .NET tiver sido inicializado no seu aplicativo nativo, você poderá adicionar a interface do usuário do MAUI do .NET ao layout do aplicativo nativo. Isso pode ser feito criando uma instância da interface do usuário e convertendo-a no tipo nativo apropriado com o método de extensão ToPlatformEmbedded.

No Android, o método de extensão ToPlatformEmbedded converte o controle do MAUI do .NET em um objeto Android View:

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

Neste exemplo, um objeto derivado de ContentView é convertido em um objeto Android View.

Observação

O método de extensão ToPlatformEmbedded se localiza na biblioteca de classes do MAUI do .NET que você criou anteriormente. Por isso, seu projeto de aplicativo nativo deve incluir uma referência a esse projeto.

O objeto View pode ser adicionado a um layout no seu aplicativo nativo:

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

No iOS e no Mac Catalyst, o método de extensão ToPlatformEmbedded converte o controle do MAUI do .NET em um objeto UIView:

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

Neste exemplo, um objeto derivado de ContentView é convertido em um objeto UIView.

Observação

O método de extensão ToPlatformEmbedded se localiza na biblioteca de classes do MAUI do .NET que você criou anteriormente. Por isso, seu projeto de aplicativo nativo deve incluir uma referência a esse projeto.

O objeto UIView pode ser adicionado a uma exibição no controlador de exibição:

stackView.AddArrangedSubView(nativeView);

Aviso

No momento, não é possível interagir com a interface do usuário do MAUI do .NET no iOS e no Mac Catalyst. Para obter mais informações, consulte Problema do GitHub nº 19340.

Além disso, um método de extensão ToUIViewController no MAUI do .NET pode ser usado para tentar converter uma página do MAUI do .NET em um UIViewController:

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

Neste exemplo, um objeto derivado de ContentPage é convertido em um UIViewController.

No Windows, o método de extensão ToPlatformEmbedded converte o controle do MAUI do .NET em um objeto FrameworkElement:

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

Neste exemplo, um objeto derivado de ContentView é convertido em um objeto FrameworkElement. O objeto FrameworkElement pode ser definido como o conteúdo de uma página WinUI.

O objeto FrameworkElement pode ser adicionado a um layout no seu aplicativo nativo:

stackPanel.Children.Add(nativeView);

Importante

Para evitar que ocorra um erro, o recarregamento dinâmico XAML deve ser desabilitado antes de executar um aplicativo nativo inserido na configuração de depuração.