Skip to content

Commit

Permalink
Add support for MAUI embedding
Browse files Browse the repository at this point in the history
  • Loading branch information
mattleibow committed Jul 18, 2024
1 parent 58fb075 commit 844b484
Show file tree
Hide file tree
Showing 19 changed files with 823 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ namespace Maui.Controls.Sample.Droid;
[Register("com.microsoft.maui.sample.emdedding." + nameof(FirstFragment))]
public class FirstFragment : Fragment
{
EmbeddingScenarios.IScenario? _scenario;
MyMauiContent? _mauiView;
Android.Views.View? _nativeView;

public override View? OnCreateView(LayoutInflater inflater, ViewGroup? container, Bundle? savedInstanceState) =>
inflater.Inflate(Resource.Layout.fragment_first, container, false);

Expand All @@ -26,14 +30,43 @@ public override void OnViewCreated(View view, Bundle? savedInstanceState)

var button3 = view.FindViewById<Button>(Resource.Id.button3)!;
button3.Click += OnMagicClicked;

// Uncomment the scenario to test:
//_scenario = new EmbeddingScenarios.Scenario1_Basic();
//_scenario = new EmbeddingScenarios.Scenario2_Scoped();
_scenario = new EmbeddingScenarios.Scenario3_Correct();

// Create the view and (maybe) the window
(_mauiView, _nativeView) = _scenario!.Embed(Activity!);

// Add the new view to the UI
var rootLayout = view.FindViewById<LinearLayout>(Resource.Id.layout_first)!;
rootLayout.AddView(_nativeView, 1, new LinearLayout.LayoutParams(MatchParent, WrapContent));
}

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

// Remove the view from the UI
var rootLayout = View!.FindViewById<LinearLayout>(Resource.Id.layout_first)!;
rootLayout.RemoveView(_nativeView);

// If we used a window, then clean that up
if (_mauiView?.Window is IWindow window)
window.Destroying();

base.OnStop();
}

private void OnMagicClicked(object? sender, EventArgs e)
private async void OnMagicClicked(object? sender, EventArgs e)
{
if (_mauiView?.DotNetBot is not Image bot)
return;

await bot.RotateTo(360, 1000);
bot.Rotation = 0;

bot.HeightRequest = 90;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ namespace Maui.Controls.Sample.Droid;
[Register("com.microsoft.maui.sample.emdedding." + nameof(SettingsFragment))]
public class SettingsFragment : Fragment
{
EmbeddingScenarios.IScenario? _scenario;
MyMauiContent? _mauiView;
Android.Views.View? _nativeView;

public override View? OnCreateView(LayoutInflater inflater, ViewGroup? container, Bundle? savedInstanceState) =>
inflater.Inflate(Resource.Layout.fragment_settings, container, false);

Expand All @@ -26,14 +30,43 @@ public override void OnViewCreated(View view, Bundle? savedInstanceState)

var button3 = view.FindViewById<Button>(Resource.Id.button3)!;
button3.Click += OnMagicClicked;

// Uncomment the scenario to test:
//_scenario = new EmbeddingScenarios.Scenario1_Basic();
//_scenario = new EmbeddingScenarios.Scenario2_Scoped();
_scenario = new EmbeddingScenarios.Scenario3_Correct();

// Create the view and (maybe) the window
(_mauiView, _nativeView) = _scenario!.Embed(Activity!);

// Add the new view to the UI
var rootLayout = view.FindViewById<LinearLayout>(Resource.Id.layout_settings)!;
rootLayout.AddView(_nativeView, 1, new LinearLayout.LayoutParams(MatchParent, WrapContent));
}

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

// Remove the view from the UI
var rootLayout = View!.FindViewById<LinearLayout>(Resource.Id.layout_settings)!;
rootLayout.RemoveView(_nativeView);

// If we used a window, then clean that up
if (_mauiView?.Window is IWindow window)
window.Destroying();

base.OnStop();
}

private void OnMagicClicked(object? sender, EventArgs e)
private async void OnMagicClicked(object? sender, EventArgs e)
{
if (_mauiView?.DotNetBot is not Image bot)
return;

await bot.RotateTo(360, 1000);
bot.Rotation = 0;

bot.HeightRequest = 90;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

public class MainViewController : UIViewController
{
EmbeddingScenarios.IScenario? _scenario;
MyMauiContent? _mauiView;
UIView? _nativeView;

public override void ViewDidLoad()
{
base.ViewDidLoad();
Expand Down Expand Up @@ -34,7 +38,16 @@ public override void ViewDidLoad()
firstButton.SetTitle("UIKit Button Above MAUI", UIControlState.Normal);
stackView.AddArrangedSubview(firstButton);

// TODO: The MAUI content will go here.
// Uncomment the scenario to test:
//_scenario = new EmbeddingScenarios.Scenario1_Basic();
//_scenario = new EmbeddingScenarios.Scenario2_Scoped();
_scenario = new EmbeddingScenarios.Scenario3_Correct();

// create the view and (maybe) the window
(_mauiView, _nativeView) = _scenario.Embed(ParentViewController!.View!.Window);

// add the new view to the UI
stackView.AddArrangedSubview(new ContainerView(_nativeView));

// Create UIKit button
var secondButton = new UIButton(UIButtonType.System);
Expand All @@ -44,11 +57,23 @@ public override void ViewDidLoad()
// Create UIKit button
var thirdButton = new UIButton(UIButtonType.System);
thirdButton.SetTitle("UIKit Button Magic", UIControlState.Normal);
thirdButton.TouchUpInside += OnMagicClicked;
stackView.AddArrangedSubview(thirdButton);

AddNavBarButtons();
}

private async void OnMagicClicked(object? sender, EventArgs e)
{
if (_mauiView?.DotNetBot is not Image bot)
return;

await bot.RotateTo(360, 1000);
bot.Rotation = 0;

bot.HeightRequest = 90;
}

private void AddNavBarButtons()
{
var addNewWindowButton = new UIBarButtonItem(
Expand Down Expand Up @@ -87,4 +112,33 @@ private void RequestSession(string? activityType = null)
});
}
}

// UIStackView uses IntrinsicContentSize instead of SizeThatFits
// so we need to create a container view to wrap the Maui view and
// redirect the IntrinsicContentSize to the 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 void LayoutSubviews()
{
if (Subviews?.FirstOrDefault() is {} view)
view.Frame = Bounds;
}

public override void SetNeedsLayout()
{
base.SetNeedsLayout();
InvalidateIntrinsicContentSize();
}

public override CGSize SizeThatFits(CGSize size) =>
Subviews?.FirstOrDefault()?.SizeThatFits(size) ?? CGSize.Empty;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,54 @@ namespace Maui.Controls.Sample.WinUI;

public sealed partial class MainWindow : Microsoft.UI.Xaml.Window
{
EmbeddingScenarios.IScenario? _scenario;
MyMauiContent? _mauiView;
FrameworkElement? _nativeView;

public MainWindow()
{
InitializeComponent();
}

private void OnRootLayoutLoaded(object? sender, RoutedEventArgs e)
private async void OnRootLayoutLoaded(object? sender, RoutedEventArgs e)
{
// Sometimes Loaded fires twice...
if (_nativeView is not null)
return;

await Task.Yield();

// Uncomment the scenario to test:
//_scenario = new EmbeddingScenarios.Scenario1_Basic();
//_scenario = new EmbeddingScenarios.Scenario2_Scoped();
_scenario = new EmbeddingScenarios.Scenario3_Correct();

// create the view and (maybe) the window
(_mauiView, _nativeView) = _scenario.Embed(this);

// add the new view to the UI
RootLayout.Children.Insert(1, _nativeView);
}

private void OnWindowClosed(object? sender, WindowEventArgs args)
{
// Remove the view from the UI
RootLayout.Children.Remove(_nativeView);

// If we used a window, then clean that up
if (_mauiView?.Window is IWindow window)
window.Destroying();
}

private void OnMagicClicked(object? sender, RoutedEventArgs e)
private async void OnMagicClicked(object? sender, RoutedEventArgs e)
{
if (_mauiView?.DotNetBot is not Image bot)
return;

await bot.RotateTo(360, 1000);
bot.Rotation = 0;

bot.HeightRequest = 90;
}

private void OnNewWindowClicked(object? sender, RoutedEventArgs e)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using Microsoft.Maui.Platform;
using Microsoft.Maui.Controls.Embedding;

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

namespace Maui.Controls.Sample;

class EmbeddingScenarios
{
public interface IScenario
{
(MyMauiContent, PlatformView) Embed(PlatformWindow window);
}

public class Scenario1_Basic : IScenario
{
public (MyMauiContent, PlatformView) Embed(PlatformWindow window)
{
var mauiApp = MauiProgram.CreateMauiApp();
#if ANDROID
var mauiContext = new MauiContext(mauiApp.Services, window);
#else
var mauiContext = new MauiContext(mauiApp.Services);
#endif
var mauiView = new MyMauiContent();
var nativeView = mauiView.ToPlatform(mauiContext);
return (mauiView, nativeView);
}
}

public class Scenario2_Scoped : IScenario
{
public (MyMauiContent, PlatformView) Embed(PlatformWindow window)
{
var mauiApp = MauiProgram.CreateMauiApp();
var mauiView = new MyMauiContent();
var nativeView = mauiView.ToPlatformEmbedded(mauiApp, window);
return (mauiView, nativeView);
}
}

public class Scenario3_Correct : IScenario
{
public static class MyEmbeddedMauiApp
{
static MauiApp? _shared;

public static MauiApp Shared => _shared ??= MauiProgram.CreateMauiApp();
}

PlatformWindow? _window;
IMauiContext? _windowContext;

public IMauiContext WindowContext =>
_windowContext ??= MyEmbeddedMauiApp.Shared.CreateEmbeddedWindowContext(_window ?? throw new InvalidOperationException());

public (MyMauiContent, PlatformView) Embed(PlatformWindow window)
{
_window ??= window;

var context = WindowContext;
var mauiView = new MyMauiContent();
var nativeView = mauiView.ToPlatformEmbedded(context);
return (mauiView, nativeView);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Extensions.Logging;
using Microsoft.Maui.Controls.Embedding;

namespace Maui.Controls.Sample;

Expand All @@ -10,6 +11,7 @@ public static MauiApp CreateMauiApp()

builder
.UseMauiApp<App>()
.UseMauiEmbedding()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
Expand Down
Loading

0 comments on commit 844b484

Please sign in to comment.