-
Notifications
You must be signed in to change notification settings - Fork 439
Migrating to Popup v2
We recently shipped version 2 of the Popup in the .NET MAUI Community Toolkit.
This comes with a number of breaking changes but we promise that you are going to love the result!
We did try hard to make a seamless migration path however this was just not feasible. We experienced an overwhelming number of issues in the original popup implementation and made the hard decision to drop and replace it with something we believe will be much more stable. The sheer fact that the Pull Request introducing popup v2 closed 33 issues just goes to show the impact!
The original implementation made use of platform specific implementations to show the popup (e.g. UIPopoverPresentationController
on iOS and macOS) while this worked well at the general presentation of a view it lead to many issues within our codebase aimed at dealing with layouts and sizing of the content - this is something that MAUI already does for us.
If you are in the process of migrating from v1 to v2 or are about to begin that journey the following article is aimed at helping.
We have moved IPopupService
from CommunityToolkit.Maui.Core
to CommunityToolkit.Maui
. You will likely need to update any using
statements in the files where you use IPopupService
from using CommunityToolkit.Maui.Core;
to using CommunityToolkit.Maui;
.
Yes we understand that this was a rather limited factor with the old implementation, thankfully the new approach makes it possible to show the same instance multiple times. This improvement was the influence behind the next improvement:
Now that popups can be displayed multiple times we have provided support for registering popups as singleton or scoped services.
You can now use any View
as the popup content without needing to inherit from Popup
. The only caveat to this rule is - if you need to return a result from the popup, you have to inherit from Popup
.
Popup
now inherits from ContentView
which provides similar properties to what had been added to the original Popup
implementation. In order to provide a much more consistent approach with how developers build their user interfaces we took the decision to shift over to the using the properties that come out of the box from .NET MAUI, therefore the following has been modified:
<toolkit:Popup
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Class="MyProject.SimplePopup"
- Size="100,200"
+ WidthRequest="100"
+ HeightRequest="200">
</toolkit:Popup>
<toolkit:Popup
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Class="MyProject.SimplePopup"
- Color="Green"
+ BackgroundColor="Green">
</toolkit:Popup>
This can now be controller by the PopupOptions
class.
Popup
s now have their content wrapped inside a Border
control making it easy to style without having to increase the complexity of your popup view. If you want to disable this default Border rendering then read on into PopupOptions
.
This can now be controller by the PopupOptions
class.
When displaying a popup through the IPopupService
, Page
, Shell
or INavigation
, developers can now pass in options that customize the behavior and appearance of the displayed popup.
Note that each of the following properties are BindableProperty
definitions meaning you can change them dynamically and control the visuals of the Popup from bindings.
PopupOptions
provides the CanBeDismissedByTappingOutsideOfPopup
property which can control the whether the user can tap or click outside of the Popup bounds to dismiss the currently displayed Popup. This is true
by default. The following example shows how to prevent a user from being able to dismiss the Popup.
await this.ShowPopupAsync(new SimplePopup(), new PopupOptions
{
CanBeDismissedByTappingOutsideOfPopup = false
});
Note
It is possible to cache PopupOptions
to reuse on future navigation actions or with other popups that share the same configuration, that would prevent unnecessary allocations.
PopupOptions
provides the PageOverlayColor
property which can control the Color
that overlays the current page. The following example shows how to set the PageOverlayColor
to orange.
await this.ShowPopupAsync(new SimplePopup(), new PopupOptions
{
PageOverlayColor = Colors.Orange
});

The above Popup
renders with an opaque background, to control the opacity modify the alpha property of a Color
. The following example shows how to
await this.ShowPopupAsync(new SimplePopup(), new PopupOptions
{
PageOverlayColor = Colors.Orange.WithAlpha(0.5f)
});

This property is a new introduction and allows developers to receive a callback when, as the name suggests, the popup is closed by tapping outside.
PopupOptions
provides the Shape
property which can control the appearance of the border around the content displayed in the Popup
. The following example shows how to set the border to be a rounded rectangle with a corner radius of 4 and a stroke of blue.
await this.ShowPopupAsync(new SimplePopup(), new PopupOptions
{
Shape = new RoundRectangle
{
CornerRadius = new CornerRadius(4),
Stroke = Colors.Blue,
StrokeThickness = 4
}
});

For more details on how to customize the Shape
property see .NET MAUI Shapes.
In order to disable the border on a Popup, simply set the Shape
property to null
. The following example shows how to achieve this
await this.ShowPopupAsync(new SimplePopup(), new PopupOptions
{
Shape = null
});

PopupOptions
provides the Shadow
property which can control the shadow which is applied to the Popup
. The following example shows how to set the shadow to be green with an opacity of 80%.
await this.ShowPopupAsync(new SimplePopup(), new PopupOptions
{
Shadow = new Shadow
{
Brush = Brush.Green,
Opacity = 0.8f
}
});

For more details on how to customize the Shadow
property see .NET MAUI Shadow.
In order to disable the border on a Popup, simply set the Shape
property to null
. The following example shows how to achieve this
await this.ShowPopupAsync(new SimplePopup(), new PopupOptions
{
Shadow = null
});

The original way of passing data through the onPresenting
parameter when calling IPopupService.Show
no longer exists. This has now been replaced by using the IQueryAttributable
interface that integrates with .NET MAUI Shell. You can use it just like you do with .NET MAUI Shell to Process navigation data using a single method. This change was made in order to provide a unified and therefore consistent way of passing navigation data around a .NET MAUI application.
Original way:
public class NamePopupViewModel : ObservableObject
{
[ObservableProperty]
public partial string Name { get; set; } = "";
}
public class MyViewModel : INotifyPropertyChanged
{
private readonly IPopupService popupService;
public MyViewModel(IPopupService popupService)
{
this.popupService = popupService;
}
public void DisplayPopup()
{
this.popupService.ShowPopup<NamePopupViewModel>(onPresenting: viewModel => viewModel.Name = "Shaun");
}
}
New way:
public class NamePopupViewModel : ObservableObject, IQueryAttributable
{
[ObservableProperty]
public partial string Name { get; set; } = "";
public void ApplyQueryAttributes(IDictionary<string, object> query)
{
Name = query[nameof(NamePopupViewModel.Name)] as string;
}
}
public class MyViewModel : INotifyPropertyChanged
{
private readonly IPopupService popupService;
public MyViewModel(IPopupService popupService)
{
this.popupService = popupService;
}
public async Task DisplayPopup()
{
var queryAttributes = new Dictionary<string, object>
{
[nameof(NamePopupViewModel.Name)] = "Shaun"
};
await this.popupService.ShowPopupAsync<NamePopupViewModel>(
Shell.Current,
options: null,
queryAttributes);
}
}
All methods (Show
and Close
) on IPopupService
now require a developer to pass in the Shell
or INavigation
implementation in order to determine which window the popup will be displayed on.
You can pass in the Shell.Current
property in order to provide the current instance. The following example assumes the earlier example of SimplePopup
exists within the codebase and uses the Navigation
property that exists on the base class of Page
provided by .NET MAUI.
public class MyPage : ContentPage
{
public async Task DisplayPopup()
{
await popupService.ShowPopupAsync<SimplePopup>(Navigation);
}
}
You can pass in the Shell.Current
property in order to provide the current instance. The following example assumes the earlier example of SimplePopup
exists within the codebase.
await popupService.ShowPopupAsync<SimplePopup>(Shell.Current);
We hope that you enjoy the new functionality!