-
Notifications
You must be signed in to change notification settings - Fork 461
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.
Popups 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!