adospace / reactorui-maui

MauiReactor is a MVU UI framework built on top of .NET MAUI
MIT License
552 stars 45 forks source link

Unable to use a scaffolded third party package as it needs access to the native control before the component has rendered #208

Closed MatthewCarse closed 4 months ago

MatthewCarse commented 4 months ago

Hi, I was trying to use The49.Maui.BottomSheet. The instructions for that package says to create an instance of the class that extends the BottomSheet class, and then call ShowAsync

using The49.Maui.BottomSheet;

public class MySheet : BottomSheet
{
    public MySheetPage()
    {
        InitializeComponent();
    }
}

var sheet = new MySheet();

// Show the sheet
sheet.ShowAsync();

So I've created a scaffolded class with the data template.

[Scaffold(typeof(The49.Maui.BottomSheet.BottomSheet))]
public partial class BottomSheet
{
    public BottomSheet Content(Func<VisualNode> render)
    {
        this.Set(Microsoft.Maui.Controls.TemplatedView.ControlTemplateProperty,
            new DataTemplate(() => TemplateHost.Create(render()).NativeElement));
        return this;
    }
}

In my component, the Render method is returning new BottomSheet with some .Content(() => ) If I set the component ref action in the constructor new BottomSheet((b) => _bottomSheet = b) with a variable private The49.Maui.BottomSheet.BottomSheet? _bottomSheet;, and I create a new instance of the component in a page and call the Render() method, the component ref is always null.

My question is, how can I get access to the native control to call ShowAsync() as it never seems to not be null (OnAddChild etc. are never hit, even when calling Render())? It seems to be a catch-22 between needing to call ShowAsync() on the bottom sheet to show it in the page, and not being able to access it because the page hasn't been rendered. Any help as to if this is possible would be much appreciated.

Thank you in advance

adospace commented 4 months ago

Hi, interesting case, BottomSheet seems a pretty unusual control as it serves only to contain its content that should be shown inside the sheet when it is visible.

In other words, it should never be part of the app's visual tree.

In this case, you don't need to create a scaffolding class, just use it as in a normal c# app (I mean without XAML). In MauiReactor I would do something like this:

static class BottomSheetManager
{
    static BottomSheet? _sheet;

    public static async Task ShowAsync(Func<VisualNode> contentRender, Action<BottomSheet>? configureSheet = null)
    {
        if (_sheet != null)
        {
            return;
        }

        _sheet = new BottomSheet
        {
            Content = (MauiControls.View)TemplateHost.Create(contentRender()).NativeElement!
        };

        configureSheet?.Invoke(_sheet);

        _sheet.Dismissed += _sheet_Dismissed;

        await _sheet.ShowAsync();
    }

    private static void _sheet_Dismissed(object? sender, DismissOrigin e)
    {
        if (_sheet == null) return;
        _sheet.Dismissed -= _sheet_Dismissed;
        _sheet = null;
    }

    public static async Task DismissAsync()
    {
        if (_sheet == null)
        {
            return;
        }

        await _sheet.DismissAsync();
        _sheet = null;
    }
}

class MainPage : Component
{
    public override VisualNode Render()
        => ContentPage(
                Grid(
                    VStack(
                        Button("Show")
                            .OnClicked(ShowBottomSheet)
                            .HCenter()
                    )
                    .VCenter()
                    .Spacing(25)
                    .Padding(30, 0)
            )
        );

    async void ShowBottomSheet()
    {
        await BottomSheetManager.ShowAsync(
            () => VStack(
                Label("Hi from bottom sheet!"),
                Button("Close")
                        .OnClicked(HideBottomSheet)
                ),
            sheet => sheet.HasBackdrop = true);
    }

    private async void HideBottomSheet()
    {
        await BottomSheetManager.DismissAsync();
    }
}

https://github.com/adospace/mauireactor-integration/tree/main/The49

MatthewCarse commented 4 months ago

That's great, thanks so much for your help.