diff --git a/samples/CommunityToolkit.Maui.Sample/Pages/Alerts/SnackbarPage.xaml b/samples/CommunityToolkit.Maui.Sample/Pages/Alerts/SnackbarPage.xaml
index e8a00d0720..f5f6e30b28 100644
--- a/samples/CommunityToolkit.Maui.Sample/Pages/Alerts/SnackbarPage.xaml
+++ b/samples/CommunityToolkit.Maui.Sample/Pages/Alerts/SnackbarPage.xaml
@@ -2,9 +2,10 @@
@@ -14,26 +15,34 @@
-
-
-
+
+
-
-
+
-
+
-
-
-
+
+
\ No newline at end of file
diff --git a/samples/CommunityToolkit.Maui.Sample/Pages/Alerts/SnackbarPage.xaml.cs b/samples/CommunityToolkit.Maui.Sample/Pages/Alerts/SnackbarPage.xaml.cs
index 47afee30fa..91ee16327f 100644
--- a/samples/CommunityToolkit.Maui.Sample/Pages/Alerts/SnackbarPage.xaml.cs
+++ b/samples/CommunityToolkit.Maui.Sample/Pages/Alerts/SnackbarPage.xaml.cs
@@ -12,12 +12,13 @@ namespace CommunityToolkit.Maui.Sample.Pages.Alerts;
public partial class SnackbarPage : BasePage
{
- const string displayCustomSnackbarText = "Display a Custom Snackbar, Anchored to this Button";
+ public const string DisplayCustomSnackbarText = "Display Custom Snackbar";
const string dismissCustomSnackbarText = "Dismiss Custom Snackbar";
- readonly IReadOnlyList colors = typeof(Colors)
+
+ readonly IReadOnlyList colors = [.. typeof(Colors)
.GetFields(BindingFlags.Static | BindingFlags.Public)
.ToDictionary(c => c.Name, c => (Color)(c.GetValue(null) ?? throw new InvalidOperationException()))
- .Values.ToList();
+ .Values];
ISnackbar? customSnackbar;
@@ -25,7 +26,7 @@ public SnackbarPage(SnackbarViewModel snackbarViewModel) : base(snackbarViewMode
{
InitializeComponent();
- DisplayCustomSnackbarButton.Text = displayCustomSnackbarText;
+ DisplayCustomSnackbarButtonAnchoredToButton.Text = DisplayCustomSnackbarText;
Snackbar.Shown += Snackbar_Shown;
Snackbar.Dismissed += Snackbar_Dismissed;
@@ -34,9 +35,9 @@ public SnackbarPage(SnackbarViewModel snackbarViewModel) : base(snackbarViewMode
async void DisplayDefaultSnackbarButtonClicked(object? sender, EventArgs args) =>
await this.DisplaySnackbar("This is a Snackbar.\nIt will disappear in 3 seconds.\nOr click OK to dismiss immediately");
- async void DisplayCustomSnackbarButtonClicked(object? sender, EventArgs args)
+ async void DisplayCustomSnackbarAnchoredToButtonClicked(object? sender, EventArgs args)
{
- if (DisplayCustomSnackbarButton.Text is displayCustomSnackbarText)
+ if (DisplayCustomSnackbarButtonAnchoredToButton.Text is DisplayCustomSnackbarText)
{
var options = new SnackbarOptions
{
@@ -52,20 +53,20 @@ async void DisplayCustomSnackbarButtonClicked(object? sender, EventArgs args)
"This is a customized Snackbar",
async () =>
{
- await DisplayCustomSnackbarButton.BackgroundColorTo(colors[Random.Shared.Next(colors.Count)], length: 500);
- DisplayCustomSnackbarButton.Text = displayCustomSnackbarText;
+ await DisplayCustomSnackbarButtonAnchoredToButton.BackgroundColorTo(colors[Random.Shared.Next(colors.Count)], length: 500);
+ DisplayCustomSnackbarButtonAnchoredToButton.Text = DisplayCustomSnackbarText;
},
FontAwesomeIcons.Microsoft,
TimeSpan.FromSeconds(30),
options,
- DisplayCustomSnackbarButton);
+ DisplayCustomSnackbarButtonAnchoredToButton);
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
await customSnackbar.Show(cts.Token);
- DisplayCustomSnackbarButton.Text = dismissCustomSnackbarText;
+ DisplayCustomSnackbarButtonAnchoredToButton.Text = dismissCustomSnackbarText;
}
- else if (DisplayCustomSnackbarButton.Text is dismissCustomSnackbarText)
+ else if (DisplayCustomSnackbarButtonAnchoredToButton.Text is dismissCustomSnackbarText)
{
if (customSnackbar is not null)
{
@@ -75,11 +76,11 @@ async void DisplayCustomSnackbarButtonClicked(object? sender, EventArgs args)
customSnackbar.Dispose();
}
- DisplayCustomSnackbarButton.Text = displayCustomSnackbarText;
+ DisplayCustomSnackbarButtonAnchoredToButton.Text = DisplayCustomSnackbarText;
}
else
{
- throw new NotSupportedException($"{nameof(DisplayCustomSnackbarButton)}.{nameof(ITextButton.Text)} Not Recognized");
+ throw new NotSupportedException($"{nameof(DisplayCustomSnackbarButtonAnchoredToButton)}.{nameof(ITextButton.Text)} Not Recognized");
}
}
@@ -97,6 +98,20 @@ async void DisplaySnackbarInModalButtonClicked(object? sender, EventArgs e)
{
if (Application.Current?.Windows[0].Page is Page mainPage)
{
+ var button = new Button()
+ .CenterHorizontal()
+ .Text("Display Snackbar");
+ button.Command = new AsyncRelayCommand(token => button.DisplaySnackbar(
+ "This Snackbar is anchored to the button on the bottom to avoid clipping the Snackbar on the top of the Page.",
+ () => { },
+ "Close",
+ TimeSpan.FromSeconds(5), token: token));
+
+ var backButton = new Button()
+ .CenterHorizontal()
+ .Text("Back to Snackbar MainPage");
+ backButton.Command = new AsyncRelayCommand(mainPage.Navigation.PopModalAsync);
+
await mainPage.Navigation.PushModalAsync(new ContentPage
{
Content = new VerticalStackLayout
@@ -105,19 +120,11 @@ await mainPage.Navigation.PushModalAsync(new ContentPage
Children =
{
- new Button { Command = new AsyncRelayCommand(static token => Snackbar.Make("Snackbar in a Modal MainPage").Show(token)) }
- .Top().CenterHorizontal()
- .Text("Display Snackbar"),
+ button,
- new Label()
- .Center().TextCenter()
- .Text("This is a Modal MainPage"),
-
- new Button { Command = new AsyncRelayCommand(mainPage.Navigation.PopModalAsync) }
- .Bottom().CenterHorizontal()
- .Text("Back to Snackbar MainPage")
+ backButton
}
- }.Center()
+ }
}.Padding(12));
}
}
diff --git a/samples/CommunityToolkit.Maui.Sample/Pages/Base/BasePage.cs b/samples/CommunityToolkit.Maui.Sample/Pages/Base/BasePage.cs
index c88679bddc..50801ecc53 100644
--- a/samples/CommunityToolkit.Maui.Sample/Pages/Base/BasePage.cs
+++ b/samples/CommunityToolkit.Maui.Sample/Pages/Base/BasePage.cs
@@ -1,9 +1,11 @@
using System.Diagnostics;
using CommunityToolkit.Maui.Sample.ViewModels;
+using Microsoft.Maui.Controls.PlatformConfiguration;
+using Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;
namespace CommunityToolkit.Maui.Sample.Pages;
-public abstract class BasePage(TViewModel viewModel) : BasePage(viewModel)
+public abstract class BasePage(TViewModel viewModel, bool shouldUseSafeArea = true) : BasePage(viewModel, shouldUseSafeArea)
where TViewModel : BaseViewModel
{
public new TViewModel BindingContext => (TViewModel)base.BindingContext;
@@ -11,11 +13,13 @@ public abstract class BasePage(TViewModel viewModel) : BasePage(view
public abstract class BasePage : ContentPage
{
- protected BasePage(object? viewModel = null)
+ protected BasePage(object? viewModel = null, bool shouldUseSafeArea = true)
{
BindingContext = viewModel;
Padding = 12;
+ On().SetUseSafeArea(shouldUseSafeArea);
+
if (string.IsNullOrWhiteSpace(Title))
{
Title = GetType().Name;
diff --git a/src/CommunityToolkit.Maui.Core/Views/Alert/Alert.macios.cs b/src/CommunityToolkit.Maui.Core/Views/Alert/Alert.macios.cs
index 6c595c2381..34925b1840 100644
--- a/src/CommunityToolkit.Maui.Core/Views/Alert/Alert.macios.cs
+++ b/src/CommunityToolkit.Maui.Core/Views/Alert/Alert.macios.cs
@@ -1,4 +1,6 @@
-namespace CommunityToolkit.Maui.Core.Views;
+using System.Diagnostics.CodeAnalysis;
+
+namespace CommunityToolkit.Maui.Core.Views;
///
/// Popup for iOS + MacCatalyst
@@ -10,9 +12,10 @@ public class Alert
///
/// Initialize Alert
///
- public Alert()
+ /// Should stretch container horizontally to fit the screen
+ public Alert(bool shouldFillAndExpandHorizontally = false)
{
- AlertView = [];
+ AlertView = new AlertView(shouldFillAndExpandHorizontally);
AlertView.ParentView.AddSubview(AlertView);
AlertView.ParentView.BringSubviewToFront(AlertView);
@@ -48,7 +51,7 @@ public Alert()
///
public void Dismiss()
{
- if (timer != null)
+ if (timer is not null)
{
timer.Invalidate();
timer.Dispose();
@@ -62,6 +65,7 @@ public void Dismiss()
///
/// Show the on the screen
///
+ [MemberNotNull(nameof(timer))]
public void Show()
{
AlertView.AnchorView = Anchor;
diff --git a/src/CommunityToolkit.Maui.Core/Views/Alert/AlertView.macios.cs b/src/CommunityToolkit.Maui.Core/Views/Alert/AlertView.macios.cs
index 78dbc3327d..1f729e2b55 100644
--- a/src/CommunityToolkit.Maui.Core/Views/Alert/AlertView.macios.cs
+++ b/src/CommunityToolkit.Maui.Core/Views/Alert/AlertView.macios.cs
@@ -1,4 +1,3 @@
-using System.Diagnostics.CodeAnalysis;
using CommunityToolkit.Maui.Core.Extensions;
namespace CommunityToolkit.Maui.Core.Views;
@@ -6,34 +5,42 @@ namespace CommunityToolkit.Maui.Core.Views;
///
/// for
///
-public class AlertView : UIView
+/// Should stretch container horizontally to fit the screen
+public class AlertView(bool shouldFillAndExpandHorizontally) : UIView
{
+ const int defaultSpacing = 10;
readonly List children = [];
///
/// Parent UIView
///
- public static UIView ParentView => Microsoft.Maui.Platform.UIApplicationExtensions.GetKeyWindow(UIApplication.SharedApplication) ?? throw new InvalidOperationException("KeyWindow is not found");
+ public UIView ParentView { get; } = Microsoft.Maui.Platform.UIApplicationExtensions.GetKeyWindow(UIApplication.SharedApplication) ?? throw new InvalidOperationException("KeyWindow is not found");
///
/// PopupView Children
///
public IReadOnlyList Children => children;
-
- ///
- /// on which Alert will appear. When null, will appear at bottom of screen.
- ///
- public UIView? AnchorView { get; set; }
-
+
///
///
///
public AlertViewVisualOptions VisualOptions { get; } = new();
+ ///
+ /// on which Alert will appear. When null, will appear at bottom of screen.
+ ///
+ public UIView? AnchorView { get; set; }
+
///
/// Container of
///
- protected UIStackView? Container { get; set; }
+ protected UIStackView Container { get; } = new()
+ {
+ Alignment = UIStackViewAlignment.Fill,
+ Distribution = UIStackViewDistribution.EqualSpacing,
+ Axis = UILayoutConstraintAxis.Horizontal,
+ TranslatesAutoresizingMaskIntoConstraints = false
+ };
///
/// Dismisses the Popup from the screen
@@ -44,7 +51,11 @@ public class AlertView : UIView
/// Adds a to
///
///
- public void AddChild(UIView child) => children.Add(child);
+ public void AddChild(UIView child)
+ {
+ children.Add(child);
+ Container.AddArrangedSubview(child);
+ }
///
/// Initializes
@@ -52,26 +63,44 @@ public class AlertView : UIView
public void Setup()
{
Initialize();
- ConstraintInParent();
+ SetParentConstraints();
}
- void ConstraintInParent()
+ ///
+ public override void LayoutSubviews()
{
- _ = Container ?? throw new InvalidOperationException($"{nameof(AlertView)}.{nameof(Initialize)} not called");
-
- const int defaultSpacing = 10;
+ base.LayoutSubviews();
+
if (AnchorView is null)
{
this.SafeBottomAnchor().ConstraintEqualTo(ParentView.SafeBottomAnchor(), -defaultSpacing).Active = true;
this.SafeTopAnchor().ConstraintGreaterThanOrEqualTo(ParentView.SafeTopAnchor(), defaultSpacing).Active = true;
}
+ else if (AnchorView.Superview is not null
+ && AnchorView.Superview.ConvertRectToView(AnchorView.Frame, null).Top < Container.Frame.Height + SafeAreaLayoutGuide.LayoutFrame.Bottom)
+ {
+ var top = AnchorView.Superview.Frame.Top + AnchorView.Frame.Height + defaultSpacing;
+ this.SafeTopAnchor().ConstraintEqualTo(ParentView.TopAnchor, top).Active = true;
+ }
else
{
- this.SafeBottomAnchor().ConstraintEqualTo(AnchorView.SafeTopAnchor(), -defaultSpacing).Active = true;
+ this.SafeBottomAnchor().ConstraintEqualTo(AnchorView.SafeTopAnchor(), 0).Active = true;
}
+ }
- this.SafeLeadingAnchor().ConstraintGreaterThanOrEqualTo(ParentView.SafeLeadingAnchor(), defaultSpacing).Active = true;
- this.SafeTrailingAnchor().ConstraintLessThanOrEqualTo(ParentView.SafeTrailingAnchor(), -defaultSpacing).Active = true;
+ void SetParentConstraints()
+ {
+ if (shouldFillAndExpandHorizontally)
+ {
+ this.SafeLeadingAnchor().ConstraintEqualTo(ParentView.SafeLeadingAnchor(), defaultSpacing).Active = true;
+ this.SafeTrailingAnchor().ConstraintEqualTo(ParentView.SafeTrailingAnchor(), -defaultSpacing).Active = true;
+ }
+ else
+ {
+ this.SafeLeadingAnchor().ConstraintGreaterThanOrEqualTo(ParentView.SafeLeadingAnchor(), defaultSpacing).Active = true;
+ this.SafeTrailingAnchor().ConstraintLessThanOrEqualTo(ParentView.SafeTrailingAnchor(), -defaultSpacing).Active = true;
+ }
+
this.SafeCenterXAnchor().ConstraintEqualTo(ParentView.SafeCenterXAnchor()).Active = true;
Container.SafeLeadingAnchor().ConstraintEqualTo(this.SafeLeadingAnchor(), defaultSpacing).Active = true;
@@ -79,23 +108,9 @@ void ConstraintInParent()
Container.SafeBottomAnchor().ConstraintEqualTo(this.SafeBottomAnchor(), -defaultSpacing).Active = true;
Container.SafeTopAnchor().ConstraintEqualTo(this.SafeTopAnchor(), defaultSpacing).Active = true;
}
-
- [MemberNotNull(nameof(Container))]
+
void Initialize()
{
- Container = new UIStackView
- {
- Alignment = UIStackViewAlignment.Fill,
- Distribution = UIStackViewDistribution.EqualSpacing,
- Axis = UILayoutConstraintAxis.Horizontal,
- TranslatesAutoresizingMaskIntoConstraints = false
- };
-
- foreach (var view in Children)
- {
- Container.AddArrangedSubview(view);
- }
-
TranslatesAutoresizingMaskIntoConstraints = false;
AddSubview(Container);
diff --git a/src/CommunityToolkit.Maui.Core/Views/Snackbar/PlatformSnackbar.macios.cs b/src/CommunityToolkit.Maui.Core/Views/Snackbar/PlatformSnackbar.macios.cs
index dd27551754..1220841364 100644
--- a/src/CommunityToolkit.Maui.Core/Views/Snackbar/PlatformSnackbar.macios.cs
+++ b/src/CommunityToolkit.Maui.Core/Views/Snackbar/PlatformSnackbar.macios.cs
@@ -31,7 +31,7 @@ public PlatformSnackbar(
UIColor actionTextColor,
UIFont actionButtonFont,
nfloat padding)
- : base(message, backgroundColor, cornerRadius, textColor, textFont, characterSpacing, padding)
+ : base(message, backgroundColor, cornerRadius, textColor, textFont, characterSpacing, padding, true)
{
padding += DefaultPadding;
diff --git a/src/CommunityToolkit.Maui.Core/Views/Toast/PlatformToast.macios.cs b/src/CommunityToolkit.Maui.Core/Views/Toast/PlatformToast.macios.cs
index 702ff17af6..60fd5bbdf7 100644
--- a/src/CommunityToolkit.Maui.Core/Views/Toast/PlatformToast.macios.cs
+++ b/src/CommunityToolkit.Maui.Core/Views/Toast/PlatformToast.macios.cs
@@ -24,6 +24,7 @@ public class PlatformToast : Alert, IDisposable
/// Toast Font
/// Toast Message Character Spacing
/// Toast Padding
+ /// Should stretch container horizontally to fit the screen
public PlatformToast(
string message,
UIColor backgroundColor,
@@ -31,7 +32,8 @@ public PlatformToast(
UIColor textColor,
UIFont font,
double characterSpacing,
- nfloat padding)
+ nfloat padding,
+ bool shouldFillAndExpandHorizontally = false) : base(shouldFillAndExpandHorizontally)
{
padding += DefaultPadding;