Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] When using iOS ModalPresentationStyles that can be automatically dismissed, navigation service gets out of sync & stops working with NavigationPage #3071

Closed
Axemasta opened this issue Feb 13, 2024 · 2 comments

Comments

@Axemasta
Copy link
Contributor

Description

This is the issue I have been discussing in the discord.

When working with a modal navigation page, where the page in question has the following iOS specific presentation style:

On<iOS>().SetModalPresentationStyle(UIModalPresentationStyle.PageSheet);

Closing the popover by using the iOS swipe gesture (using Prism to close the modal works as intended), will cause the navigation service to lock up.

I have investigated with a local sample connected to the prism source code and the following happens:

  • Prism does not know the current page has been dismissed & holds a reference to it
  • The first time you call the navigation service, there will be an error output in the console:
[Presentation] Attempt to present <Microsoft_Maui_Controls_Platform_ControlsModalWrapper: 0x107931000> on <Microsoft_Maui_Controls_Platform_ControlsModalWrapper: 0x1087425e0> (from <Microsoft_Maui_Controls_Handlers_Compatibility_NavigationRenderer: 0x10c842400>) whose view is not in the window hierarchy.
  • Subsequent calls to the navigation service get deadlocked because the original semaphore for the previous attempt doesn't get cleared, this looks like the thread actually crashes since the finally & exception blocks on NavigateAsync don't get called.

Normal content pages presented in this way are unaffected, I believe this is because prism is listening for their disappeared events which do fire.

Steps to Reproduce

  1. Create a prism app
  2. Add a modal page to navigate to:
private async void OnNavPopover()
{
    var nav = await navigationService.CreateBuilder()
        .AddNavigationPage(true)
        .AddSegment<PopoverViewModel>()
        .NavigateAsync();

    if (!nav.Success)
    {
        Debugger.Break();
    }
}
3. Swipe the popover closed
4. Try to navigate again, nothing happens & the following warning is present in the console:

[Presentation] Attempt to present <Microsoft_Maui_Controls_Platform_ControlsModalWrapper: 0x107931000> on <Microsoft_Maui_Controls_Platform_ControlsModalWrapper: 0x1087425e0> (from <Microsoft_Maui_Controls_Handlers_Compatibility_NavigationRenderer: 0x10c842400>) whose view is not in the window hierarchy.

5. The navigation service is now soft locked and can't be used for any navigation

### Platform with bug

.NET MAUI

### Affected platforms

iOS

### Did you find any workaround?

I will be raising a PR to address this issue, if the following code is added to the `PrismNavigationPage`, `GoBackAsync` can be called after the page removal to ensure that the navigation service remains in sync with the app's page stack:
```csharp
protected override async void OnDisappearing()
{
    var presentationStyle = Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific.Page.GetModalPresentationStyle(this);

    if (presentationStyle == UIModalPresentationStyle.FullScreen)
    {
        return;
    }

    if (PageNavigationService.NavigationSource == PageNavigationSource.Device)
    {
        await MvvmHelpers.HandleNavigationPageSwipedAway(this);
    }

    base.OnDisappearing();
}

Relevant log output

No response

@Axemasta
Copy link
Contributor Author

Prism iOS Modal Gif

Here is a little clip from my repro sample, you can see that as soon as I close the navigation page popover, the other commands stop working, the view model code is below & is incredibly simple:

public class MainViewModel : ViewModelBase
{
    public DelegateCommand NavigateCommand { get; }
    
    public DelegateCommand ModalCommand { get; }
    
    public DelegateCommand PopoverCommand { get; }
    
    public DelegateCommand NavPopoverCommand { get; }
    
    public MainViewModel(INavigationService navigationService) 
        : base(navigationService)
    {
        Title = "Main Page";

        NavigateCommand = new DelegateCommand(OnNavigate);
        ModalCommand = new DelegateCommand(OnModal);
        PopoverCommand = new DelegateCommand(OnPopover);
        NavPopoverCommand = new DelegateCommand(OnNavPopover);
    }

    private async void OnNavigate()
    {
        var nav = await navigationService.CreateBuilder()
            .AddSegment<AnotherViewModel>()
            .NavigateAsync();

        if (!nav.Success)
        {
            Debugger.Break();
        }
    }
    
    private async void OnModal()
    {
        var navParams = new NavigationParameters()
        {
            { KnownNavigationParameters.UseModalNavigation, false },
        };

        var nav = await navigationService.CreateBuilder()
            .AddNavigationPage(true)
            .AddSegment<AnotherViewModel>()
            .WithParameters(navParams)
            .NavigateAsync();

        if (!nav.Success)
        {
            Debugger.Break();
        }
    }
    
    private async void OnPopover()
    {
        var nav = await navigationService.CreateBuilder()
            .AddSegment<PopoverViewModel>(true)
            .NavigateAsync();

        if (!nav.Success)
        {
            Debugger.Break();
        }
    }

    private async void OnNavPopover()
    {
        var nav = await navigationService.CreateBuilder()
            .AddNavigationPage(true)
            .AddSegment<PopoverViewModel>()
            .NavigateAsync();

        if (!nav.Success)
        {
            Debugger.Break();
        }
    }
}

@dansiegel
Copy link
Member

closed by #3072

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants