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 @@ -1664,13 +1664,34 @@ internal void UpdateTitleArea(Page page)
if (n is null)
return;

Container titleViewContainer = new Container(titleView, n.NavigationBar);
Container titleViewContainer = CreateTitleViewContainer(titleView, n.NavigationBar);

UpdateTitleImage(titleViewContainer, titleIcon);
NavigationItem.TitleView = titleViewContainer;
}
}

/// <summary>
/// Creates a Container with the appropriate configuration for the current iOS version.
/// For iOS 26+, uses autoresizing masks and sets frame from navigation bar to prevent layout issues.
/// </summary>
Container CreateTitleViewContainer(View titleView, UINavigationBar navigationBar)
{
// iOS 26+ requires autoresizing masks and explicit frame sizing to prevent TitleView from covering content
if (OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26))
{
var navigationBarFrame = navigationBar.Frame;
if (navigationBarFrame != CGRect.Empty)
{
return new Container(titleView, navigationBar, navigationBarFrame);
}
// Fallback: If navigation bar frame isn't available, use standard constructor
// The view will still use autoresizing masks (configured in constructor)
}

return new Container(titleView, navigationBar);
}

void UpdateIconColor()
{
if (_navigation.TryGetTarget(out NavigationRenderer navigationRenderer))
Expand Down Expand Up @@ -2083,13 +2104,35 @@ class Container : UIView
IPlatformViewHandler _child;
UIImageView _icon;
bool _disposed;
nfloat? _navigationBarHeight;

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

public Container(View view, UINavigationBar bar) : base(bar.Bounds)
{
// For iOS 26+, we need to use autoresizing masks instead of constraints to ensure proper TitleView display
InitializeContainer(view, bar, null);
}

/// <summary>
/// Creates a Container with an explicitly set frame from the navigation bar.
/// Used on iOS 26+ to ensure proper sizing when using autoresizing masks.
/// </summary>
/// <param name="view">The MAUI view to display in the title</param>
/// <param name="bar">The navigation bar</param>
/// <param name="navigationBarFrame">The navigation bar frame to use for sizing</param>
internal Container(View view, UINavigationBar bar, CGRect navigationBarFrame) : base(CGRect.Empty)
{
// Set frame to match navigation bar dimensions, starting at origin (0,0)
Frame = new CGRect(0, 0, navigationBarFrame.Width, navigationBarFrame.Height);
InitializeContainer(view, bar, navigationBarFrame.Height);
}

void InitializeContainer(View view, UINavigationBar bar, nfloat? navigationBarHeight)
{
// iOS 26+ and MacCatalyst 26+ require autoresizing masks instead of constraints
// to prevent TitleView from expanding beyond navigation bar bounds and covering content.
// This is a workaround for layout behavior changes in iOS 26.
if (OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26))
{
TranslatesAutoresizingMaskIntoConstraints = true;
Expand All @@ -2106,6 +2149,8 @@ public Container(View view, UINavigationBar bar) : base(bar.Bounds)
}

_bar = bar as MauiControlsNavigationBar;
_navigationBarHeight = navigationBarHeight;

if (view != null)
{
_view = view;
Expand Down Expand Up @@ -2170,9 +2215,16 @@ nfloat ToolbarHeight
{
get
{
// For iOS 26+, use the actual navigation bar height if available
if (_navigationBarHeight.HasValue)
return _navigationBarHeight.Value;

if (Superview?.Bounds.Height > 0)
return Superview.Bounds.Height;

// Fallback to device-specific defaults
// Note: iOS 26+ uses taller navigation bars, but this fallback
// should rarely be hit as we prefer using the actual navigation bar frame
return (DeviceInfo.Idiom == DeviceIdiom.Phone && DeviceDisplay.MainDisplayInfo.Orientation.IsLandscape()) ? 32 : 44;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ protected virtual void UpdateTitleView()
{
if (titleView.Parent != null)
{
var view = new TitleViewContainer(titleView);
var view = CreateTitleViewContainer(titleView);
NavigationItem.TitleView = view;
}
else
Expand All @@ -316,6 +316,27 @@ protected virtual void UpdateTitleView()
}
}

/// <summary>
/// Creates a TitleViewContainer with the appropriate configuration for the current iOS version.
/// For iOS 26+, uses autoresizing masks and sets frame from navigation bar to prevent layout issues.
/// </summary>
TitleViewContainer CreateTitleViewContainer(View titleView)
{
// iOS 26+ requires autoresizing masks and explicit frame sizing to prevent TitleView from covering content
if (OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26))
{
var navigationBarFrame = ViewController?.NavigationController?.NavigationBar.Frame;
if (navigationBarFrame.HasValue)
{
return new TitleViewContainer(titleView, navigationBarFrame.Value);
}
// Fallback: If navigation bar frame isn't available, use standard constructor
// The view will still use autoresizing masks (configured in constructor)
}

return new TitleViewContainer(titleView);
}

void OnTitleViewParentSet(object? sender, EventArgs e)
{
if (sender is Element element)
Expand Down Expand Up @@ -684,17 +705,40 @@ public TitleViewContainer(View view) : base(view)
{
MatchHeight = true;

if (OperatingSystem.IsIOSVersionAtLeast(11) || OperatingSystem.IsTvOSVersionAtLeast(11))
// iOS 26+ and MacCatalyst 26+ require autoresizing masks instead of constraints
// to prevent TitleView from expanding beyond navigation bar bounds and covering content.
// This is a workaround for layout behavior changes in iOS 26.
if (OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26))
{
TranslatesAutoresizingMaskIntoConstraints = true;
AutoresizingMask = UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidth;
}
else if (OperatingSystem.IsIOSVersionAtLeast(11) || OperatingSystem.IsTvOSVersionAtLeast(11))
{
TranslatesAutoresizingMaskIntoConstraints = false;
}
else
{
// Pre-iOS 11 also uses autoresizing masks
TranslatesAutoresizingMaskIntoConstraints = true;
AutoresizingMask = UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidth;
}
}

/// <summary>
/// Creates a TitleViewContainer with an explicitly set frame from the navigation bar.
/// Used on iOS 26+ to ensure proper sizing when using autoresizing masks.
/// </summary>
/// <param name="view">The MAUI view to display in the title</param>
/// <param name="navigationBarFrame">The navigation bar frame to use for sizing</param>
internal TitleViewContainer(View view, CGRect navigationBarFrame) : this(view)
{
// Set frame to match navigation bar dimensions, starting at origin (0,0)
// The X and Y are set to 0 because this view will be positioned by the navigation bar
Frame = new CGRect(0, 0, navigationBarFrame.Width, navigationBarFrame.Height);
Height = navigationBarFrame.Height; // Set Height for MatchHeight logic
}

public override CGRect Frame
{
get => base.Frame;
Expand Down