Native Embedding in .NET MAUI 10: Add MAUI Pages to Existing iOS and Android Apps
Learn how to embed .NET MAUI 10 pages inside existing native iOS and Android apps. Covers the MauiApp singleton bootstrap, DI bridging between native and MAUI containers, image asset MSBuild wiring, and the gotchas the official docs quietly omit.
Native embedding in .NET MAUI 10 lets you host individual MAUI pages, controls, or a whole MauiApp instance inside an existing native iOS, Android, WinUI, or Mac Catalyst project without converting the whole app to MAUI. You bootstrap a MauiApp the same way MAUI does internally, then call ToUIViewController() on iOS or resolve a container view on Android and push it into your native navigation stack. It's the pragmatic migration path Apple, Google, and Microsoft's own teams reach for when a full rewrite isn't on the table.
Native embedding was promoted from experimental to a supported API surface in .NET 9 and hardened in .NET MAUI 10, so there's no more MauiProgram.CreateMauiApp() hackery in native projects.
On iOS you convert a MAUI page or view to a UIViewController via page.ToUIViewController(mauiContext); on Android you host it as a platform View resolved from the same MauiContext.
You need a shared MauiApp singleton. Creating one per screen leaks handlers, image caches, and DI scopes. Bootstrap once in your UIApplicationDelegate or Application.OnCreate().
Handlers, fonts, and services registered in MauiAppBuilder work identically to a full MAUI app. Shell navigation does not; you route with the native stack instead.
The most common failure mode is missing image assets: MAUI's build task processes them for MAUI-SDK projects only, so native hosts need explicit MauiImage item groups.
What is native embedding in .NET MAUI?
Native embedding is a hosting model where a native platform project (a Xcode-style iOS app, an Android Studio-style project, a WinUI 3 desktop app, or a Mac Catalyst target) bootstraps the .NET MAUI runtime and mounts MAUI content inside a native container view. You aren't building a MAUI app that happens to call some native code through handlers. You're building a native app that happens to render a MAUI page inside a UIViewController, a FrameLayout, or a ContentControl.
Microsoft first shipped this as an experimental API in .NET 8, promoted it to supported in .NET 9, and cleaned up the last of the rough edges in the .NET MAUI native embedding documentation. The API surface you touch is small: MauiApp.CreateBuilder(), an extension called UseMauiEmbeddedApp<TApp>(), and a handful of ToPlatform() / ToUIViewController() / ToContainerView() extensions in the Microsoft.Maui.Platform namespace.
Here's the key mental model. A full MAUI app owns the UIApplication/Application lifecycle and installs its own MauiUIApplicationDelegate or MauiApplication. A native-embedded MAUI app does not. Your AppDelegate stays yours; MAUI just runs alongside it as a hosted subsystem, similar to how you'd host a SwiftUI view in a UIKit app via UIHostingController.
When native embedding makes sense (and when it doesn't)
Honestly, I've shipped this pattern in three production apps over the last eighteen months, and the trade-off is always the same: you get incremental adoption at the cost of dual maintenance. Use native embedding when:
You're migrating from Xamarin.iOS/Xamarin.Android (the "native" variants, not Xamarin.Forms). The binding native libraries in .NET MAUI 10 path is still your friend here for Objective-C frameworks, but embedding lets you rewrite screens one at a time instead of doing a big-bang migration.
The native app has heavy platform-specific investment (custom Metal renderers, ARKit, ML Kit pipelines, a hand-tuned RecyclerView) that would take months to reproduce as MAUI handlers.
You need to add a single cross-platform feature (a settings screen, an in-app purchase flow, an onboarding tutorial) to two existing native codebases and don't want to write it twice.
Compliance or SDK constraints force the app entry point to remain a native project. I've seen this with automotive SDK integrations that require a specific Application subclass.
Project setup: adding MAUI to a native iOS or Android app
So, the setup is fiddlier than the docs suggest. Assuming you have an existing .csproj targeting net10.0-ios or net10.0-android (or a Xamarin project you've migrated using the .NET upgrade assistant for Xamarin native projects), you enable MAUI by adding UseMaui and a couple of item groups:
The gotcha here is UseMaui. It flips on the MSBuild targets that convert MauiImage items into Assets.xcassets catalogs on iOS and drawable resources on Android. Without it, your embedded MAUI page ships without images and every Image Source="logo.png" silently renders empty. Apple's asset-catalog documentation describes the underlying build product these targets generate, and Google's Android resource guide covers the drawable equivalent.
Bootstrapping the MauiApp singleton
The single most important rule of native embedding: you build the MauiApp exactly once per process. Every MAUI page you embed later resolves handlers, fonts, and services from that singleton's IServiceProvider. Build it twice and you'll get duplicate handler registrations, a second image loading pipeline, and DI scopes that don't share singletons.
// Shared/MauiHost.cs, called from both iOS AppDelegate and Android Application
public static class MauiHost
{
private static MauiApp? _app;
public static MauiApp App => _app
?? throw new InvalidOperationException(
"MauiHost.Start() must be called before accessing App.");
public static void Start()
{
if (_app is not null) return;
var builder = MauiApp.CreateBuilder()
.UseMauiEmbeddedApp<EmbeddedApp>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
// Register the pages and view models we want to embed in native screens.
builder.Services.AddSingleton<IAuthTokenStore, KeychainTokenStore>();
builder.Services.AddTransient<SettingsPageViewModel>();
builder.Services.AddTransient<SettingsPage>();
_app = builder.Build();
}
}
// EmbeddedApp is a bare-bones Application subclass; it never displays a MainPage.
internal sealed class EmbeddedApp : Application
{
protected override Window CreateWindow(IActivationState? activationState) =>
new Window(); // Required by the base class; the native host owns real windows.
}
Embedding a MAUI page in a native iOS UIViewController
On iOS the entry point is your existing AppDelegate. Call MauiHost.Start() in FinishedLaunching, then when you want to push a MAUI screen, convert it to a UIViewController via the Microsoft.Maui.Platform extension:
using Microsoft.Maui.Platform;
using UIKit;
[Register("AppDelegate")]
public class AppDelegate : UIApplicationDelegate
{
public override UIWindow? Window { get; set; }
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
MauiHost.Start();
Window = new UIWindow(UIScreen.MainScreen.Bounds);
Window.RootViewController = new UINavigationController(new HomeViewController());
Window.MakeKeyAndVisible();
return true;
}
}
// From any native UIViewController, push the MAUI settings page:
public class HomeViewController : UIViewController
{
private void OpenSettingsTapped()
{
var mauiContext = new MauiContext(MauiHost.App.Services, this);
var settingsPage = MauiHost.App.Services.GetRequiredService<SettingsPage>();
UIViewController mauiVc = settingsPage.ToUIViewController(mauiContext);
mauiVc.Title = "Settings";
NavigationController!.PushViewController(mauiVc, animated: true);
}
}
Three things worth calling out. First, the MauiContext is cheap to create per-navigation and holds a reference to the presenting UIViewController; MAUI uses it to resolve modal presentation, keyboard avoidance, and safe-area insets. Second, resolving the SettingsPage from DI (rather than new SettingsPage()) lets your MAUI page take constructor-injected view models and services, which is the whole point of doing this in MAUI in the first place. Third, ToUIViewController() handles the platform view creation and disposes correctly when the UINavigationController pops, so there's no need for manual lifecycle wiring like the old Xamarin.Forms FormsEmbeddedApplicationDelegate required.
Embedding a MAUI page in a native Android Activity
Android is where the platform pain shows up. You bootstrap in your Application subclass (or the first Activity.OnCreate if you don't have one), then embed by inserting the MAUI-produced platform View into a native FrameLayout:
[Application]
public class MainApplication : Application
{
public MainApplication(IntPtr handle, JniHandleOwnership ownership) : base(handle, ownership) { }
public override void OnCreate()
{
base.OnCreate();
MauiHost.Start();
}
}
// In your Activity, host the MAUI page inside a FrameLayout from your layout XML:
public class HostActivity : AppCompatActivity
{
protected override void OnCreate(Bundle? savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.activity_host);
var mauiContext = new MauiContext(MauiHost.App.Services, this);
var settingsPage = MauiHost.App.Services.GetRequiredService<SettingsPage>();
// ToContainerView() returns the platform-native Android View wrapping the MAUI page.
Android.Views.View platformView = settingsPage.ToContainerView(mauiContext);
var container = FindViewById<FrameLayout>(Resource.Id.maui_container)!;
container.AddView(platformView, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MatchParent,
ViewGroup.LayoutParams.MatchParent));
}
}
The critical difference from iOS is this: on Android you're inserting a View into an existing view hierarchy, so you must forward OnConfigurationChanged, back-button presses, and lifecycle callbacks yourself. The MAUI page won't automatically observe Activity lifecycle events unless you route them through the IPlatformApplication interface. This is documented in the .NET MAUI app lifecycle reference, and it's the single most common source of "why isn't OnAppearing firing" bug reports I see from teams new to embedding. I hit this exact bug shipping my first embedded MAUI screen on Android; two afternoons gone.
For deep-linked routes and per-page state, you'll still want the patterns from the existing guide on mastering navigation and dependency injection in .NET MAUI. Just remember that Shell routing is off the table under embedding, so navigation always crosses back through the native stack.
Sharing services and DI between native and MAUI
The MauiApp.Services property is a standard IServiceProvider, so any native code with a reference to MauiHost.App.Services can resolve MAUI-registered services directly. Where it gets interesting is going the other direction: MAUI pages needing to talk to services the native app already registered in its own DI container (yours, Autofac, Hilt via Java interop, whatever).
The clean pattern is to make the native DI container the source of truth and register a bridge into the MAUI builder:
// Assume the native app already has an existing IServiceProvider.
public static void Start(IServiceProvider nativeContainer)
{
if (_app is not null) return;
var builder = MauiApp.CreateBuilder()
.UseMauiEmbeddedApp<EmbeddedApp>();
// Forward specific services from the native container to MAUI's DI graph.
builder.Services.AddSingleton(nativeContainer.GetRequiredService<IAnalytics>());
builder.Services.AddSingleton(nativeContainer.GetRequiredService<IAuthTokenStore>());
// Register MAUI-only view models and pages.
builder.Services.AddTransient<SettingsPageViewModel>();
builder.Services.AddTransient<SettingsPage>();
_app = builder.Build();
}
Do not attempt to swap MAUI's internal IServiceProvider for your own. The framework registers dozens of handlers, animation managers, and image caches through the builder pipeline, and skipping that setup produces the most infuriating "some MAUI controls just don't render" bugs you'll ever chase. The related work on platform-specific code and handlers in .NET MAUI covers the handler registration pipeline in more detail.
Limitations, gotchas, and platform pain
Here's the punch list of things the official docs quietly omit. I've hit each of these on production apps:
Shell navigation is unsupported
Shell assumes it owns the window and controls the flyout, tab bar, and back button behavior. Under embedding, none of that is true; the native UINavigationController or androidx.navigation graph owns navigation. Use NavigationPage as the root of embedded flows if you need push/pop within a MAUI sub-flow, or route back through the native stack for cross-screen transitions.
Hot Reload works, XAML Hot Reload is limited
C# Hot Reload against embedded MAUI pages works reliably in Visual Studio 2026. XAML Hot Reload requires the debugger to have attached during a full MauiApp build; it works on the MAUI project itself but is spotty when the entry point is a native project. Restarting the debugger usually clears the connection.
Image assets need explicit MSBuild wiring
Because your project's SDK isn't Microsoft.NET.Sdk.Maui, the auto-discovery of Resources\Images doesn't happen. You must add MauiImage item groups manually (as shown above) and, for iOS, verify the generated Assets.xcassets catalog contains your images by inspecting the obj/ folder after build.
Crash-report SDK initialization order matters
If you're calling MauiHost.Start() from OnCreate alongside Firebase, Sentry, or other native SDKs that hook the process, make sure MauiHost.Start() runs after anything that installs global exception handlers. MAUI's internal exception handler will otherwise swallow crash-report SDK notifications.
Multiple MAUI pages on screen simultaneously
Technically supported. Practically slow. Every embedded MAUI page instantiates its own visual tree and handler graph. Two side-by-side MAUI pages on an iPad split-view will roughly double your memory footprint and idle CPU. Prefer a single MAUI page that composes multiple views internally.
WinUI and Mac Catalyst
The same APIs exist (ToPlatform(mauiContext) returns a FrameworkElement for WinUI and a UIView for Mac Catalyst) but tooling coverage is thinner. Mac Catalyst embedding in particular has a known issue with modal presentation from an embedded MAUI page that requires manually setting modalPresentationStyle = .formSheet on the presenting native view controller. Watch the .NET MAUI GitHub releases for fixes as they land.
Frequently Asked Questions
Can you embed .NET MAUI in an existing Xamarin.iOS app?
Yes, but you must first migrate the Xamarin.iOS project to a .NET 10 iOS project using the .NET upgrade assistant. Native embedding requires the host to target net10.0-ios. Once migrated, the ToUIViewController() pattern shown above works identically.
Does native embedding require the full .NET MAUI SDK?
Yes. You need the Microsoft.Maui.Controls NuGet package and the MAUI workloads installed (dotnet workload install maui). What you don't need is the Microsoft.NET.Sdk.Maui project SDK; you can use Microsoft.NET.Sdk with <UseMaui>true</UseMaui>.
How does native embedding differ from a standalone .NET MAUI app?
A standalone MAUI app installs its own UIApplicationDelegate/Application subclass and owns the platform lifecycle. An embedded MAUI app runs alongside your existing native lifecycle owners, so MAUI is a guest in the process, not the host. You lose Shell navigation and gain incremental adoption.
Is native embedding production ready in .NET MAUI 10?
Yes. Microsoft removed the experimental flag in .NET 9 and refined the API in .NET 10. Multiple large ISVs (financial and healthcare apps I've consulted for) ship it in production. It's stable; the constraints are architectural, not maturity-related.
What are the performance costs of embedding compared to a native-only app?
Expect an 80 to 200ms cold-start overhead on iOS and 100 to 300ms on Android for the MauiApp bootstrap, plus a memory baseline of roughly 15 to 25MB for the MAUI runtime and default handlers. Per-page overhead is minimal once bootstrapped; a MAUI page renders in the 15 to 40ms range on modern devices.
Build .NET MAUI 10 apps that adapt across phones, tablets, and foldables. WindowSizeClass buckets, TwoPaneView, hinge detection, and iPad Stage Manager gotchas with working code.
A practical guide to .NET MAUI Shell navigation and dependency injection. Learn how to structure routes, pass data between pages, build testable navigation services, and avoid common pitfalls with service lifetimes in mobile apps.