Native embedding
Typically, a .NET Multi-platform App UI (.NET MAUI) app includes pages that contain layouts, such as Grid, and layouts that contain views, such as Button. Pages, layouts, and views all derive from Element. Native embedding enables any .NET MAUI controls that derive from Element to be consumed in .NET for Android, .NET for iOS, .NET for Mac Catalyst, and WinUI native apps.
The process for consuming a .NET MAUI control in a native app is as follows:
- Create extension methods to bootstrap your native embedded app. For more information, see Create extension methods.
- Create a .NET MAUI single project that contains your .NET MAUI UI and any dependencies. For more information, see Create a .NET MAUI single project.
- Create a native app and enable .NET MAUI support in it. For more information, see Enable .NET MAUI support.
- Initialize .NET MAUI in your native app project. For more information, see Initialize .NET MAUI.
- Create the .NET MAUI UI and convert it to the appropriate native type with the
ToPlatformEmbedding
extension method. For more information, see Consume .NET MAUI controls.
- Create a .NET MAUI single project that contains your .NET MAUI UI and any dependencies. For more information, see Create a .NET MAUI single project.
- Create a native app and enable .NET MAUI support in it. For more information, see Enable .NET MAUI support.
- Initialize .NET MAUI in your native app project. For more information, see Initialize .NET MAUI.
- Create the .NET MAUI UI and convert it to the appropriate native type with the
ToPlatformEmbedding
extension method. For more information, see Consume .NET MAUI controls.
Note
When using native embedding, .NET MAUI's data binding engine still works. However, page navigation must be performed using the native navigation API.
Create extension methods
Before creating a native app that consumes .NET MAUI controls, you should first create a .NET MAUI class library project and delete the Platforms folder and the Class1
class from it. Then, add a class to it named EmbeddedExtensions
that contains the following code:
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>();
}
}
These extension methods are in the Microsoft.Maui.Controls
namespace, and are used to bootstrap your native embedded app on each platform. The extension methods reference EmbeddedPlatformApplication
, EmbeddedWindowHandler
, and EmbeddedWindowProvider
types that you must also add to the .NET MAUI library project.
The following code shows the EmbeddedPlatformApplication
class, which should be added to the same .NET MAUI library project as the EmbeddedExtensions
class:
#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);
}
}
The following code shows the EmbeddedWindowHandler
class, which should be added to the same .NET MAUI library project as the EmbeddedExtensions
class:
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.");
}
The following code shows the EmbeddedWindowProvider
class, which should be added to the same .NET MAUI library project as the EmbeddedExtensions
class:
#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;
}
Create a .NET MAUI single project
Before creating a native app that consumes .NET MAUI controls, you should add a .NET MAUI app project to the same solution as the .NET MAUI class library project you created previously. The .NET MAUI app project will store the UI you intend you re-use in your native embedded app. After adding a new .NET MAUI app project to the solution, perform the following steps:
Delete the Properties folder from the project.
Delete the Platforms folder from the project.
Delete the Resources/AppIcon folder from the project.
Delete the Resources/raw folder from the project.
Delete the Resources/Splash folder from the project.
Delete the
AppShell
class from the project.Modify the
App
class so that it doesn't set theMainPage
property:public partial class App : Application { public App() { InitializeComponent(); } }
Delete the
MainPage
class from the project.Modify the project file so that the
$(TargetFramework)
build property is set tonet8.0
, and the$(OutputType)
build property is removed:<PropertyGroup> <TargetFramework>net8.0</TargetFramework> <RootNamespace>MyMauiApp</RootNamespace> <UseMaui>true</UseMaui> <SingleProject>true</SingleProject> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> ... </PropertyGroup>
Important
Ensure you set the
$(TargetFramework)
build property, not the$(TargetFrameworks)
build property.Modify the
CreateMauiApp
method in theMauiProgram
class so that it accepts an optionalAction<MauiAppBuilder>
argument that's invoked before the method returns: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(); }
At this point you should add your required .NET MAUI UI to the project, including any dependencies and resources, and ensure that the project builds correctly.
Create a .NET MAUI single project
Before creating a native app that consumes .NET MAUI controls, you should add a .NET MAUI app project to the same solution as the .NET MAUI class library project you created previously. The .NET MAUI app project will store the UI you intend you re-use in your native embedded app. After adding a new .NET MAUI app project to the solution, perform the following steps:
Delete the Properties folder from the project.
Delete the Platforms folder from the project.
Delete the Resources/AppIcon folder from the project.
Delete the Resources/raw folder from the project.
Delete the Resources/Splash folder from the project.
Delete the
AppShell
class from the project.Modify the
App
class so that it doesn't set theMainPage
property:public partial class App : Application { public App() { InitializeComponent(); } }
Delete the
MainPage
class from the project.Modify the project file so that the
$(TargetFramework)
build property is set tonet9.0
, and the$(OutputType)
build property is removed:<PropertyGroup> <TargetFramework>net9.0</TargetFramework> <RootNamespace>MyMauiApp</RootNamespace> <UseMaui>true</UseMaui> <SingleProject>true</SingleProject> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> ... </PropertyGroup>
Important
Ensure you set the
$(TargetFramework)
build property, not the$(TargetFrameworks)
build property.In the
MauiProgram
class, modify theCreateMauiApp
method to accept aTApp
generic argument, and accept an optionalAction<MauiAppBuilder>
argument that's invoked before the method returns. In addition, change the call fromUseMauiApp<App>
toUseMauiEmbeddedApp<TApp>
:public static class MauiProgram { // Create a MauiApp using the specified application. public static MauiApp CreateMauiApp<TApp>(Action<MauiAppBuilder>? additional = null) where TApp : App { var builder = MauiApp.CreateBuilder(); builder .UseMauiEmbeddedApp<TApp>() .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(); } }
In the
MauiProgram
class, add aCreateMauiApp
overload that accepts an optionalAction<MauiAppBuilder>
argument:public static class MauiProgram { ... // Create a MauiApp using the default application. public static MauiApp CreateMauiApp(Action<MauiAppBuilder>? additional = null) => CreateMauiApp<App>(additional); }
You should then add your required .NET MAUI UI to the project, including any dependencies and resources, and ensure that the project builds correctly.
Enable .NET MAUI support
To consume .NET MAUI controls that derive from Element in a .NET for Android, .NET for iOS, .NET for Mac Catalyst, or WinUI app, you should add your native app project to the same solution as the .NET MAUI class library project you created previously. Then you should enable .NET MAUI support in your native app's project file by setting the $(UseMaui)
and $(MauiEnablePlatformUsings)
build properties to true
in the first <PropertyGroup>
node in the project file:
<PropertyGroup>
...
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
<UseMaui>true</UseMaui>
<MauiEnablePlatformUsings>true</MauiEnablePlatformUsings>
</PropertyGroup>
For .NET for Mac Catalyst apps, you'll also need to set the $(SupportedOSPlatformVersion)
build property to a minimum of 14.0:
<PropertyGroup>
...
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
<SupportedOSPlatformVersion>14.2</SupportedOSPlatformVersion>
<UseMaui>true</UseMaui>
<MauiEnablePlatformUsings>true</MauiEnablePlatformUsings>
</PropertyGroup>
For .NET for Mac Catalyst apps, you'll also need to set the $(SupportedOSPlatformVersion)
build property to a minimum of 15.0:
<PropertyGroup>
...
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
<SupportedOSPlatformVersion>15.0</SupportedOSPlatformVersion>
<UseMaui>true</UseMaui>
<MauiEnablePlatformUsings>true</MauiEnablePlatformUsings>
</PropertyGroup>
For WinUI apps, you'll also need to set the $(EnableDefaultXamlItems)
build property to false
:
<PropertyGroup>
...
<Nullable>enable</Nullable>
<ImplicitUsings>true</ImplicitUsings>
<UseMaui>true</UseMaui>
<MauiEnablePlatformUsings>true</MauiEnablePlatformUsings>
<EnableDefaultXamlItems>false</EnableDefaultXamlItems>
</PropertyGroup>
This will stop you receiving build errors about the InitializeComponent
method already being defined.
Then, add $(PackageReference)
build items to the project file for the Microsoft.Maui.Controls
and Microsoft.Maui.Controls.Compatiblity
NuGet packages:
<ItemGroup>
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" />
</ItemGroup>
Then, add $(PackageReference)
build items to the project file for the Microsoft.Maui.Controls
NuGet package:
<ItemGroup>
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
</ItemGroup>
Initialize .NET MAUI
.NET MAUI must be initialized before a native app project can construct a .NET MAUI control. Choosing when to initialize it primarily depends on when it's most convenient in your app flow - it could be performed at startup or just before a .NET MAUI control is constructed. The approach outlined here is to initialize .NET MAUI when the app's initial UI is created.
Typically, the pattern for initializing .NET MAUI in a native app project is as follows:
- Create a MauiApp object.
- Create a MauiContext object from the MauiApp object. The MauiContext object will be used to obtain a native view from the .NET MAUI view.
On Android, the OnCreate
override in the MainActivity
class is typically the place to perform app startup related tasks. The following code example shows .NET MAUI being initialized in the MainActivity
class:
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 context = UseWindowContext
? mauiApp.CreateEmbeddedWindowContext(this) // Create window context
: new MauiContext(mauiApp.Services, this); // Create app context
...
}
}
On iOS and Mac Catalyst, the AppDelegate
class should be modified to return true
for the FinishedLaunching
override:
namespace MyNativeEmbeddedApp.iOS;
[Register("AppDelegate")]
public class AppDelegate : UIApplicationDelegate
{
public override UIWindow? Window { get; set; }
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) => true;
}
The WillConnect
method in the SceneDelegate
class should then be modified to create your main view controller and set it as the view of the UINavigationController
:
namespace MyNativeEmbeddedApp.iOS;
[Register("SceneDelegate")]
public class SceneDelegate : UIResponder, IUIWindowSceneDelegate
{
[Export("window")]
public UIWindow? Window { get; set; }
[Export("scene:willConnectToSession:options:")]
public void WillConnect(UIScene scene, UISceneSession session, UISceneConnectionOptions connectionOptions)
{
if (scene is not UIWindowScene windowScene)
return;
Window = new UIWindow(windowScene);
var mainVC = new MainViewController();
var navigationController = new UINavigationController(mainVC);
navigationController.NavigationBar.PrefersLargeTitles = true;
Window.RootViewController = navigationController;
Window.MakeKeyAndVisible();
}
...
}
Then, in the XML editor, open the Info.plist file and add the following XML to the end of the file:
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>SceneDelegate</string>
</dict>
</array>
</dict>
</dict>
.NET MAUI can then be initialized in the ViewDidLoad
method in your main view controller:
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 context = UseWindowContext
? mauiApp.CreateEmbeddedWindowContext(GetWindow()) // Create window context
: new MauiContext(mauiApp.Services); // Create app context
...
}
}
On Windows, the MainWindow
class is typically the place to perform UI related app startup tasks:
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 context = UseWindowContext
? mauiApp.CreateEmbeddedWindowContext(this) // Create window context
: new MauiContext(mauiApp.Services); // Create app context
...
}
}
In this example, the MauiApp object is created using lazy initialization. The UseMauiEmbedding
extension method is invoked on the MauiAppBuilder object. Therefore, your native app project should include a reference to the .NET MAUI class library project you created that contains this extension method. A MauiContext object is then created from the MauiApp object, with a bool
determining where the context is scoped from. The MauiContext object will be used when converting .NET MAUI controls to native types.
Embedding can be performed in an app context or a window context, but for maximum .NET MAUI compatibility it should be performed in a window context.
App context
Native embedding can be performed in an app context, where the native app has no knowledge of a window. With this approach, native embedding initialization requires you to:
- Create a MauiApp object.
- Create a MauiContext object from the MauiApp object. The MauiContext object will be used to obtain a native view from the .NET MAUI view.
The following example shows this approach:
var mauiApp = MauiProgram.CreateMauiApp();
var context = new MauiContext(mauiApp.Services); // Activity also needs passing on Android
A .NET MAUI view can then be created and converted to a native view with the ToPlatformEmbedded
extension method, which requires the MauiContext object as an argument.
This approach is suitable for scenarios where a native app needs to embed a simple .NET MAUI UI, but doesn't require access to all .NET MAUI features. The disadvantage of this approach is that tooling such as hot reload, and some .NET MAUI features, won't work.
Tip
Creating a MauiApp object each time a .NET MAUI view is embedded as a native view isn't recommended. This can be problematic if embedded views access the Application.Current
property. Instead, the MauiApp object can be created as a shared, static instance:
public static class MyEmbeddedMauiApp
{
static MauiApp? _shared;
public static MauiApp Shared => _shared ??= MauiProgram.CreateMauiApp();
}
With this approach, you can instantiate the MauiApp object early in your app lifecycle to avoid having a small delay the first time you embed a .NET MAUI view in your app.
On Android, a fragment represents a portion of the UI within an activity. The following code example shows .NET MAUI being initialized in a fragment:
using Android.Runtime;
using Android.Views;
using AndroidX.Navigation.Fragment;
using Microsoft.Maui.Controls.Embedding;
using Fragment = AndroidX.Fragment.App.Fragment;
using View = Android.Views.View;
namespace MyNativeEmbeddedApp.Droid;
[Register("com.companyname.nativeembeddingdemo." + nameof(FirstFragment))]
public class FirstFragment : Fragment
{
public override View? OnCreateView(LayoutInflater inflater, ViewGroup? container, Bundle? savedInstanceState) =>
inflater.Inflate(Resource.Layout.fragment_first, container, false);
public override void OnViewCreated(View view, Bundle? savedInstanceState)
{
base.OnViewCreated(view, savedInstanceState);
// Ensure .NET MAUI app is built before creating .NET MAUI views
var mauiApp = MyEmbeddedMauiApp.Shared;
// Create .NET MAUI context
var context = new MauiContext(mauiApp.Services, Activity);
...
}
}
On iOS and Mac Catalyst, the AppDelegate
class should be modified to return true
for the FinishedLaunching
override:
namespace MyNativeEmbeddedApp.iOS;
[Register("AppDelegate")]
public class AppDelegate : UIApplicationDelegate
{
public override UIWindow? Window { get; set; }
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) => true;
}
The WillConnect
method in the SceneDelegate
class should then be modified to create your main view controller and set it as the view of the UINavigationController
:
namespace MyNativeEmbeddedApp.iOS;
[Register("SceneDelegate")]
public class SceneDelegate : UIResponder, IUIWindowSceneDelegate
{
[Export("window")]
public UIWindow? Window { get; set; }
[Export("scene:willConnectToSession:options:")]
public void WillConnect(UIScene scene, UISceneSession session, UISceneConnectionOptions connectionOptions)
{
if (scene is not UIWindowScene windowScene)
return;
Window = new UIWindow(windowScene);
var mainVC = new MainViewController();
var navigationController = new UINavigationController(mainVC);
navigationController.NavigationBar.PrefersLargeTitles = true;
Window.RootViewController = navigationController;
Window.MakeKeyAndVisible();
}
...
}
Then, in the XML editor, open the Info.plist file and add the following XML to the end of the file:
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>SceneDelegate</string>
</dict>
</array>
</dict>
</dict>
.NET MAUI can then be initialized in the ViewDidLoad
method in your main view controller:
using Microsoft.Maui.Controls.Embedding;
namespace MyNativeEmbeddedApp.iOS;
public class MainViewController : UIViewController
{
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Ensure .NET MAUI app is built before creating .NET MAUI views
var mauiApp = MyEmbeddedMauiApp.Shared;
// Create .NET MAUI context
var context = new MauiContext(mauiApp.Services);
...
}
}
On Windows, the MainWindow
class is typically the place to perform UI related app startup tasks:
using Microsoft.Maui.Controls.Embedding;
using Microsoft.UI.Xaml;
namespace MyNativeEmbeddedApp.WinUI;
public sealed partial class MainWindow : Microsoft.UI.Xaml.Window
{
public MainWindow()
{
this.InitializeComponent();
}
private async void OnRootLayoutLoaded(object? sender, RoutedEventArgs e)
{
await Task.Yield();
// Ensure .NET MAUI app is built before creating .NET MAUI views
var mauiApp = MyEmbeddedMauiApp.Shared;
// Create .NET MAUI context
var context = new MauiContext(mauiApp.Services);
...
}
}
In this example, the MauiApp object is created as a shared, static instance. When this object is created, MauiProgram.CreateMauiApp
is called which in turn calls the UseMauiEmbedding
extension method on the MauiAppBuilder object. Therefore, your native app project should include a reference to the .NET MAUI class library project you created that contains your MauiProgram
class and your .NET MAUI UI. A MauiContext object is then created from the MauiApp object, that's scoped to the MauiApp object. The MauiContext object will be used when converting .NET MAUI controls to native types.
Window context
Native embedding can be performed in a window context, where the native app has knowledge of a window. In some scenarios, .NET MAUI views require access to a window to work correctly. For example, adaptive triggers require access to a view's window, and if there is no window they don't work.
With this approach, native embedding initialization requires you to:
- Create a MauiApp object.
- Create a MauiContext object with the
CreateEmbeddedWindowContext
method. The MauiContext object will be used to obtain a native view from the .NET MAUI view.
The CreateEmbeddedWindowContext
method creates a window context that relates a native window to a .NET MAUI window:
var mauiApp = MauiProgram.CreateMauiApp();
var context = mauiApp.CreateEmbeddedWindowContext(this);
A .NET MAUI view can then be created and converted to a native view with the ToPlatformEmbedded
extension method, which requires the MauiContext object as an argument.
Note
The ToPlatformEmbedded
extension method has an overload that adds a .NET MAUI view to an embedded window.
The advantage of this approach is that there's a single .NET MAUI window for each native window, window-related APIs will work correctly, and tooling such as hot reload works correctly.
Tip
Creating a MauiApp object each time a .NET MAUI view is embedded as a native view isn't recommended. This can be problematic if embedded views access the Application.Current
property. Instead, the MauiApp object can be created as a shared, static instance:
public static class MyEmbeddedMauiApp
{
static MauiApp? _shared;
public static MauiApp Shared => _shared ??= MauiProgram.CreateMauiApp();
}
With this approach, you can instantiate the MauiApp object early in your app lifecycle to avoid having a small delay the first time you embed a .NET MAUI view in your app.
On Android, a fragment represents a portion of the UI within an activity. The following code example shows .NET MAUI being initialized in a fragment:
using Android.Runtime;
using Android.Views;
using AndroidX.Navigation.Fragment;
using Microsoft.Maui.Controls.Embedding;
using Fragment = AndroidX.Fragment.App.Fragment;
using View = Android.Views.View;
namespace MyNativeEmbeddedApp.Droid;
[Register("com.companyname.nativeembeddingdemo." + nameof(FirstFragment))]
public class FirstFragment : Fragment
{
Activity? _window;
IMauiContext? _windowContext;
public IMauiContext WindowContext =>
_windowContext ??= MyEmbeddedMauiApp.Shared.CreateEmbeddedWindowContext(_window ?? throw new InvalidOperationException());
public override View? OnCreateView(LayoutInflater inflater, ViewGroup? container, Bundle? savedInstanceState) =>
inflater.Inflate(Resource.Layout.fragment_first, container, false);
public override void OnViewCreated(View view, Bundle? savedInstanceState)
{
base.OnViewCreated(view, savedInstanceState);
_window ??= Activity;
// Create MAUI embedded window context
var context = WindowContext;
...
}
}
On iOS and Mac Catalyst, the AppDelegate
class should be modified to return true
for the FinishedLaunching
override:
namespace MyNativeEmbeddedApp.iOS;
[Register("AppDelegate")]
public class AppDelegate : UIApplicationDelegate
{
public override UIWindow? Window { get; set; }
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions) => true;
}
The WillConnect
method in the SceneDelegate
class should then be modified to create your main view controller and set it as the view of the UINavigationController
:
namespace MyNativeEmbeddedApp.iOS;
[Register("SceneDelegate")]
public class SceneDelegate : UIResponder, IUIWindowSceneDelegate
{
[Export("window")]
public UIWindow? Window { get; set; }
[Export("scene:willConnectToSession:options:")]
public void WillConnect(UIScene scene, UISceneSession session, UISceneConnectionOptions connectionOptions)
{
if (scene is not UIWindowScene windowScene)
return;
Window = new UIWindow(windowScene);
var mainVC = new MainViewController();
var navigationController = new UINavigationController(mainVC);
navigationController.NavigationBar.PrefersLargeTitles = true;
Window.RootViewController = navigationController;
Window.MakeKeyAndVisible();
}
...
}
Then, in the XML editor, open the Info.plist file and add the following XML to the end of the file:
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>SceneDelegate</string>
</dict>
</array>
</dict>
</dict>
.NET MAUI can then be initialized in the ViewDidLoad
method in your main view controller:
using Microsoft.Maui.Controls.Embedding;
namespace MyNativeEmbeddedApp.iOS;
public class MainViewController : UIViewController
{
UIKit.UIWindow? _window;
IMauiContext? _windowContext;
public IMauiContext WindowContext =>
_windowContext ??= MyEmbeddedMauiApp.Shared.CreateEmbeddedWindowContext(_window ?? throw new InvalidOperationException());
public override void ViewDidLoad()
{
base.ViewDidLoad();
_window ??= ParentViewController!.View!.Window;
// Create MAUI embedded window context
var context = WindowContext;
...
}
}
On Windows, the MainWindow
class is typically the place to perform UI related app startup tasks:
using Microsoft.Maui.Controls.Embedding;
using Microsoft.UI.Xaml;
namespace MyNativeEmbeddedApp.WinUI;
public sealed partial class MainWindow : Microsoft.UI.Xaml.Window
{
Microsoft.UI.Xaml.Window? _window;
IMauiContext? _windowContext;
public IMauiContext WindowContext =>
_windowContext ??= MyEmbeddedMauiApp.Shared.CreateEmbeddedWindowContext(_window ?? throw new InvalidOperationException());
public MainWindow()
{
this.InitializeComponent();
_window ??= this;
}
private async void OnRootLayoutLoaded(object? sender, RoutedEventArgs e)
{
await Task.Yield();
// Create MAUI embedded window context
var context = WindowContext;
...
}
}
In this example, the MauiApp object is created as a shared, static instance. When this object is created, MauiProgram.CreateMauiApp
is called which in turn calls the UseMauiEmbedding
extension method on the MauiAppBuilder object. Therefore, your native app project should include a reference to the .NET MAUI class library project you created that contains your MauiProgram
class and your .NET MAUI UI. A MauiContext object is then created with the CreateEmbeddedWindowContext
method, that's scoped to the window. The MauiContext object will be used when converting .NET MAUI controls to native types.
Consume .NET MAUI controls
After .NET MAUI has been initialized in your native app, you can add your .NET MAUI UI to your native app's layout. This can be achieved by creating an instance of the UI and converting it to the appropriate native type with the ToPlatformEmbedded
extension method.
On Android, the ToPlatformEmbedded
extension method converts the .NET MAUI control to an Android View object:
var mauiView = new MyMauiContent();
Android.Views.View nativeView = mauiView.ToPlatformEmbedded(context);
In this example, a ContentView-derived object is converted to an Android View object.
Note
The ToPlatformEmbedded
extension method is in the .NET MAUI class library you created earlier. Therefore your native app project should include a reference to that project.
Note
The ToPlatformEmbedded
extension method is in the Microsoft.Maui.Controls.Embedding namespace. Therefore your native app project should include a using
statement for that namespace.
The View object can then be added to a layout in your native app:
rootLayout.AddView(nativeView, new LinearLayout.LayoutParams(MatchParent, WrapContent));
On iOS and Mac Catalyst, the ToPlatformEmbedded
extension method converts the .NET MAUI control to a UIView object:
var mauiView = new MyMauiContent();
UIView nativeView = mauiView.ToPlatformEmbedded(context);
nativeView.WidthAnchor.ConstraintEqualTo(View.Frame.Width).Active = true;
nativeView.HeightAnchor.ConstraintEqualTo(500).Active = true;
In this example, a ContentView-derived object is converted to a UIView object and then width and height constraints are set on it to allow interaction.
Note
The ToPlatformEmbedded
extension method is in the .NET MAUI class library you created earlier. Therefore your native app project should include a reference to that project.
The UIView object can then be added to a view in your view controller:
stackView.AddArrangedSubView(nativeView);
var mauiView = new MyMauiContent();
UIView nativeView = mauiView.ToPlatformEmbedded(context);
In this example, a ContentView-derived object is converted to a UIView object.
Note
The ToPlatformEmbedded
extension method is in the Microsoft.Maui.Controls.Embedding namespace. Therefore your native app project should include a using
statement for that namespace.
The UIView object can then be added to a view in your view controller:
stackView.AddArrangedSubView(new ContainerView(nativeView));
ContainerView
is a custom type that wraps the .NET MAUI view to ensure that it's sized correctly. This is achieved by redirecting IntrinsicContentSize
to the .NET MAUI view's SizeThatFits
:
class ContainerView : UIView
{
public ContainerView(UIView view)
{
AddSubview(view);
}
public override CGSize IntrinsicContentSize =>
SizeThatFits(new CGSize(nfloat.MaxValue, nfloat.MaxValue));
public override CGSize SizeThatFits(CGSize size) =>
Subviews?.FirstOrDefault()?.SizeThatFits(size) ?? CGSize.Empty;
public override void LayoutSubviews()
{
if (Subviews?.FirstOrDefault() is { } view)
view.Frame = Bounds;
}
public override void SetNeedsLayout()
{
base.SetNeedsLayout();
InvalidateIntrinsicContentSize();
}
}
In addition, a ToUIViewController
extension method in .NET MAUI can be used to convert a .NET MAUI page to a UIViewController:
MyMauiPage myMauiPage = new MyMauiPage();
UIViewController myPageController = myMauiPage.ToUIViewController(context);
In this example, a ContentPage-derived object is converted to a UIViewController.
On Windows, the ToPlatformEmbedded
extension method converts the .NET MAUI control to a FrameworkElement object:
var mauiView = new MyMauiContent();
FrameworkElement nativeView = myMauiPage.ToPlatformEmbedded(context);
In this example, a ContentView-derived object is converted to a FrameworkElement object. The FrameworkElement object can then be set as the content of a WinUI page.
Note
The ToPlatformEmbedded
extension method is in the .NET MAUI class library you created earlier. Therefore your native app project should include a reference to that project.
Note
The ToPlatformEmbedded
extension method is in the Microsoft.Maui.Controls.Embedding namespace. Therefore your native app project should include a using
statement for that namespace.
The FrameworkElement object can then be added to a layout in your native app:
stackPanel.Children.Add(nativeView);
Important
To avoid an error occurring, XAML hot reload should be disabled before running a native embedded app in debug configuration.
Support XAML hot reload
XAML hot reload isn't supported in native embedded apps. However, you can still use XAML hot reload to quickly iterate on your .NET MAUI UI by creating a .NET MAUI app that consumes the .NET MAUI UI.
To view your .NET MAUI UI with XAML hot reload:
In the project containing your .NET MAUI UI, update the
MauiProgram
class to add aCreateMauiApp
overload, and modify the existingCreateMauiApp
method to accept a generic argument:public static class MauiProgram { public static MauiApp CreateMauiApp(Action<MauiAppBuilder>? additional = null) => CreateMauiApp<App>(additional); public static MauiApp CreateMauiApp<TApp>(Action<MauiAppBuilder>? additional = null) where TApp : App { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<TApp>() .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(); } }
In the project containing your .NET MAUI UI, convert each resource dictionary from a stand-alone XAML file to a resource dictionary that's backed by a code-behind file.
In the project containing your .NET MAUI UI, update your resource dictionary instantiation, typically in App.xaml, so that the
Source
property also specifies the assembly that contains the resource dictionary:<ResourceDictionary Source="Resources/Styles/Colors.xaml;assembly=NativeEmbeddingDemo" /> <ResourceDictionary Source="Resources/Styles/Styles.xaml;assembly=NativeEmbeddingDemo" />
Create a new .NET MAUI app and add it to the solution containing your .NET MAUI UI project and native embedded apps.
In your .NET MAUI app project, add a reference to the project that contains your .NET MAUI UI.
In your .NET MAUI app project, delete any Resource child folders where the resource is provided by your .NET MAUI UI project. For example, if your .NET MAUI UI project contains Resources > Fonts, Resources > Images, and Resources > Styles folders, these folders should be deleted from the .NET MAUI app you've just created. This enables your .NET MAUI app to consume the resources from the project containing your .NET MAUI UI.
In your .NET MAUI app, update your
App
class so that it derives from theApp
class in your .NET MAUI UI project:<myMauiUIProject:App xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:myMauiUIProject="clr-namespace:NativeEmbeddingDemo;assembly=NativeEmbeddingDemo" x:Class="TestHarnessApp.TestApp"> <myMauiUIProject:App.Resources> <!-- App specific resources go here --> </myMauiUIProject:App.Resources> </myMauiUIProject:App>
Then update the code-behind file for the
App
class so that it derives from theApp
class in your .NET MAUI UI project, and loads any XAML resources from this project:public partial class TestApp : myMauiUIProject.App { public TestApp() { var baseResources = Resources; InitializeComponent(); Resources.MergedDictionaries.Add(baseResources); MainPage = new HostPage(); } }
In your .NET MAUI app, add a page that displays the UI from the project containing your .NET MAUI UI:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:myMauiUIProject="clr-namespace:NativeEmbeddingDemo;assembly=NativeEmbeddingDemo" x:Class="TestHarnessApp.HostPage" Title="HostPage"> <myMauiUIProject:MyMauiContent /> </ContentPage>
In your .NET MAUI app, update the
MauiProgram
class to call theCreateMauiApp
method in the project containing your .NET MAUI UI:public static class MauiProgram { public static MauiApp CreateMauiApp() => NativeEmbeddingDemo.MauiProgram.CreateMauiApp<TestApp>(builder => { // Add any test harness configuration such as service stubs or mocks. }); }
You should now be able to run your .NET MAUI app project on each platform and use XAML hot reload to iterate on your .NET MAUI UI.
For an example of this approach, see the sample app.