diff --git a/src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/maui-sc.aotprofile.txt b/src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/maui-sc.aotprofile.txt
index 9c70c4bb6c67..cf63344a2780 100644
--- a/src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/maui-sc.aotprofile.txt
+++ b/src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/maui-sc.aotprofile.txt
@@ -8028,14 +8028,12 @@ Methods:
void Microsoft.Maui.PlatformDispatcher:.cctor ()
void Microsoft.Maui.PlatformDispatcher:.ctor (intptr,Android.Runtime.JniHandleOwnership)
void Microsoft.Maui.PlatformInterop:.cctor ()
- void Microsoft.Maui.PlatformInterop:DrawMauiDrawablePath (Android.Graphics.Drawables.PaintDrawable,Android.Graphics.Canvas,int,int,Android.Graphics.Path,Android.Graphics.Paint)
void Microsoft.Maui.PlatformInterop:LoadImageFromFont (Android.Content.Context,int,string,Android.Graphics.Typeface,single,Microsoft.Maui.IImageLoaderCallback)
void Microsoft.Maui.PlatformInterop:LoadImageFromFont (Android.Widget.ImageView,int,string,Android.Graphics.Typeface,single,Microsoft.Maui.IImageLoaderCallback)
void Microsoft.Maui.PlatformInterop:RemoveFromParent (Android.Views.View)
void Microsoft.Maui.PlatformInterop:RequestLayoutIfNeeded (Android.Views.View)
void Microsoft.Maui.PlatformInterop:Set (Android.Views.View,int,int,int,int,bool,single,single,single,single,single,single,single,single,single,single)
void Microsoft.Maui.PlatformInterop:SetColorFilter (Android.Graphics.Drawables.Drawable,int,int)
- void Microsoft.Maui.PlatformInterop:SetPaintValues (Android.Graphics.Paint,single,Android.Graphics.Paint/Join,Android.Graphics.Paint/Cap,single,Android.Graphics.PathEffect)
void Microsoft.Maui.PlatformInterop:SetPivotXIfNeeded (Android.Views.View,single)
void Microsoft.Maui.PlatformInterop:SetPivotYIfNeeded (Android.Views.View,single)
void Microsoft.Maui.PlatformMauiAppCompatActivity:.cctor ()
diff --git a/src/Controls/src/Core/Compatibility/Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs
index 660477d5b947..3c32df43d13e 100644
--- a/src/Controls/src/Core/Compatibility/Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs
+++ b/src/Controls/src/Core/Compatibility/Handlers/FlyoutPage/iOS/PhoneFlyoutPageRenderer.cs
@@ -365,6 +365,24 @@ void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
void LayoutChildren(bool animated)
{
var frame = Element.Bounds.ToCGRect();
+
+ // Apply safe area insets to the FlyoutPage container if UseSafeArea is enabled.
+ // This ensures the Flyout content (e.g., CollectionView) renders below the status bar/notch
+ // on iOS devices with notches (iPhone X and newer). Without this adjustment, the container
+ // would start at Y=0, causing content to overlap with the status bar.
+ // https://github.com/dotnet/maui/issues/29170
+ if (Element is FlyoutPage flyoutPage && flyoutPage is ISafeAreaView sav &&
+ !sav.IgnoreSafeArea && OperatingSystem.IsIOSVersionAtLeast(11))
+ {
+ var safeAreaTopInset = View.SafeAreaInsets.Top;
+
+ if (safeAreaTopInset > 0)
+ {
+ frame.Y = safeAreaTopInset;
+ frame.Height -= safeAreaTopInset;
+ }
+ }
+
var flyoutFrame = frame;
nfloat opacity = 1;
diff --git a/src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs
index a120b1cdee60..f7928950539e 100644
--- a/src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs
+++ b/src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs
@@ -946,6 +946,17 @@ void UpdateBarTextColor()
NavigationBar.TintColor = iconColor == null || NavPage.OnThisPlatform().GetStatusBarTextColorMode() == StatusBarTextColorMode.DoNotAdjust
? UINavigationBar.Appearance.TintColor
: iconColor.ToPlatform();
+
+ if ((OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26)) && NavigationBar.TintColor is not null)
+ {
+ if (VisibleViewController?.NavigationItem?.RightBarButtonItems is UIBarButtonItem[] items)
+ {
+ foreach (var item in items)
+ {
+ item.TintColor = NavigationBar.TintColor;
+ }
+ }
+ }
}
void SetStatusBarStyle()
@@ -1064,7 +1075,7 @@ internal static void SetFlyoutLeftBarButton(UIViewController containerController
// We only check height because the navigation bar constrains vertical space (44pt height),
// but allows horizontal flexibility. Width can vary based on icon design and content,
// while height must fit within the fixed navigation bar bounds to avoid clipping.
-
+
// if the image is bigger than the default available size, resize it
if (icon is not null)
{
@@ -1602,12 +1613,28 @@ UIImage GetEmptyBackIndicatorImage()
return empty;
}
+ ///
+ /// Called when the view controller's view transitions to a new size.
+ /// On iPad iOS 26+, manually updates TitleView frame to handle Stage Manager window resizing.
+ ///
public override void ViewWillTransitionToSize(SizeF toSize, IUIViewControllerTransitionCoordinator coordinator)
{
base.ViewWillTransitionToSize(toSize, coordinator);
if (UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Pad)
+ {
UpdateLeftBarButtonItem();
+
+ // For iOS 26+, force TitleView to re-layout on window size changes (iPad Stage Manager, multitasking)
+ // Complements TraitCollectionDidChange handling (device rotation) added in #32815
+ if (OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26))
+ {
+ coordinator.AnimateAlongsideTransition(_ =>
+ {
+ UpdateTitleViewFrameForOrientation();
+ }, null);
+ }
+ }
}
public override void TraitCollectionDidChange(UITraitCollection previousTraitCollection)
@@ -1937,6 +1964,14 @@ void UpdateToolbarItems()
}
NavigationItem.SetRightBarButtonItems(primaries is null ? [] : primaries.ToArray(), false);
+ if ((OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26)) && primaries is not null && _navigation.TryGetTarget(out NavigationRenderer navigationRenderer)
+ && navigationRenderer.NavigationBar?.TintColor is UIColor tintColor)
+ {
+ foreach (var item in primaries)
+ {
+ item.TintColor = tintColor;
+ }
+ }
if (_navigation.TryGetTarget(out NavigationRenderer n))
{
diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellContentFragment.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellContentFragment.cs
index ad0f11feeada..c3179651a5ca 100644
--- a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellContentFragment.cs
+++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellContentFragment.cs
@@ -222,6 +222,21 @@ void Destroy()
_shellPageContainer = null;
}
+ internal void DisposePage()
+ {
+ if (_destroyed)
+ {
+ return;
+ }
+ Destroy();
+
+ if (_page is not null)
+ {
+ _page.DisconnectHandlers();
+ _page = null;
+ }
+ }
+
protected override void Dispose(bool disposing)
{
if (_disposed)
diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellFlyoutRecyclerAdapter.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellFlyoutRecyclerAdapter.cs
index 912e1a407433..558f99d4c436 100644
--- a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellFlyoutRecyclerAdapter.cs
+++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellFlyoutRecyclerAdapter.cs
@@ -139,6 +139,16 @@ public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int
var container = new ContainerView(parent.Context, content, MauiContext);
container.MatchWidth = true;
+
+ // In Auto or Disabled scroll modes, RecyclerView passes an EXACTLY heightMeasureSpec,
+ // causing items to be measured to the full RecyclerView height.
+ // Setting MeasureHeight = true forces UNSPECIFIED mode, allowing items to use their natural height (~48dp).
+ // This enables RecyclerView to detect when scrolling is needed and to create all required view holders.
+ if (_shellContext.Shell.FlyoutVerticalScrollMode != ScrollMode.Enabled)
+ {
+ container.MeasureHeight = true;
+ }
+
container.LayoutParameters = new LP(LP.MatchParent, LP.WrapContent);
linearLayout.AddView(container);
diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellFlyoutRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellFlyoutRenderer.cs
index b08347bc387e..c243bacdc684 100644
--- a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellFlyoutRenderer.cs
+++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellFlyoutRenderer.cs
@@ -260,8 +260,15 @@ void AddFlyoutContentToLayoutIfNeeded(FlyoutBehavior behavior)
protected virtual void OnShellPropertyChanged(object sender, PropertyChangedEventArgs e)
{
- if (_flyoutContent == null)
+ if (e.PropertyName == Shell.FlowDirectionProperty.PropertyName)
+ {
+ UpdateFlowDirection();
+ }
+
+ if (_flyoutContent is null)
+ {
return;
+ }
if (e.PropertyName == Shell.FlyoutIsPresentedProperty.PropertyName)
{
@@ -275,10 +282,6 @@ protected virtual void OnShellPropertyChanged(object sender, PropertyChangedEven
UpdateDrawerState();
}
- else if (e.PropertyName == Shell.FlowDirectionProperty.PropertyName)
- {
- UpdateFlowDirection();
- }
}
void UpdateDrawerState()
diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellFlyoutTemplatedContentRenderer.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellFlyoutTemplatedContentRenderer.cs
index 4a5ef866de94..75c19f1ffb26 100644
--- a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellFlyoutTemplatedContentRenderer.cs
+++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellFlyoutTemplatedContentRenderer.cs
@@ -310,6 +310,7 @@ protected virtual void UpdateFlyoutContent()
_rootView.AddView(_flyoutContentView, index);
UpdateContentPadding();
+ UpdateVerticalScrollMode();
}
@@ -932,7 +933,6 @@ public RecyclerViewContainer(Context context) : base(context)
{
SetClipToPadding(false);
SetLayoutManager(_layoutManager = new ScrollLayoutManager(context, (int)Orientation.Vertical, false));
- SetLayoutManager(new LinearLayoutManager(context, (int)Orientation.Vertical, false));
}
protected override void Dispose(bool disposing)
diff --git a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellItemRendererBase.cs b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellItemRendererBase.cs
index 3efd98363373..63cce3dfdcfa 100644
--- a/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellItemRendererBase.cs
+++ b/src/Controls/src/Core/Compatibility/Handlers/Shell/Android/ShellItemRendererBase.cs
@@ -167,6 +167,11 @@ protected virtual Task HandleFragmentUpdate(ShellNavigationSource navSourc
if (ChildFragmentManager.Contains(removeFragment.Fragment) && !isForCurrentTab && removeFragment != _currentFragment)
RemoveFragment(removeFragment.Fragment);
_fragmentMap.Remove(page);
+
+ if (removeFragment is ShellContentFragment shellFragment)
+ {
+ shellFragment.DisposePage();
+ }
}
if (!isForCurrentTab && removeFragment != _currentFragment)
diff --git a/src/Controls/src/Core/Editor/Editor.Android.cs b/src/Controls/src/Core/Editor/Editor.Android.cs
index 851df83ca820..b96d0eff8814 100644
--- a/src/Controls/src/Core/Editor/Editor.Android.cs
+++ b/src/Controls/src/Core/Editor/Editor.Android.cs
@@ -18,5 +18,22 @@ public static void MapText(IEditorHandler handler, Editor editor)
Platform.EditTextExtensions.UpdateText(handler.PlatformView, editor);
}
+
+ // TODO: Material3 - make it public in .net 11
+ internal static void MapText(EditorHandler2 handler, Editor editor)
+ {
+ if (handler.PlatformView is null)
+ {
+ return;
+ }
+
+ if (handler.DataFlowDirection == DataFlowDirection.FromPlatform)
+ {
+ Platform.EditTextExtensions.UpdateTextFromPlatform(handler.PlatformView, editor);
+ return;
+ }
+
+ Platform.EditTextExtensions.UpdateText(handler.PlatformView, editor);
+ }
}
}
diff --git a/src/Controls/src/Core/Editor/Editor.Mapper.cs b/src/Controls/src/Core/Editor/Editor.Mapper.cs
index d2dc2b69a706..0258acedebea 100644
--- a/src/Controls/src/Core/Editor/Editor.Mapper.cs
+++ b/src/Controls/src/Core/Editor/Editor.Mapper.cs
@@ -15,6 +15,16 @@ public partial class Editor
EditorHandler.Mapper.ReplaceMapping(nameof(Text), MapText);
EditorHandler.Mapper.ReplaceMapping(nameof(TextTransform), MapText);
+#if ANDROID
+ if (RuntimeFeature.IsMaterial3Enabled)
+ {
+ EditorHandler2.Mapper.ReplaceMapping(nameof(Text), MapText);
+ EditorHandler2.Mapper.ReplaceMapping(nameof(TextTransform), MapText);
+ EditorHandler2.Mapper.AppendToMapping(nameof(VisualElement.IsFocused), InputView.MapIsFocused);
+ EditorHandler2.CommandMapper.PrependToMapping(nameof(IEditor.Focus), InputView.MapFocus);
+ }
+#endif
+
#if IOS || ANDROID
EditorHandler.Mapper.AppendToMapping(nameof(VisualElement.IsFocused), InputView.MapIsFocused);
EditorHandler.Mapper.AppendToMapping(nameof(VisualElement.IsVisible), InputView.MapIsVisible);
diff --git a/src/Controls/src/Core/FlyoutPage/FlyoutPage.cs b/src/Controls/src/Core/FlyoutPage/FlyoutPage.cs
index c2570bdfb132..580652749e05 100644
--- a/src/Controls/src/Core/FlyoutPage/FlyoutPage.cs
+++ b/src/Controls/src/Core/FlyoutPage/FlyoutPage.cs
@@ -351,6 +351,12 @@ void OnWindowChanged(object sender, EventArgs e)
void OnMainDisplayInfoChanged(object sender, DisplayInfoChangedEventArgs e)
{
Handler?.UpdateValue(nameof(FlyoutBehavior));
+
+#if ANDROID || WINDOWS
+ // Trigger toolbar re-evaluation on orientation change. iOS handles this natively
+ // via PhoneFlyoutPageRenderer.ViewWillTransitionToSize().
+ OnPropertyChanged(nameof(FlyoutLayoutBehavior));
+#endif
}
IView IFlyoutView.Flyout => this.Flyout;
diff --git a/src/Controls/src/Core/FontExtensions.cs b/src/Controls/src/Core/FontExtensions.cs
index 639fcb93cf0d..2ef5d6a833bb 100644
--- a/src/Controls/src/Core/FontExtensions.cs
+++ b/src/Controls/src/Core/FontExtensions.cs
@@ -34,5 +34,20 @@ public static Font ToFont(this IFontElement element, double? defaultSize = null)
return Font.OfSize(element.FontFamily, size, enableScaling: element.FontAutoScalingEnabled).WithAttributes(element.FontAttributes);
}
+
+ internal static Font GetEffectiveFont(this Span span, double defaultFontSize, Font? defaultFont)
+ {
+ var fontFamily = span.IsSet(Span.FontFamilyProperty) ? span.FontFamily : defaultFont?.Family;
+ var fontSize = span.IsSet(Span.FontSizeProperty) ? span.FontSize : defaultFontSize;
+ var fontAttributes = span.IsSet(Span.FontAttributesProperty)
+ ? span.FontAttributes
+ : (defaultFont?.GetFontAttributes() ?? FontAttributes.None);
+ var autoScaling = span.IsSet(Span.FontAutoScalingEnabledProperty)
+ ? span.FontAutoScalingEnabled
+ : (defaultFont?.AutoScalingEnabled ?? true);
+
+ return Font.OfSize(fontFamily, fontSize, enableScaling: autoScaling)
+ .WithAttributes(fontAttributes);
+ }
}
}
diff --git a/src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs b/src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs
index 2c2a75a63074..2cacd2f34def 100644
--- a/src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs
+++ b/src/Controls/src/Core/Handlers/Items/Android/MauiRecyclerView.cs
@@ -524,6 +524,28 @@ protected virtual void LayoutPropertyChanged(object sender, PropertyChangedEvent
}
}
+ public override bool OnTouchEvent(MotionEvent e)
+ {
+ // If ItemsView is disabled, don't handle touch events
+ if (ItemsView?.IsEnabled == false)
+ {
+ return false;
+ }
+
+ return base.OnTouchEvent(e);
+ }
+
+ public override bool OnInterceptTouchEvent(MotionEvent e)
+ {
+ // If ItemsView is disabled, intercept all touch events to prevent interactions
+ if (ItemsView?.IsEnabled == false)
+ {
+ return true;
+ }
+
+ return base.OnInterceptTouchEvent(e);
+ }
+
protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
base.OnLayout(changed, l, t, r, b);
diff --git a/src/Controls/src/Core/Handlers/Items/Android/RecyclerViewScrollListener.cs b/src/Controls/src/Core/Handlers/Items/Android/RecyclerViewScrollListener.cs
index 1464f866368f..8f285d875aae 100644
--- a/src/Controls/src/Core/Handlers/Items/Android/RecyclerViewScrollListener.cs
+++ b/src/Controls/src/Core/Handlers/Items/Android/RecyclerViewScrollListener.cs
@@ -12,6 +12,7 @@ public class RecyclerViewScrollListener : Recycler
int _horizontalOffset, _verticalOffset;
TItemsView _itemsView;
readonly bool _getCenteredItemOnXAndY = false;
+ bool _hasCompletedFirstLayout = false;
public RecyclerViewScrollListener(TItemsView itemsView, ItemsViewAdapter itemsViewAdapter) : this(itemsView, itemsViewAdapter, false)
{
@@ -28,6 +29,8 @@ public RecyclerViewScrollListener(TItemsView itemsView, ItemsViewAdapter itemsViewAdapter)
{
ItemsViewAdapter = itemsViewAdapter;
+ // Reset flag when adapter changes to handle ItemsSource updates
+ _hasCompletedFirstLayout = false;
}
public override void OnScrolled(RecyclerView recyclerView, int dx, int dy)
@@ -38,6 +41,17 @@ public override void OnScrolled(RecyclerView recyclerView, int dx, int dy)
_horizontalOffset = itemCount == 0 ? 0 : _horizontalOffset + dx;
_verticalOffset = itemCount == 0 ? 0 : _verticalOffset + dy;
+ // Prevent the Scrolled event from firing on the very first layout callback only.
+ // This is the initial OnScrolled(0,0) call when the view is first laid out.
+ // After that, layout is marked as complete and all subsequent scroll events are allowed.
+ if (!_hasCompletedFirstLayout && !recyclerView.IsLaidOut && dx == 0 && dy == 0)
+ {
+ return;
+ }
+
+ // Mark that first layout has been processed - all future scrolls should fire events
+ _hasCompletedFirstLayout = true;
+
var (First, Center, Last) = GetVisibleItemsIndex(recyclerView);
var itemsViewScrolledEventArgs = new ItemsViewScrolledEventArgs
{
@@ -54,21 +68,30 @@ public override void OnScrolled(RecyclerView recyclerView, int dx, int dy)
// Don't send RemainingItemsThresholdReached event for non-linear layout managers
// This can also happen if a layout pass has not happened yet
- if (Last == -1)
+ if (Last == -1 || ItemsViewAdapter is null || _itemsView.RemainingItemsThreshold == -1)
+ {
+ return;
+ }
+
+ var itemsSource = ItemsViewAdapter.ItemsSource;
+ int headerValue = itemsSource.HasHeader ? 1 : 0;
+ int footerValue = itemsSource.HasFooter ? 1 : 0;
+
+ // Calculate actual data item count (excluding header and footer positions)
+ int actualItemCount = ItemsViewAdapter.ItemCount - footerValue - headerValue;
+
+ // Ensure we're within the data items region (not in header/footer)
+ if (Last < headerValue || Last > actualItemCount)
+ {
return;
+ }
+
+ // Check if we're at or within threshold distance from the last data item
+ bool isThresholdReached = (Last == actualItemCount - 1) || (actualItemCount - 1 - Last <= _itemsView.RemainingItemsThreshold);
- switch (_itemsView.RemainingItemsThreshold)
+ if (isThresholdReached)
{
- case -1:
- return;
- case 0:
- if (Last == ItemsViewAdapter.ItemsSource.Count - 1)
- _itemsView.SendRemainingItemsThresholdReached();
- break;
- default:
- if (ItemsViewAdapter.ItemsSource.Count - 1 - Last <= _itemsView.RemainingItemsThreshold)
- _itemsView.SendRemainingItemsThresholdReached();
- break;
+ _itemsView.SendRemainingItemsThresholdReached();
}
}
diff --git a/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewDelegator.cs b/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewDelegator.cs
index 622e72211714..428d8c992c80 100644
--- a/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewDelegator.cs
+++ b/src/Controls/src/Core/Handlers/Items/iOS/ItemsViewDelegator.cs
@@ -29,12 +29,9 @@ public override void Scrolled(UIScrollView scrollView)
{
var (visibleItems, firstVisibleItemIndex, centerItemIndex, lastVisibleItemIndex) = GetVisibleItemsIndex();
- if (!visibleItems)
- return;
-
var contentInset = scrollView.ContentInset;
- var contentOffsetX = scrollView.ContentOffset.X + contentInset.Left;
- var contentOffsetY = scrollView.ContentOffset.Y + contentInset.Top;
+ var contentOffsetX = !visibleItems ? 0 : scrollView.ContentOffset.X + contentInset.Left;
+ var contentOffsetY = !visibleItems ? 0 : scrollView.ContentOffset.Y + contentInset.Top;
var itemsViewScrolledEventArgs = new ItemsViewScrolledEventArgs
{
@@ -58,6 +55,11 @@ public override void Scrolled(UIScrollView scrollView)
PreviousHorizontalOffset = (float)contentOffsetX;
PreviousVerticalOffset = (float)contentOffsetY;
+ if (!visibleItems)
+ {
+ return;
+ }
+
switch (itemsView.RemainingItemsThreshold)
{
case -1:
diff --git a/src/Controls/src/Core/Handlers/Items2/ItemsViewHandler2.iOS.cs b/src/Controls/src/Core/Handlers/Items2/ItemsViewHandler2.iOS.cs
index c8e8ed19df3d..b56dcd9f4ef9 100644
--- a/src/Controls/src/Core/Handlers/Items2/ItemsViewHandler2.iOS.cs
+++ b/src/Controls/src/Core/Handlers/Items2/ItemsViewHandler2.iOS.cs
@@ -146,7 +146,8 @@ protected virtual void ScrollToRequested(object sender, ScrollToRequestEventArgs
return;
}
- var position = Items.ScrollToPositionExtensions.ToCollectionViewScrollPosition(args.ScrollToPosition, UICollectionViewScrollDirection.Vertical);
+ var scrollDirection = Controller.GetScrollDirection();
+ var position = Items.ScrollToPositionExtensions.ToCollectionViewScrollPosition(args.ScrollToPosition, scrollDirection);
Controller.CollectionView.ScrollToItem(indexPath,
position, args.IsAnimated);
diff --git a/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs b/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs
index ec38e061e904..5fd94e697991 100644
--- a/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs
+++ b/src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs
@@ -30,12 +30,9 @@ public override void Scrolled(UIScrollView scrollView)
{
var (visibleItems, firstVisibleItemIndex, centerItemIndex, lastVisibleItemIndex) = GetVisibleItemsIndex();
- if (!visibleItems)
- return;
-
var contentInset = scrollView.ContentInset;
- var contentOffsetX = scrollView.ContentOffset.X + contentInset.Left;
- var contentOffsetY = scrollView.ContentOffset.Y + contentInset.Top;
+ var contentOffsetX = !visibleItems ? 0 : scrollView.ContentOffset.X + contentInset.Left;
+ var contentOffsetY = !visibleItems ? 0 : scrollView.ContentOffset.Y + contentInset.Top;
var itemsViewScrolledEventArgs = new ItemsViewScrolledEventArgs
{
@@ -59,6 +56,11 @@ public override void Scrolled(UIScrollView scrollView)
PreviousHorizontalOffset = (float)contentOffsetX;
PreviousVerticalOffset = (float)contentOffsetY;
+ if (!visibleItems)
+ {
+ return;
+ }
+
switch (itemsView.RemainingItemsThreshold)
{
case -1:
diff --git a/src/Controls/src/Core/Handlers/Shell/ShellItemHandler.Windows.cs b/src/Controls/src/Core/Handlers/Shell/ShellItemHandler.Windows.cs
index 3850a5f3aa7b..c5c8725ba264 100644
--- a/src/Controls/src/Core/Handlers/Shell/ShellItemHandler.Windows.cs
+++ b/src/Controls/src/Core/Handlers/Shell/ShellItemHandler.Windows.cs
@@ -115,11 +115,19 @@ protected override void DisconnectHandler(FrameworkElement platformView)
}
if (_shellItem is IShellItemController shellItemController)
+ {
shellItemController.ItemsCollectionChanged -= OnItemsChanged;
- if (VirtualView.Parent is Shell shell)
- {
- shell.Navigated -= OnShellNavigated;
+ if (VirtualView.Parent is Shell shell)
+ {
+ shell.Navigated -= OnShellNavigated;
+ }
+
+ foreach (var item in shellItemController.GetItems())
+ {
+ item.PropertyChanged -= OnShellItemPropertyChanged;
+ }
+
}
}
@@ -203,6 +211,7 @@ internal void MapMenuItems()
foreach (var item in shellItemController.GetItems())
{
+ item.PropertyChanged += OnShellItemPropertyChanged;
if (Routing.IsImplicit(item))
items.Add(item.CurrentItem);
else
@@ -254,6 +263,7 @@ internal void MapMenuItems()
void SetValues(BaseShellItem bsi, NavigationViewItemViewModel vm)
{
vm.Content = bsi.Title;
+ vm.IsEnabled = bsi.IsEnabled;
var iconSource = bsi.Icon?.ToIconSource(MauiContext!);
if (iconSource != null)
@@ -409,6 +419,33 @@ void OnSearchBoxQuerySubmitted(Microsoft.UI.Xaml.Controls.AutoSuggestBox sender,
}
}
+ void OnShellItemPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (_mainLevelTabs == null || sender is not BaseShellItem shellItem)
+ {
+ return;
+ }
+
+ for (int i = 0; i < _mainLevelTabs.Count; i++)
+ {
+ if (_mainLevelTabs[i].Data != sender)
+ {
+ continue;
+ }
+
+ switch (e.PropertyName)
+ {
+ case nameof(BaseShellItem.IsEnabled):
+ _mainLevelTabs[i].IsEnabled = shellItem.IsEnabled;
+ break;
+ case nameof(BaseShellItem.Title):
+ _mainLevelTabs[i].Content = shellItem.Title;
+ break;
+ }
+ return;
+ }
+ }
+
void OnCurrentSearchHandlerPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (_currentSearchHandler is null)
diff --git a/src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs b/src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs
index ecd58db7cae4..cd006d288762 100644
--- a/src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs
+++ b/src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs
@@ -69,25 +69,46 @@ internal static IMauiHandlersCollection AddControlsHandlers(this IMauiHandlersCo
#else
handlersCollection.AddHandler();
handlersCollection.AddHandler();
+#endif
+#if ANDROID
+ if (RuntimeFeature.IsMaterial3Enabled)
+ {
+ handlersCollection.AddHandler
diff --git a/src/Templates/src/templates/maui-mobile/Pages/ProjectDetailPage.xaml b/src/Templates/src/templates/maui-mobile/Pages/ProjectDetailPage.xaml
index 3b3b0113b3a4..0d18f2322c0c 100644
--- a/src/Templates/src/templates/maui-mobile/Pages/ProjectDetailPage.xaml
+++ b/src/Templates/src/templates/maui-mobile/Pages/ProjectDetailPage.xaml
@@ -167,15 +167,22 @@
SemanticProperties.Description="Clean up"
/>
-
-
+
+
+
+
+
-
+
-
-
+
+