Skip to content

Commit 971e326

Browse files
CopilotPureWeen
andauthored
Fix iOS 26 TitleView covering content in Shell and NavigationPage (#32341)
* Initial plan * Implement improved fix for iOS 26 TitleView covering content issue - Added CreateTitleViewContainer helper method to reduce code duplication - Improved iOS 26+ detection with proper fallback when navigation bar frame unavailable - Set frame origin to (0,0) instead of preserving potentially uninitialized X/Y values - Added comprehensive XML documentation explaining iOS 26 workaround - Added test case and UI test for Issue32287 This implementation addresses the critical issues identified in PR #32337 review: - Eliminates silent failure by providing fallback to standard constructor - Clarifies frame positioning logic with explicit (0,0) origin - Reduces code duplication through helper method - Adds proper documentation for maintainability Co-authored-by: PureWeen <[email protected]> * Apply iOS 26 TitleView fix to NavigationPage Extended the iOS 26+ TitleView fix to NavigationPage in addition to Shell: 1. Added CreateTitleViewContainer helper method to centralize container creation 2. Added Container constructor overload accepting navigation bar frame for iOS 26+ 3. Refactored Container initialization to use InitializeContainer method 4. Updated ToolbarHeight to use actual navigation bar height when available 5. Eliminated hardcoded 44pt height assumption for iOS 26+ This ensures both Shell and NavigationPage TitleView implementations handle iOS 26 layout changes consistently, preventing TitleView from covering page content. Co-authored-by: PureWeen <[email protected]> * Enhance Issue32287 test with rotation and dynamic content size changes Extended the Issue32287 test based on review feedback: 1. Added rotation test to verify TitleView behavior in landscape mode 2. Added dynamic font size change tests to verify container tracks nav bar changes 3. Added buttons to increase/decrease TitleView font size 4. Added x:Name to TitleLabel for programmatic access 5. Created placeholder snapshot image for test validation 6. Wrapped rotation tests with TEST_FAILS_ON_WINDOWS && TEST_FAILS_ON_CATALYST Tests now comprehensively verify that TitleView container correctly tracks navigation bar frame changes without covering content in all scenarios. Co-authored-by: PureWeen <[email protected]> * - update tests * Add NavigationPage equivalent tests for Issue32287 Created comprehensive NavigationPage tests mirroring the Shell tests: 1. Issue32287NavigationPage.xaml - NavigationPage with custom TitleView 2. Issue32287NavigationPage.xaml.cs - Code-behind with font size controls 3. Issue32287NavigationPage.cs - UI tests matching Shell test scenarios: - Basic test verifying TitleView doesn't cover content - Dynamic content size change test - Landscape rotation test (iOS/Android only) 4. Created placeholder snapshot image for visual validation Tests verify that the NavigationPage Container fix handles iOS 26+ layout changes consistently with Shell, preventing TitleView from covering content. Co-authored-by: PureWeen <[email protected]> * Fix NavigationPage XAML structure and add platform-specific comments Fixed XAML parsing error and addressed PR review feedback: 1. Changed Issue32287NavigationPage from NavigationPage to ContentPage - NavigationPage cannot have content directly set in XAML - NavigationPage.TitleView is an attached property set on ContentPage - Updated base class from NavigationPage to ContentPage 2. Added explanatory comments for platform-specific conditionals - Documented that rotation tests only work on iOS/Android - Explained SetOrientationLandscape/Portrait not supported on Windows/Catalyst - Applied to both Issue32287 and Issue32287NavigationPage test files Fixes XAML error: "Cannot set the content of NavigationPage as it doesn't have a ContentPropertyAttribute" Co-authored-by: PureWeen <[email protected]> * - fix navigation page * Delete src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/CustomTitleViewDoesNotCoverContent.png * Delete src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Issue32287NavigationPageCustomTitleViewDoesNotCoverContent.png * - fix tests and add suggested fix * Remove all test files from PR Deleted all Issue32287 test files as requested: - Issue32287.xaml and Issue32287.xaml.cs (Shell tests) - Issue32287NavigationPage.xaml and Issue32287NavigationPage.xaml.cs (NavigationPage tests) - Issue32287.cs and Issue32287NavigationPage.cs (UI test implementations) The PR now contains only the iOS 26 TitleView fix implementation without test coverage. Co-authored-by: PureWeen <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: PureWeen <[email protected]> Co-authored-by: Shane Neuville <[email protected]>
1 parent 0d0d565 commit 971e326

File tree

2 files changed

+100
-4
lines changed

2 files changed

+100
-4
lines changed

src/Controls/src/Core/Compatibility/Handlers/NavigationPage/iOS/NavigationRenderer.cs

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1664,13 +1664,34 @@ internal void UpdateTitleArea(Page page)
16641664
if (n is null)
16651665
return;
16661666

1667-
Container titleViewContainer = new Container(titleView, n.NavigationBar);
1667+
Container titleViewContainer = CreateTitleViewContainer(titleView, n.NavigationBar);
16681668

16691669
UpdateTitleImage(titleViewContainer, titleIcon);
16701670
NavigationItem.TitleView = titleViewContainer;
16711671
}
16721672
}
16731673

1674+
/// <summary>
1675+
/// Creates a Container with the appropriate configuration for the current iOS version.
1676+
/// For iOS 26+, uses autoresizing masks and sets frame from navigation bar to prevent layout issues.
1677+
/// </summary>
1678+
Container CreateTitleViewContainer(View titleView, UINavigationBar navigationBar)
1679+
{
1680+
// iOS 26+ requires autoresizing masks and explicit frame sizing to prevent TitleView from covering content
1681+
if (OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26))
1682+
{
1683+
var navigationBarFrame = navigationBar.Frame;
1684+
if (navigationBarFrame != CGRect.Empty)
1685+
{
1686+
return new Container(titleView, navigationBar, navigationBarFrame);
1687+
}
1688+
// Fallback: If navigation bar frame isn't available, use standard constructor
1689+
// The view will still use autoresizing masks (configured in constructor)
1690+
}
1691+
1692+
return new Container(titleView, navigationBar);
1693+
}
1694+
16741695
void UpdateIconColor()
16751696
{
16761697
if (_navigation.TryGetTarget(out NavigationRenderer navigationRenderer))
@@ -2083,13 +2104,35 @@ class Container : UIView
20832104
IPlatformViewHandler _child;
20842105
UIImageView _icon;
20852106
bool _disposed;
2107+
nfloat? _navigationBarHeight;
20862108

20872109
//https://developer.apple.com/documentation/uikit/uiview/2865930-directionallayoutmargins
20882110
const int SystemMargin = 16;
20892111

20902112
public Container(View view, UINavigationBar bar) : base(bar.Bounds)
20912113
{
2092-
// For iOS 26+, we need to use autoresizing masks instead of constraints to ensure proper TitleView display
2114+
InitializeContainer(view, bar, null);
2115+
}
2116+
2117+
/// <summary>
2118+
/// Creates a Container with an explicitly set frame from the navigation bar.
2119+
/// Used on iOS 26+ to ensure proper sizing when using autoresizing masks.
2120+
/// </summary>
2121+
/// <param name="view">The MAUI view to display in the title</param>
2122+
/// <param name="bar">The navigation bar</param>
2123+
/// <param name="navigationBarFrame">The navigation bar frame to use for sizing</param>
2124+
internal Container(View view, UINavigationBar bar, CGRect navigationBarFrame) : base(CGRect.Empty)
2125+
{
2126+
// Set frame to match navigation bar dimensions, starting at origin (0,0)
2127+
Frame = new CGRect(0, 0, navigationBarFrame.Width, navigationBarFrame.Height);
2128+
InitializeContainer(view, bar, navigationBarFrame.Height);
2129+
}
2130+
2131+
void InitializeContainer(View view, UINavigationBar bar, nfloat? navigationBarHeight)
2132+
{
2133+
// iOS 26+ and MacCatalyst 26+ require autoresizing masks instead of constraints
2134+
// to prevent TitleView from expanding beyond navigation bar bounds and covering content.
2135+
// This is a workaround for layout behavior changes in iOS 26.
20932136
if (OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26))
20942137
{
20952138
TranslatesAutoresizingMaskIntoConstraints = true;
@@ -2106,6 +2149,8 @@ public Container(View view, UINavigationBar bar) : base(bar.Bounds)
21062149
}
21072150

21082151
_bar = bar as MauiControlsNavigationBar;
2152+
_navigationBarHeight = navigationBarHeight;
2153+
21092154
if (view != null)
21102155
{
21112156
_view = view;
@@ -2170,9 +2215,16 @@ nfloat ToolbarHeight
21702215
{
21712216
get
21722217
{
2218+
// For iOS 26+, use the actual navigation bar height if available
2219+
if (_navigationBarHeight.HasValue)
2220+
return _navigationBarHeight.Value;
2221+
21732222
if (Superview?.Bounds.Height > 0)
21742223
return Superview.Bounds.Height;
21752224

2225+
// Fallback to device-specific defaults
2226+
// Note: iOS 26+ uses taller navigation bars, but this fallback
2227+
// should rarely be hit as we prefer using the actual navigation bar frame
21762228
return (DeviceInfo.Idiom == DeviceIdiom.Phone && DeviceDisplay.MainDisplayInfo.Orientation.IsLandscape()) ? 32 : 44;
21772229
}
21782230
}

src/Controls/src/Core/Compatibility/Handlers/Shell/iOS/ShellPageRendererTracker.cs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ protected virtual void UpdateTitleView()
306306
{
307307
if (titleView.Parent != null)
308308
{
309-
var view = new TitleViewContainer(titleView);
309+
var view = CreateTitleViewContainer(titleView);
310310
NavigationItem.TitleView = view;
311311
}
312312
else
@@ -316,6 +316,27 @@ protected virtual void UpdateTitleView()
316316
}
317317
}
318318

319+
/// <summary>
320+
/// Creates a TitleViewContainer with the appropriate configuration for the current iOS version.
321+
/// For iOS 26+, uses autoresizing masks and sets frame from navigation bar to prevent layout issues.
322+
/// </summary>
323+
TitleViewContainer CreateTitleViewContainer(View titleView)
324+
{
325+
// iOS 26+ requires autoresizing masks and explicit frame sizing to prevent TitleView from covering content
326+
if (OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26))
327+
{
328+
var navigationBarFrame = ViewController?.NavigationController?.NavigationBar.Frame;
329+
if (navigationBarFrame.HasValue)
330+
{
331+
return new TitleViewContainer(titleView, navigationBarFrame.Value);
332+
}
333+
// Fallback: If navigation bar frame isn't available, use standard constructor
334+
// The view will still use autoresizing masks (configured in constructor)
335+
}
336+
337+
return new TitleViewContainer(titleView);
338+
}
339+
319340
void OnTitleViewParentSet(object? sender, EventArgs e)
320341
{
321342
if (sender is Element element)
@@ -684,17 +705,40 @@ public TitleViewContainer(View view) : base(view)
684705
{
685706
MatchHeight = true;
686707

687-
if (OperatingSystem.IsIOSVersionAtLeast(11) || OperatingSystem.IsTvOSVersionAtLeast(11))
708+
// iOS 26+ and MacCatalyst 26+ require autoresizing masks instead of constraints
709+
// to prevent TitleView from expanding beyond navigation bar bounds and covering content.
710+
// This is a workaround for layout behavior changes in iOS 26.
711+
if (OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26))
712+
{
713+
TranslatesAutoresizingMaskIntoConstraints = true;
714+
AutoresizingMask = UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidth;
715+
}
716+
else if (OperatingSystem.IsIOSVersionAtLeast(11) || OperatingSystem.IsTvOSVersionAtLeast(11))
688717
{
689718
TranslatesAutoresizingMaskIntoConstraints = false;
690719
}
691720
else
692721
{
722+
// Pre-iOS 11 also uses autoresizing masks
693723
TranslatesAutoresizingMaskIntoConstraints = true;
694724
AutoresizingMask = UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidth;
695725
}
696726
}
697727

728+
/// <summary>
729+
/// Creates a TitleViewContainer with an explicitly set frame from the navigation bar.
730+
/// Used on iOS 26+ to ensure proper sizing when using autoresizing masks.
731+
/// </summary>
732+
/// <param name="view">The MAUI view to display in the title</param>
733+
/// <param name="navigationBarFrame">The navigation bar frame to use for sizing</param>
734+
internal TitleViewContainer(View view, CGRect navigationBarFrame) : this(view)
735+
{
736+
// Set frame to match navigation bar dimensions, starting at origin (0,0)
737+
// The X and Y are set to 0 because this view will be positioned by the navigation bar
738+
Frame = new CGRect(0, 0, navigationBarFrame.Width, navigationBarFrame.Height);
739+
Height = navigationBarFrame.Height; // Set Height for MatchHeight logic
740+
}
741+
698742
public override CGRect Frame
699743
{
700744
get => base.Frame;

0 commit comments

Comments
 (0)