[BUG] When using iOS ModalPresentationStyles that can be automatically dismissed, navigation service gets out of sync & stops working with NavigationPage
Axemasta opened this issue · comments
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
- Create a prism app
- 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
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();
}
}
}
closed by #3072