'Display a popup with xamarin forms
I want to display a small popup in a xamarin forms application (iOS/Android PCL)
I am actually working with a ContentPage (XAML + C# code)
and i am showing this popup with:
await Navigation.PushModalAsync(mypopupinstance)
It works fine, but the popup is on the full screen. I just want a small popup and i want to see what is behind.
Thanks
Solution 1:[1]
You'll need to look elsewhere for this kind of functionality. One such library is Rotorgames' Popup plugin: https://github.com/rotorgames/Rg.Plugins.Popup
Solution 2:[2]
Modal pages can't be presented like that.
For small popups you can use
DisplayAlert()
Inside a Page.
If you want something more customizable just wrap the content of your Page Inside a relative Layout or grid, and add the popup on top of your normal content.
Solution 3:[3]
I am working on the same issue, so far I was able to create a popup which can hold a content page. I am gladly willing to share my current state. Please note that I will keep the code examples as short as possible, therefore reducing it to a simple loading and a text prompt dialog.
Approach
After having used the Acr.UserDialogs library for a while, i felt the need of having dialogs I could customize due to my personal requirements. Also I wanted to minimize the necessity of having to rely on plugins. Ideally such a dialog should be invoked with a simple call, for instance:
Dialogs.ShowLoading();
or
string result = Dialogs.ShowPrompt();
As things are with Xamarin.Forms it is quite obvious that we will require a dependency service implementation for that to work.
Shared Code Library
We create a basic interface "IDialogs.cs":
public interface IDialogs
{
bool IsDialogOpen();
void ShowLoading(LoadingDialog dialog);
void ShowPrompt(PromptDialog dialog);
void HideDialog();
}
Next thing to do is to have a static dialog class, which can be called from every page where a dialog is needed. "Dialogs.cs":
public static class Dialogs
{
private static IDialogs dialogService = DependencyService.Get<IDialogs>();
public static bool IsDialogOpen()
{
return dialogService.IsDialogOpen();
}
public static void ShowLoading()
{
LoadingDialog dlg = new LoadingDialog();
dialogService.ShowLoading(dlg);
}
public static Task<string> ShowPromptText()
{
TaskCompletionSource<string> dialogCompletion = new TaskCompletionSource<string>();
PromptDialog dialog = new PromptDialog();
dialog.Canceled += (object sender, object result) => { dialogService.HideDialog(); dialogCompletion.SetResult((string)result); };
dialog.Confirmed += (object sender, object result) => { dialogService.HideDialog(); dialogCompletion.SetResult((string)result); };
dialogService.ShowPrompt(dialog);
return dialogCompletion.Task;
}
public static void HideDialog()
{
dialogService.HideDialog();
}
}
You will notice, that we are using a TaskCompletionSource together with custom event handlers in the ShowPromptText method. This enables us to display the dialog and await the user pressing the okay or cancel button and consuming the returned result.
As of now, the dialogs are as simple as they can get. I use some additional code for styling and themeing, but i will leave that out in order to keep this answer short and simple.
The loading dialog:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.Dialogs.LoadingDialog" BackgroundColor="Transparent">
<ContentPage.Content>
<Grid BackgroundColor="#bb000000">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Frame BackgroundColor="Black" CornerRadius="15" Grid.Row="1" x:Name="ContentGrid" Margin="100,0,100,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ActivityIndicator Grid.Row="0" Color="White" IsRunning="True" HorizontalOptions="Center" VerticalOptions="Center"/>
<Label x:Name="LoadingLabel" Text="Loading ..." VerticalOptions="End" HorizontalOptions="Center" Grid.Row="1" TextColor="White" />
</Grid>
</Frame>
</Grid>
</ContentPage.Content>
(no need to post xaml.cs code for this, since there is no interaction with a loading screen)
The prompt dialog
PromptDialog.Xaml:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:dialogs="clr-namespace:BetterUI.Dialogs"
x:Class="MyApp.Dialogs.PromptDialog" BackgroundColor="Transparent">
<ContentPage.Content>
<ScrollView>
<Grid BackgroundColor="#bb000000">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Frame x:Name="ContentGrid" Grid.Row="1" CornerRadius="15" BackgroundColor="White" Margin="50,0,50,0" Padding="0">
<Grid Grid.Row="1" Margin="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Grid x:Name="HeadingGrid" Padding="10, 5, 10, 5" Margin="-15,0,-15,0" Grid.Row="0" BackgroundColor="Black">
<Label x:Name="HeadingLabel" Text="Enter text" TextColor="White" Margin="20,0,20,0"/>
</Grid>
<Label l x:Name="DescriptionLabel" Text="Enter your text" Grid.Row="1" Margin="15,0,15,0"/>
<Entry x:Name="DialogResultText" Placeholder="Text" PlaceholderColor="LightGray" TextColor="Black" Grid.Row="2" Margin="15,0,15,0"/>
<Grid Grid.Row="3" ColumnSpacing="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="CancelButton" Text="Cancel" Clicked="OnCancelClick" Grid.Column="0" CornerRadius="0"/>
<Button x:Name="ConfirmButton" Text="Okay" Clicked="OnOkayClick" Grid.Column="1" CornerRadius="0"/>
</Grid>
</Grid>
</Frame>
</Grid>
</ScrollView>
</ContentPage.Content>
PromptDialog.xaml.cs:
public partial class PromptDialog : ContentPage
{
public event EventHandler<object> Confirmed;
public event EventHandler<object> Canceled;
public PromptDialog()
{
InitializeComponent();
}
private void OnCancelClick(object sender, EventArgs e)
{
Canceled?.Invoke(this, string.Empty);
}
private void OnOkayClick(object sender, EventArgs e)
{
Confirmed?.Invoke(this, DialogResultText.Text);
}
}
Android implementation
First of all we will create the android implementation of our IDialogs interface created in the shared code earlier:
[assembly: Dependency(typeof(DialogService))]
namespace MyApp.Droid.Services
{
/// <summary>
/// Handles displaying dialog items on screen
/// </summary>
public class DialogService : IDialogs
{
private static DialogFragment currentDialog;
/// <summary>
/// returns if a dialog is already open
/// </summary>
/// <returns></returns>
public bool IsDialogOpen()
{
return (currentDialog != null && currentDialog.IsVisible);
}
/// <summary>
/// Initialize Dialog Service with activity
/// </summary>
/// <param name="activity">activity</param>
public static void Init(Activity activity)
{
Activity = activity;
}
public static Activity Activity { get; set; }
/// <summary>
/// Displays a loading dialog
/// </summary>
/// <param name="dialog">Instance of progress dialog (xamarin.forms)</param>
public void ShowLoading(Dialogs.LoadingDialog dialog)
{
if (Activity == null)
return;
DialogFragment frag = dialog.CreateDialogFragment(Activity);
frag.SetStyle(DialogFragmentStyle.NoTitle, Resource.Style.DialogFrame);
frag.Show(Activity.FragmentManager, "dialog");
currentDialog = frag;
}
/// <summary>
/// Displays a prompt dialog
/// </summary>
/// <param name="dialog"></param>
public void ShowPrompt(Dialogs.PromptDialog dialog)
{
if (Activity == null)
return;
DialogFragment frag = dialog.CreateDialogFragment(Activity);
frag.SetStyle(DialogFragmentStyle.NoTitle, Resource.Style.DialogFrame);
frag.Show(Activity.FragmentManager, "dialog");
currentDialog = frag;
}
/// <summary>
/// Hides loading dialog
/// </summary>
public void HideDialog()
{
if (Activity == null)
return;
if (currentDialog != null)
{
currentDialog.Dismiss();
currentDialog = null;
}
}
}
}
Note that you have to set the activity for the dialog service prior to calling the actual methods to show a dialog, so in your MainActivity.cs make sure to call
DialogService.Init(this);
after having initialized Xamarin.Forms.
Finally, here comes some black magic:
Usually, one would implement such a dialog in Android by putting a fragment container into the main layout and throwing a fragment inside. Unfortunately, due to using Xamarin.Forms, such a main layout isn't available by default.
Even though Xamarin.Forms offers a view extension, which allows converting a ContentPage to a fragment (ContentPage.CreateFragment), we won't have any success of using this because it needs a target fragment container to be placed in.
However, android provides something called a DialogFragment, which can be thrown at the screen without having the need of a defined fragment container.
Unfortunately there isn't any out-of-the-box solution for creating a DialogFragment from Xamarin Forms. Good news is that this can be overcome with the power of using System.Reflection(;), so we create our own extension method and some modified versions of internal classes, xamarin.forms uses under the hood. In order to achieve this, I took the code from Xamarin.Forms in Xamarin.Platform.Android and modified it to create a DialogFragment from a ContentPage:
public static class PageExtensions
{
public static DialogFragment CreateDialogFragment(this ContentPage view, Context context)
{
if (!Forms.IsInitialized)
throw new InvalidOperationException("call Forms.Init() before this");
// Get Platform constructor via reflection and call it to create new platform object
Platform platform = (Platform)typeof(Platform).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(Context), typeof(bool) }, null)
?.Invoke(new object[] { context, true });
// Set the page to the platform
if (platform != null)
{
platform.GetType().GetMethod("SetPage", BindingFlags.NonPublic | BindingFlags.Instance)?.Invoke(platform, new object[] { view });
// Finally get the view group
ViewGroup vg = (Android.Views.ViewGroup)platform.GetType().GetMethod("GetViewGroup", BindingFlags.NonPublic | BindingFlags.Instance)?.Invoke(platform, null);
return new EmbeddedDialogFragment(vg, platform);
}
return null;
}
public class DefaultApplication : Xamarin.Forms.Application
{
}
class EmbeddedDialogFragment : DialogFragment
{
readonly ViewGroup _content;
readonly Platform _platform;
bool _disposed;
public EmbeddedDialogFragment()
{
}
public EmbeddedDialogFragment(ViewGroup content, Platform platform)
{
_content = content;
_platform = platform;
}
public override global::Android.Views.View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
this.Dialog.Window.SetSoftInputMode(SoftInput.AdjustResize);
return _content;
}
public override void OnDestroy()
{
this.Dialog?.Window.SetSoftInputMode(SoftInput.AdjustPan);
base.OnDestroy();
}
protected override void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
_disposed = true;
if (disposing)
{
(_platform as IDisposable)?.Dispose();
}
base.Dispose(disposing);
}
}
}
iOS implementation
Fortunately, for the iOS implementation no deep diving into Xamarin.Forms code is necessary:
Here is the iOS implementation of DialogService:
[assembly: Dependency(typeof(DialogService))]
namespace BetterUI.iOS.Services
{
public class DialogService : IDialogs
{
private UIViewController currentDialog;
private UIWindow popupWindow = null;
public void HideLoading()
{
if (currentDialog != null)
{
UIApplication.SharedApplication.KeyWindow.RootViewController.DismissModalViewController(false);
currentDialog.Dispose();
currentDialog = null;
}
}
public bool IsDialogOpen()
{
return (currentDialog != null && currentDialog.IsBeingPresented);
}
public void ShowLoading(LoadingDialog dialog)
{
UIViewController dialogController = dialog.CreateViewController();
ShowDialog(dialogController);
currentDialog = dialogController;
}
public void ShowPrompt(PromptDialog dialog)
{
UIViewController dialogController = dialog.CreateViewController();
ShowDialog(dialogController);
currentDialog = dialogController;
}
private void ShowDialog(UIViewController dialogController)
{
var bounds = UIScreen.MainScreen.Bounds;
dialogController.View.Frame = bounds;
UIApplication.SharedApplication.KeyWindow.RootViewController.ModalPresentationStyle = UIModalPresentationStyle.CurrentContext;
UIApplication.SharedApplication.KeyWindow.RootViewController.AddChildViewController(dialogController);
UIApplication.SharedApplication.KeyWindow.RootViewController.View.Opaque = false;
UIApplication.SharedApplication.KeyWindow.RootViewController.View.Layer.AllowsGroupOpacity = true;
UIApplication.SharedApplication.KeyWindow.RootViewController.View.Layer.BackgroundColor = new CGColor(Color.White.ToCGColor(), 0.0f);
UIApplication.SharedApplication.KeyWindow.RootViewController.View.BackgroundColor = UIColor.Clear;
UIApplication.SharedApplication.KeyWindow.RootViewController.View.AddSubview(dialogController.View);
dialogController.ModalPresentationStyle = UIModalPresentationStyle.OverCurrentContext;
dialogController.View.Opaque = false;
dialogController.View.BackgroundColor = UIColor.Clear.ColorWithAlpha(0.0f);
}
}
}
Et voila, now whenever we use the calls from the "Approach"-Section of this post, a nice popup dialog containing our custom Xamarin.Forms ContentPage will show up.
Solution 4:[4]
It is easier to use a package, like Plugins.Popup to achieve this, without custom renderers, it is impossible to add an Image to the default AlertDialog, which will limit you.
Using Popup Plugin, you just have to add it to your solution, initialize in both iOS and Android:
[Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
Rg.Plugins.Popup.Popup.Init();
global::Xamarin.Forms.Forms.Init ();
LoadApplication (new App ());
return base.FinishedLaunching (app, options);
}
}
Android:
namespace HelloXamarinFormsWorld.Android
{
[Activity(Label = "HelloXamarinFormsWorld", MainLauncher = true,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsApplicationActivity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
Rg.Plugins.Popup.Popup.Init(this, bundle);
Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication (new App ());
}
}
}
Create a Popup Page
<pages:PopupPage.Animation>
<animations:ScaleAnimation
PositionIn="Center"
PositionOut="Center"
ScaleIn="1.2"
ScaleOut="0.8"
DurationIn="400"
DurationOut="300"
EasingIn="SinOut"
EasingOut="SinIn"
HasBackgroundAnimation="True"/>
</pages:PopupPage.Animation>
<!--You can use any elements here which are extended from Xamarin.Forms.View-->
<StackLayout
VerticalOptions="Center"
HorizontalOptions="Center"
Padding="20, 20, 20, 20">
<Label
Text="Test"/>
</StackLayout>
And, to show in your page:
await Navigation.PushPopupAsync(page);
Solution 5:[5]
As of March 22, 2022, there is a Popup
Included in the Xamarin Community Toolkit. You can find Microsoft's documentation here.
An object instantiation through XAML:
<xct:Popup xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xct="clr-namespace:Xamarin.CommunityToolkit.UI.Views;assembly=Xamarin.CommunityToolkit"
x:Class="MyProject.SimplePopup">
<StackLayout>
<Label Text="Hello Simple Popup" />
</StackLayout>
</xct:Popup>
An object instantiation through code:
using Xamarin.CommunityToolkit.UI.Views;
// can also specify a type to be returned from the dismissal of the popup
// e.g. var popup = new Popup<string>
var popup = new Popup
{
// Can set the content to any custom layout
Content = new StackLayout
{
Children =
{
new Label
{
Text = "Hello Simple Popup"
}
}
}
};
You can show the popup through the Navigation stack:
using Xamarin.CommunityToolkit.Extensions;
App.Current.MainPage.Navigation.ShowPopup(popup);
Or call it through await App.Current.MainPage.Navigation.ShowPopupAsync(popup);
It also has events you can intercept and handle, like popup.Dismissed
.
The popup can even return a value when it is dismissed if you instantiate it with a type specified:
// provide the <T> type string here for the example
var popup = new Popup<string>
{
Content = new StackLayout
{
Children =
{
new Label
{
Text = "Hello Simple Popup"
},
new Button
{
Text = "Dismiss",
// pass the <T> you would like as the result into Dismiss
// in this case it is a string "Dismiss was clicked"
Command = new Command(() => Dismiss("Dismiss was clicked"))
}
}
}
};
var result = await App.Current.MainPage.Navigation.ShowPopupAsync(popup);
Console.WriteLine(result);
Solution 6:[6]
Use package named 'Rg.Plugin.Popup'. It will help you.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Timothy James |
Solution 2 | |
Solution 3 | |
Solution 4 | Bruno Caceiro |
Solution 5 | Ibrennan208 |
Solution 6 | PRASAD CP |