Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,29 @@ TitleViewContainer CreateTitleViewContainer(View titleView)
return new TitleViewContainer(titleView);
}

/// <summary>
/// Re-applies the navigation bar frame to the current TitleView container.
/// On iOS 26+ the TitleView container uses autoresizing masks with an explicitly set frame
/// (see <see cref="CreateTitleViewContainer"/>), so the frame is not automatically recomputed
/// when the navigation bar resizes during rotation or window size changes. This explicitly
/// resizes the container to match the navigation bar's new dimensions.
/// </summary>
internal void UpdateTitleViewFrameForOrientation()
{
if (NavigationItem?.TitleView is not TitleViewContainer titleViewContainer)
{
return;
}

var navigationBarFrame = ViewController?.NavigationController?.NavigationBar.Frame;
if (navigationBarFrame.HasValue)
{
titleViewContainer.Frame = new CGRect(0, 0, navigationBarFrame.Value.Width, navigationBarFrame.Value.Height);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] iOS Shell layoutUpdateTitleViewFrameForOrientation() resizes the native TitleViewContainer.Frame, but it does not refresh the container's Height value used by UIContainerView.LayoutSubviews() when MatchHeight is true. On iPhone rotation the navigation bar height can change between portrait and landscape, so the container frame can match the new bar while the MAUI title view is still measured/arranged with the previous bar height, causing vertical clipping or stale layout. Please update the stored height together with the frame, or route this through a helper that keeps Frame and Height in sync, and cover the rotated height/clipping case in the regression test.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] Layout Measure-Arrange - The orientation helper updates the native Frame but does not synchronize TitleViewContainer.Height, even though the constructor sets Height because UIContainerView uses it for MatchHeight measurement/arrange. On rotations where the navigation bar height changes, the MAUI TitleView can be arranged with stale height. Set titleViewContainer.Height = navigationBarFrame.Value.Height before LayoutIfNeeded().

titleViewContainer.Height = navigationBarFrame.Value.Height;
titleViewContainer.LayoutIfNeeded();
}
}

void OnTitleViewParentSet(object? sender, EventArgs e)
{
if (sender is Element element)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using CoreGraphics;
using Foundation;
using Microsoft.Maui.Controls.Handlers.Compatibility;
using Microsoft.Maui.Controls.Internals;
Expand Down Expand Up @@ -303,6 +304,41 @@ public override void DidMoveToParentViewController(UIViewController parent)
base.DidMoveToParentViewController(parent);
}

public override void ViewWillTransitionToSize(CGSize toSize, IUIViewControllerTransitionCoordinator coordinator)
{
base.ViewWillTransitionToSize(toSize, coordinator);

// On iOS 26+ the TitleView container uses autoresizing masks with an explicitly set frame,
// so it does not automatically resize when the navigation bar changes width during rotation.
// Re-apply the frame for the pushed pages' TitleViews alongside the transition.
if (OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26))
{
coordinator.AnimateAlongsideTransition(_ =>
{
foreach (var tracker in _trackers.Values)
{
(tracker as ShellPageRendererTracker)?.UpdateTitleViewFrameForOrientation();
}
}, null);
}
}

public override void TraitCollectionDidChange(UITraitCollection previousTraitCollection)
{
base.TraitCollectionDidChange(previousTraitCollection);
if (previousTraitCollection?.VerticalSizeClass != TraitCollection.VerticalSizeClass ||
previousTraitCollection?.HorizontalSizeClass != TraitCollection.HorizontalSizeClass)
{
if (OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26))
{
foreach (var tracker in _trackers.Values)
{
(tracker as ShellPageRendererTracker)?.UpdateTitleViewFrameForOrientation();
}
}
}
}

public override void ViewDidLoad()
{
if (_disposed)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,30 @@ public override void ViewWillTransitionToSize(CGSize toSize, IUIViewControllerTr
{
base.ViewWillTransitionToSize(toSize, coordinator);
_isRotating = true;

// On iOS 26+ the TitleView container uses autoresizing masks with an explicitly set frame,
// so it does not automatically resize when the navigation bar changes width during rotation.
// Re-apply the frame alongside the transition so the TitleView fills the navigation bar.
if (OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26))
{
coordinator.AnimateAlongsideTransition(_ =>
{
(_tracker as ShellPageRendererTracker)?.UpdateTitleViewFrameForOrientation();
}, null);
}
}

public override void TraitCollectionDidChange(UITraitCollection previousTraitCollection)
{
base.TraitCollectionDidChange(previousTraitCollection);
if (previousTraitCollection?.VerticalSizeClass != TraitCollection.VerticalSizeClass ||
previousTraitCollection?.HorizontalSizeClass != TraitCollection.HorizontalSizeClass)
{
if (OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26))
{
(_tracker as ShellPageRendererTracker)?.UpdateTitleViewFrameForOrientation();
}
}
}

public override void ViewDidLoad()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ override Microsoft.Maui.Controls.Handlers.Items2.StructuredItemsViewController2<
~override Microsoft.Maui.Controls.Platform.Compatibility.ShellFlyoutRenderer.ViewWillTransitionToSize(CoreGraphics.CGSize toSize, UIKit.IUIViewControllerTransitionCoordinator coordinator) -> void
override Microsoft.Maui.Controls.Platform.Compatibility.ShellItemRenderer.ViewDidAppear(bool animated) -> void
~override Microsoft.Maui.Controls.Platform.Compatibility.ShellSectionRenderer.DidMoveToParentViewController(UIKit.UIViewController parent) -> void
*REMOVED*~override Microsoft.Maui.Controls.Platform.Compatibility.ShellSectionRootRenderer.TraitCollectionDidChange(UIKit.UITraitCollection previousTraitCollection) -> void
override Microsoft.Maui.Controls.Platform.Compatibility.ShellTableViewController.LoadView() -> void
override Microsoft.Maui.Controls.Shapes.Shape.OnPropertyChanged(string? propertyName = null) -> void
override Microsoft.Maui.Controls.SwipeItemView.IsEnabledCore.get -> bool
Expand All @@ -17,3 +16,5 @@ override Microsoft.Maui.Controls.TitleBar.OnBindingContextChanged() -> void
override Microsoft.Maui.Controls.Handlers.Items2.StructuredItemsViewController2<TItemsView>.UpdateFlowDirection() -> void
~override Microsoft.Maui.Controls.SwipeItems.OnPropertyChanged(string propertyName = null) -> void
Microsoft.Maui.Controls.Label.~Label() -> void
~override Microsoft.Maui.Controls.Platform.Compatibility.ShellSectionRenderer.TraitCollectionDidChange(UIKit.UITraitCollection previousTraitCollection) -> void
~override Microsoft.Maui.Controls.Platform.Compatibility.ShellSectionRenderer.ViewWillTransitionToSize(CoreGraphics.CGSize toSize, UIKit.IUIViewControllerTransitionCoordinator coordinator) -> void
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ override Microsoft.Maui.Controls.Handlers.Items2.StructuredItemsViewController2<
~override Microsoft.Maui.Controls.Platform.Compatibility.ShellFlyoutRenderer.ViewWillTransitionToSize(CoreGraphics.CGSize toSize, UIKit.IUIViewControllerTransitionCoordinator coordinator) -> void
override Microsoft.Maui.Controls.Platform.Compatibility.ShellItemRenderer.ViewDidAppear(bool animated) -> void
~override Microsoft.Maui.Controls.Platform.Compatibility.ShellSectionRenderer.DidMoveToParentViewController(UIKit.UIViewController parent) -> void
*REMOVED*~override Microsoft.Maui.Controls.Platform.Compatibility.ShellSectionRootRenderer.TraitCollectionDidChange(UIKit.UITraitCollection previousTraitCollection) -> void
override Microsoft.Maui.Controls.Platform.Compatibility.ShellTableViewController.LoadView() -> void
override Microsoft.Maui.Controls.Shapes.Shape.OnPropertyChanged(string? propertyName = null) -> void
override Microsoft.Maui.Controls.SwipeItemView.IsEnabledCore.get -> bool
Expand All @@ -17,3 +16,5 @@ override Microsoft.Maui.Controls.TitleBar.OnBindingContextChanged() -> void
override Microsoft.Maui.Controls.Handlers.Items2.StructuredItemsViewController2<TItemsView>.UpdateFlowDirection() -> void
~override Microsoft.Maui.Controls.SwipeItems.OnPropertyChanged(string propertyName = null) -> void
Microsoft.Maui.Controls.Label.~Label() -> void
~override Microsoft.Maui.Controls.Platform.Compatibility.ShellSectionRenderer.TraitCollectionDidChange(UIKit.UITraitCollection previousTraitCollection) -> void
~override Microsoft.Maui.Controls.Platform.Compatibility.ShellSectionRenderer.ViewWillTransitionToSize(CoreGraphics.CGSize toSize, UIKit.IUIViewControllerTransitionCoordinator coordinator) -> void
74 changes: 74 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue35844.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 35844, "Shell TitleView does not resize after rotation on iOS 26+", PlatformAffected.iOS)]
public class Issue35844Shell : Shell
{
public Issue35844Shell()
{
Issue35844 contentPage = new Issue35844();

ShellContent shellContent = new ShellContent
{
Content = contentPage,
Route = "Issue35844"
};

Items.Add(shellContent);
}
}

public class Issue35844 : ContentPage
{
public Issue35844()
{
Shell.SetTitleView(this, new Grid
{
BackgroundColor = Colors.LightBlue,
AutomationId = "TitleViewGrid",
HorizontalOptions = LayoutOptions.Fill,
Children =
{
new Label
{
Text = "Shell TitleView",
AutomationId = "TitleLabel",
TextColor = Colors.White,
FontSize = 18,
FontAttributes = FontAttributes.Bold,
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center
}
}
});

Content = new VerticalStackLayout
{
Padding = new Thickness(20),
Spacing = 10,
Children =
{
new Label
{
Text = "Issue 35844",
FontSize = 20,
FontAttributes = FontAttributes.Bold,
AutomationId = "HeaderLabel",
HorizontalOptions = LayoutOptions.Center
},
new Label
{
Text = "Shell TitleView should fill the navigation bar width after rotation on iOS 26+.",
FontSize = 14,
AutomationId = "DescriptionLabel"
},
new Label
{
Text = "Rotate device to test",
AutomationId = "StatusLabel",
FontSize = 16,
TextColor = Colors.Gray
}
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#if TEST_FAILS_ON_WINDOWS && TEST_FAILS_ON_CATALYST // SetOrientationLandscape/Portrait is only supported on iOS and Android.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[major] Regression Prevention - This preprocessor guard also includes the test in the Android test project: Android defines TEST_FAILS_ON_WINDOWS and TEST_FAILS_ON_CATALYST. The HostApp issue is marked PlatformAffected.iOS, so Android issue navigation can fail to find this sample. Scope the test to iOS explicitly, e.g. #if IOS / #if IOSUITEST, unless Android coverage is intentionally added.


using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues
{
public class Issue35844 : _IssuesUITest
{
public override string Issue => "Shell TitleView does not resize after rotation on iOS 26+";

public Issue35844(TestDevice device) : base(device) { }

[Test]
[Category(UITestCategories.Shell)]
public void ShellTitleViewResizesOnRotation()
{
App.WaitForElement("TitleViewGrid");
App.WaitForElement("StatusLabel");

// Capture portrait width
var portraitRect = App.WaitForElement("TitleViewGrid").GetRect();
var portraitWidth = portraitRect.Width;

App.SetOrientationLandscape();
App.WaitForElement("TitleViewGrid"); // re-wait to ensure layout has settled after rotation

// After rotation, TitleView width must change to fill the wider nav bar
var landscapeRect = App.WaitForElement("TitleViewGrid").GetRect();
var landscapeWidth = landscapeRect.Width;

Assert.That(landscapeWidth, Is.Not.EqualTo(portraitWidth).Within(50),
"Shell TitleView width should expand after rotating to landscape on iOS 26+");
Assert.That(landscapeWidth, Is.GreaterThan(portraitWidth),
"Shell TitleView should be wider in landscape than portrait");

// Rotate back and verify TitleView returns to original width
App.SetOrientationPortrait();
App.WaitForElement("TitleViewGrid"); // re-wait to ensure layout has settled after rotation

var finalRect = App.WaitForElement("TitleViewGrid").GetRect();
Assert.That(finalRect.Width, Is.EqualTo(portraitWidth).Within(5),
"Shell TitleView should return to original portrait width after rotating back");
}
}
}
#endif
Loading