CommunityToolkit / Maui

The .NET MAUI Community Toolkit is a community-created library that contains .NET MAUI Extensions, Advanced UI/UX Controls, and Behaviors to help make your life as a .NET MAUI developer easier

Home Page:https://learn.microsoft.com/dotnet/communitytoolkit/maui

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[BUG] Problem closing a popup in iOS

acaliaro opened this issue · comments

Is there an existing issue for this?

  • I have searched the existing issues

Did you read the "Reporting a bug" section on Contributing file?

Current Behavior

It appears there are still issues managing popups on iOS. I have a popup that creates a loader. When the loarder appears, I open a DisplayAlert to display a message. Meanwhile, a Task closes the loader. I would expect the loader to be closed and the DisplayAlert to remain open: in fact, in Android this is the correct behavior. On iOS, the DisplayAlert is closed and the loader popup remains open

Expected Behavior

Closing the popup should close the popup, not the DisplayAlert

Steps To Reproduce

Run the repo on iOS.
Press the "Popup problem!!!!" button
Press the button "Press to show a loader!!"

Link to public reproduction project repository

https://github.com/acaliaro/MauiPopupResizeProblem

Environment

- .NET MAUI CommunityToolkit:9.0.1
- OS:iOS 17.3
- .NET MAUI:8.0.60

Anything else?

No response

Just encountered the same problem. The spinner popup is hidden by the FilePicker modal window. Then the timeout hits and the spinner popup closes itself. On iOS the closing code closes the FilePicker but the spinner stays open forever.

If I do not control the ShowPopup and Close of the Popup in the OnIsBusyChanged method and write it as below, will it work as expected?

	async Task ShowLoaderAsync()
	{
		try
		{
			if (App.LoadingView == null)
			{
				App.LoadingView = new LoadingView();
				Shell.Current.ShowPopup(App.LoadingView);
			}
			await Task.Delay(50);

			await Task.Run(async () =>
			{

				Console.WriteLine("Another thread...");
				await Task.Delay(1000);
			});

			if (App.LoadingView != null)
			{
				await App.LoadingView.CloseAsync();
			}
			App.LoadingView = null;

			await Shell.Current.DisplayAlert("Warning", "Time is passed!!", "Ok");
			await Shell.Current.GoToAsync("..", animate: true);

		}
		catch (Exception ex)
		{
			await Shell.Current.DisplayAlert("Error", ex.Message, "Ok");
		}
		finally
		{
			if (App.LoadingView != null)
			{
				await App.LoadingView.CloseAsync();
			}
			App.LoadingView = null;
		}
	}

I called the ShowPopup and CloseAsync methods on the main thread and waited for the CloseAsync method call to complete.
Below is the verification video.

iPhone.15.iOS.17.0.2024-06-25.13-33-16.mp4

@acaliaro , Is the above movement what you are expecting?

I don't know... The correct movement is that I can close the popup in another thread and the displayalert should not close. In your example, in the second thread you do nothing.... And you close the popup before you display the DisplayAlert but I would like to close the popup only on "finally" clause otherwise I have to close it everywhere before use a DisplayAlert... I don't know if I have been clear. Thanks for your work!!!!

@acaliaro , I tried various things, but on iOS, if I display a Popup from another thread and close it, the Popup disappears, but the thread does not close properly. Therefore, I implemented it as shown above. Is it what you want to do, to display a Popup while processing something, and when the processing is finished, display a message with DisplayAlert and close the Popup?

If you write the process to close a Popup using only finally, it will look like the following.

iPhone.15.iOS.17.0.2024-06-25.16-00-06.mp4

I'm sorry that I don't understand what kind of result you want.

HI. The problem is that the second thread (the one that waits for 1000 and sets IsBusy to false) should close the loader popup, but instead it closes the display alert. That is the question. I think there is something in the implementation of the popup in iOS that conflicts with the implementation of the DisplayAlert in Maui, otherwise I don't understand why closing a popup interacts with a DisplayAlert

@acaliaro , I understand what you want to say, thank you.
If DisplayAlert and Popup are displayed at the same time, it may be possible that the DisplayAlert side is closed.
I'll try to find out the cause as time permits.

@cat0363 Thanks for the effort!! The other "problem" is that on Android this situation doesn't happen... the loader closes correctly (closed by the second thread) and the DisplayAlert remains visible.

When the DismissViewControllerAsync(true) method is called, the Popup will be closed, but if Popups are displayed in a hierarchical manner, the Popup will be closed in order starting from the last displayed Popup. I found that this method is closing the DisplayAlert. This is because DisplayAlert is displayed in a higher layer than Popup.

@acaliaro , I think this is why the DisplayAlert was closed without the Popup closing, but I'm not sure if this can be fixed at this time.

When the DismissViewControllerAsync(true) method is called, the Popup will be closed, but if Popups are displayed in a hierarchical manner, the Popup will be closed in order starting from the last displayed Popup. I found that this method is closing the DisplayAlert. This is because DisplayAlert is displayed in a higher layer than Popup.

@acaliaro , I think this is why the DisplayAlert was closed without the Popup closing, but I'm not sure if this can be fixed at this time.

So the community popup and DisplayAlert implementation use the same "resource" which is then closed LIFO?

@acaliaro , When I called the DismissViewControllerAsync method twice in a row, the DisplayAlert and then the Popup were closed in that order. From this, the order in which they are closed is LIFO. When Popups are displayed in a hierarchy, closing a Popup in a lower hierarchy will close all Popups in the hierarchy above that hierarchy. Perhaps the information below will be helpful.

https://developer.apple.com/documentation/uikit/uiviewcontroller/1621505-dismiss

Additional Information:
The .NET MAUI DisplayAlert implementation is in the PresentPopUp method in the file below.

[src\Controls\src\Core\Platform\AlertManager\AlertManager.iOS.cs]

static void PresentPopUp(Window virtualView, UIWindow platformView, UIAlertController alert, ActionSheetArguments arguments = null)

@cat0363 Thanks for the clarification. In your opinion, is there a possibility to close a specific popup regardless of the hierarchy?
For example, this is the code I use to manage the opening and closing of the loader popup:

  if (value)
  {
      if (App.LoadingView == null)
      {
          App.LoadingView = new LoadingView();

          Shell.Current.ShowPopup(App.LoadingView);
      }
  }
  else
  {
      try
      {
          if (App.LoadingView != null)
          {
              App.LoadingView.Close();
          }
      }
      catch 
      {
      }
      finally
      {
          App.LoadingView = null;
      }
  }

In your opinion, can we have the reference of the loader popup so that "Close" closes that popup exactly, regardless of any popup hierarchy that is created?

@acaliaro , Even if I intentionally specified a Popup reference and called the DismissViewControllerAsync method, the last Alert displayed was closed first. It may be better to ask for opinions on whether what you want to do is possible in the .NET MAUI Discussions. There may be someone who can judge whether it is possible or not. I will continue to investigate, but it seems very likely that this will not be possible due to iOS specifications.

ok @cat0363 , let me know if you found some news. BTW, I think in XF there was not this kind of problem. Am I wrong?

@acaliaro , I've never tried a pattern like this in XF, so I don't know if it's a problem or not.
If the problem in this issue does not occur in XF, the way Popup is implemented may be different from that in XF.

After trying various things, I was unable to close the Popup unless I closed the last Alert that was displayed.
For this reason, it is not possible to close just the Popup, but it is possible to achieve the expected behavior by closing the Alert, then the Popup, and then redisplaying the Alert.

iPhone.15.iOS.17.0.2024-07-05.16-24-00.mp4

@acaliaro , I don't think this is the behavior you are expecting.
Below is the code to achieve the above behavior.

[src\CommunityToolkit.Maui.Core\Handlers\Popup\PopupHandler.macios.cs]

	public static async void MapOnClosed(PopupHandler handler, IPopup view, object? result)
	{
		var presentationController = handler.PlatformView.PresentationController;

		if (handler.PlatformView.PopoverPresentationController is UIPopoverPresentationController ppc)
		{
			if (ppc.PresentedViewController is MauiPopup mauiPopupController)
			{
				UIAlertController? tmpAlertViewController = null;

				if (mauiPopupController.PresentedViewController is UIAlertController alertViewController)
				{
					tmpAlertViewController = alertViewController;
					await alertViewController.DismissViewControllerAsync(true);
				}
				await mauiPopupController.DismissViewControllerAsync(true);

				if (tmpAlertViewController != null)
				{
					if (handler.PlatformView.ViewController is not null)
					{
						handler.PlatformView.ViewController.PresentViewController(tmpAlertViewController, true, null);
					}
				}
			}
		}

		view.HandlerCompleteTCS.TrySetResult();

		handler.DisconnectHandler(handler.PlatformView);
	}

Thanks for the effort @cat0363 , but that's not what I want. Unfortunately due to how the popup was implemented, I don't think the loader issue can be managed using the communityToolkit popup, unless you have a reference for each of the open popups so you can close the single popup using the reference.

When popovers are displayed in a hierarchical manner, even if you call the DismissViewControllerAsync method only on the hierarchy you want to close, the last displayed Popover will be closed. In the code I wrote, mauiPopupController is an instance of Popup and alertViewController is an instance of Alert. You can see that if you call mauiPopupController's DismissViewControllerAsync method without calling alertViewController's DismissViewControllerAsync method, the Alert side will be closed. You cannot intentionally close only certain hierarchies. It is unclear whether this is by design or not. If there is a counterexample, it may be a clue to the solution.