Xamarin.forms: [Enhancement] Popup Control

Created on 1 Feb 2018  Â·  22Comments  Â·  Source: xamarin/Xamarin.Forms

Rationale

Forms users would like to have the option of defining and displaying Popup controls. These are controls which are modal, can host arbitrary content, allow for complex user interaction, can return a response from the user, and are expressed (by default) using the appropriate controls on each platform:

  • iOS – UIView presented by a UIPopoverPresentationController
  • UWP – Flyout
  • Android – Dialog

Requirements

Popup display must be asynchronous.

Popups must be effectively modal; i.e., only one may be displayed at any given time.

Popups must be light-dismissable where the platform allows for it (e.g., on iPad, they should be light-dismissable; on iPhone this is not an option on all versions).

Users must be able to define their own custom Popups with arbitrary return value types.

Users must be able to host the following in a Popup:

  • View
  • ContentPage
  • NavigationPage

This will effectively mean that Popups must provide for hosting VisualElement; for VisualElement types which are not View, any type other than ContentPage or NavigationPage will throw a NotSupportedException.

Popups must allow for (optionally) specifying an "anchor" View; behavior regarding the anchor will be platform-specific:

  • On iPad, the arrow of the presented PopOver will point to the anchor View. On iPhone, the anchor view will have no effect.
  • On Android, the Dialog will be presented near the anchor View.
  • On UWP, the anchor View will be the View to which the Flyout will be attached.

Popups must allow for a Size specification. If no Size is specified, the Popup will size to its content.

Forms should define a convenience Popup subclass for returning a 1-bit response (i.e., a yes/no, true/false, ok/cancel). In addition to providing convenience for users dealing with this common requirement, it will also serve as an example for users who need to implement other custom scenarios.

Forms should define a convenience Popup subclass for returning (effectively) a void value (in order to easily support users wanting to display simple content with no user response).

API Changes

The INavigation interface will add the following method:

Task<T> ShowPopupAsync<T>(Popup<T> popup);

The following interface will be added to Core:

public interface IPopup<out T>
{
    VisualElement Element { get; }

    void SetDismissDelegate(Action<T> dismissDelegate);
}

The Dismiss delegate is invoked when the Popup is dismissed.

The following classes will be added to Core:

public abstract class Popup<T>

This will be the base class from which all Popups will be built and will provide the core properties and functionality.

public struct PopupDismissed{}
public class Popup : Popup<PopupDismissed>

This is a convenience implementation for users who simply need to display content without receiving a user response.

public class BinaryResultPopup : Popup<BinaryResult>
public enum BinaryResult
{
    Negative,
    Affirmative
}

This is a convenience implementation for users who simply need a yes/no type of response.

Other Notes

A proof-of-concept implementation of an API very similar to this one (iOS-only) exists at https://github.com/xamarin/Xamarin.Forms/tree/popover

Difficulty: High

The basic implementation on iOS should be relatively easy; most of it can be taken from the PoC branch. UWP should also be fairly simple, as Flyout already provides most of what's required. Android implementation may be a bit trickier because of the requirement to position the Popup near the anchor control.

The most difficult aspect is the requirement to display a NavigationPage as content; the current Navigation model may need to be adjusted to accommodate this.

F100 in-progress high impact proposal-accepted enhancement âž•

Most helpful comment

All 22 comments

@hartez Any particular issue or roadblock with the Popup implementation started on that branch? Or it wasn't just enough time to completed yet?

@andreinitescu Just never enough time/demand to finish it. I think the branch is missing NavigationPage support, but otherwise is pretty close to the final spec (the big difference is that the branch calls them Popovers instead of Popups).

The branch has a bunch of demo code in the Control Gallery.

I aslo used Rg.Plugins.Popup for that

@hartez We have implemented this as a DependencyService already on all three platforms (iOS, Android and UWP). Happy to look at contributing a PR.

@mattrichnz We'd love to see it! Thanks!

Please include Mac in this as well, if it would help I can detail out how that would work.

I'd love to see this added!

I have taken this spec and implemented in my current project for Android and UWP. I am planning an iOS implementation but haven't finished it yet. While working on the spec I have a few items I would like to change that will make the Popup implementation easier to use than documented.

My Branch:

I am presenting here to get community feedback and see if these changes would be considered if I submit a Pull Request

Reasoning

The original proposal is really flexbile and is built with sub-classing in-mind. However, I found the original proposal missing details that made the UWP implementation very difficult. The original Popup workflow diverges from the standard ContentPage and ContentView implementations which made it difficult to use.

For my project I decided to alter the spec which greatly simplified my usage. I prefer to use MVVM frameworks when developing Xamarin.Forms apps and the current spec will not work with the new Prism Dialog Spec which is what I wanted to use this in. My altered proposal will work because of the addition of event handling. This also means any other MVVM framework that implements a Dialog or Popup abstraction will be able to work with the new spec.

New Proposal

The new proposal below is code pulled from my working fork that includes some detailed XML comments

INavigation

New Navigation methods

```c#
interface INavigation
{
Task ShowPopupAsync(Popup popup);
Task ShowPopupAsync(Popup popup);
}

### Popup 
New popup class which utilizes `object` instead of generic types, this allows us to sub-class just like `ContentPage` and `ContentView`

```c#
public class Popup : View
{
    /// <summary>
    /// Gets or sets the <see cref="Content"/> to render in the Popup.
    /// </summary>
    /// <remarks>
    /// The View can be or type: <see cref="Content"/>, <see cref="ContentPage"/> or <see cref="NavigationPage"/>
    /// </remarks>
    public virtual View Content { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="Color"/> of the Popup.
    /// </summary>
    /// <remarks>
    /// This color sets the native background color of the <see cref="Popup"/>, which is
    /// independent of any background color configured in the actual View.
    /// </remarks>
    public Color Color { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="Color"/> of the Popup Border.
    /// </summary>
    /// <remarks>
    /// This color sets the native border color of the <see cref="Popup"/>, which is
    /// independent of any border color configured in the actual view.
    /// </remarks>
    public Color BorderColor { get; set; } // UWP ONLY - wasn't originally in spec

    /// <summary>
    /// Gets or sets the <see cref="Content"/> anchor.
    /// </summary>
    /// <remarks>
    /// The Anchor is where the Popup will render closest to. When an Anchor is configured
    /// the popup will appear centered over that control or as close as possible.
    /// </remarks>
    public View Anchor { get; set; }

    /// <summary>
    /// Gets or sets the <see cref="Size"/> of the Popup Display. 
    /// </summary>
    /// <remarks>
    /// The Popup will always try to constrain the actual size of the <see cref="Popup" />
    /// to the <see cref="Popup" /> of the View unless a <see cref="Size"/> is specified.
    /// If the <see cref="Popup" /> contiains <see cref="LayoutOptions"/> a <see cref="Size"/>
    /// will be required. This will allow the View to have a concept of <see cref="Size"/>
    /// that varies from the actual <see cref="Size"/> of the <see cref="Popup" />
    /// </remarks>
    public Size Size { get; set; }

    /// <summary>
    /// Gets or sets if the popup can be dismissed by tapping in
    /// the background mask.
    /// </summary>
    /// <remarks>
    /// Typically this is a black or grey mask on the outside of the
    /// popup display depending on the platform.
    /// </remarks>
    public bool IsLightDismissEnabled { get; set; }


    /// <summary>
    /// Dismissed event fired when the popup is dismissed. 
    /// </summary>
    /// <remarks>
    /// Check the <see cref="PopupDismissedEventArgs"/> which
    /// includes if the popup was dismissed by the light background
    /// mask.
    /// </remarks>
    public event EventHandler<PopupDismissedEventArgs> Dismissed;

    /// <summary>
    /// Manually dismiss the popup with a result
    /// </summary>
    public void Dismiss(object result);
}

PopupDismissedEventArgs

There is now an event that is being used and I needed to create a custom EventArgs to track the following properties:

  • Result
  • IsLightDismissed

```c#
public class PopupDismissedEventArgs : EventArgs
{
///


/// Gets or sets the popup dismissed result
///

public object Result { get; set; }

/// <summary>
/// Gets or sets if the event was fired from light dismissed.
/// </summary>
public bool IsLightDismissed { get; set; }

}

## Usage
With the new API defined an implementation can use existing Xamarin.Forms techniques to create the Popup just like `ContentPage` and `ContentView`. 

```xaml
<Popup xmlns="http://xamarin.com/schemas/2014/forms" 
       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
       xmlns:d="http://xamarin.com/schemas/2014/forms/design"
       xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
       mc:Ignorable="d"
       x:Class="DemoApp.Popups.MyPopup">

    <Popup.Content>
        <Label 
            Text="Hello from Popup in Xamarin.Forms"
            VerticalOptions="CenterAndExpand"
            HorizontalOptions="CenterAndExpand" />
    </Popup.Content>

</Popup>

Consider the following code behind for a ContentPage that uses the Popup we just defined.

Simple Usage (ignore result)

When creating a simple alert style Popup where the result doesn't matter at all.

```c#
public class MainPage : ContentPage
{
public async void PopupAwaitIgnoreResult()
{
var popup = new MyPopup();
await Navigation.ShowPopupAsync(popup);
}
}

### Task Awaitable Result
When creating a popup that you await for the result and then continue execution of the method.

```c#
public class MainPage : ContentPage
{   
    public async void PopupWithAwaitResult()
    {
        var popup = new MyPopup();
        var isOkay = await Navigation.ShowPopupAsync<bool>(popup);

        if (isOkay)
        {
            // invoke okay logic
        }
        else
        {
            // invoke not okay logic
        }
    }
}

Event Result

When creating a popup and you want to listen for the Dismissed event. This allows the code to invoke the popup and have the callback executed in a different part of their code.

```c#
public class MainPage : ContentPage
{
public void PopupWithEventCallback()
{
var popup = new MyPopup();
popup.Dismissed += OnDismissed;
Navigation.ShowPopupAsync(popup);

    void OnDismissed(object sender, PopupDismissedEventArgs e)
    {
        var isOkay = (bool)e.Result;
        if (isOkay)
        {
            // invoke okay logic
        }
        else
        {
            // invoke not okay logic
        }
    }
}

}
```

Screenshots

I have this working in the following platforms at the moment:

  • UWP
  • Android

I am still working on an iOS implementation but once that is completed I can submit a pull request.

Below are some gifs of what this looks like on the different platforms

Android

popup_example_layoutoptions

UWP

popup_example_uwp

Amazing work @ahoefling!! I implemented android version on my project and it is working fine. Can we also extend to consider RelativePosition like in Syncfusion popup control: https://help.syncfusion.com/xamarin/sfpopuplayout/popup-positioning

@ahoefling I think some work need to done on popup size (at least in Android). I am not able to create popup that can extend horizontally.

I think we may also need to think about LifeCycle event so that user can add their own animation.

I've been thinking a lot about this as I haven't posted an update in a long time. I am hoping to get back to my work on this to submit my PR in the beginning of next year. I have a project that is going to require me to finish the iOS and UWP portions

@hartez Users must be able to host the following in a Popup:
View
ContentPage
NavigationPage

This will effectively mean that Popups must provide for hosting VisualElement; for VisualElement types which are not View, any type other than ContentPage or NavigationPage will throw a NotSupportedException.
The most difficult aspect is the requirement to display a NavigationPage as content; the current Navigation model may need to be adjusted to accommodate this.

Better to simplify so only a View is allowed as doing it for only some subtypes of VisualElement is difficult and breaks the type system as you describe.

@samhouts I see some new tags, is anyone from Xamarin starting to look at this? I updated my branch the other day and am hoping to make a PR soon that will give us all something concrete to look at.

@ahoefling No, we're just doing some backlog grooming at the moment. We'd love to receive your PR! Thanks!

Thanks for clarifying that

We are happy with Rg.Plugins.Popup, especially now that it supports UWP/Android/iOS/Mac.

This issue needs to define a significant improvement over Rg.Plugins.Popup, or else it's needless duplication of effort.

In response to dotnet/maui#76

Showing a view arbitrarily above the view hierarchy has always been one of my personal "most wanted". As a XF user since its release and someone trying to create richer user experience in an environment where third-party code is frowned upon, I want to voice my support!

To help...I'm not familiar with all of the popup implementations out there, but would like to describe our implementation to see if we've done anything NEW. Would love some feedback!

  • We use the MessagingCenter to send popup requests from XF to specific platform popup handlers
  • You DO NOT have to derive from a particular Application or Page class.
  • We use the platform specific popup windows to render a XF View. (Android.Widget.PopupWindow, custom UIViewController with UIModalPresentationStyle.Popover for iOS, and System.Windows.Controls.Primitives.Popup for WPF)
  • We can anchor the popup to a view or the app window/screen
  • We have attached properties that allow us to show a popup w/ attached view whenever a XF View is tapped (it adds a TapGestureRecognizer). There are also attached properties for IsPresented, XOffset, YOffset, and BindingContext.
  • Using our popup implementation, we can create/extend custom controls to have drop-down features. On our custom picker, the user can specify to UsePopup or native picker view.
  • When the popup closes, a popup closed message is sent.
  • We would like to add relative positioning to anchor feature. Some platforms are easier than others.

P.S. Can't post the code ATM, but if this implementation is of interest, I can go through the appropriate channels.

@charlesroddie if the creator of Rg.Popup is happy then the work he has done could be used to implement it into XF.

I believe that's how a lot of the Xamarin.essentials features where created (could be wrong).

Could be used as an opportunity to update the code and add more features.

Today I spoke with @PureWeen @jsuarezruiz and @jfversluis about this and agreed that my PR #9616 is best suited for the Xamarin Community Toolkit. I have created a new specification that will ultimately be a port of the Xamarin.Forms PR that was submitted in Feb 2020.

  • New Spec - Xamarin/XamarinCommunityToolkit#292

I believe this issue should be closed out since we have a new one to track work in the community toolkit

Was this page helpful?
0 / 5 - 0 ratings