Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
17 changes: 16 additions & 1 deletion src/Controls/src/Core/Label/Label.iOS.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#nullable disable
using System;
using CoreFoundation;
Comment thread
SubhikshaSf4851 marked this conversation as resolved.
Outdated
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Graphics;
using UIKit;
Expand All @@ -12,7 +13,21 @@ protected override Size ArrangeOverride(Rect bounds)
{
var size = base.ArrangeOverride(bounds);

RecalculateSpanPositions(size);
// On iOS 26+ with NavigationPage, the UILabel's native Bounds may not be
// finalized during MAUI's ArrangeOverride due to the WrapperView layout
// timing. Detect this by checking if the UILabel Bounds are unset despite
// a valid MAUI-computed size, and defer span recalculation to the next main
// run loop iteration when iOS has propagated the frame correctly.
if ((OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26)) && HasFormattedTextSpans && Handler is LabelHandler labelHandler &&
labelHandler.PlatformView is UILabel platformLabel &&
platformLabel.Bounds.Width == 0 && size.Width > 0)
{
platformLabel.BeginInvokeOnMainThread(() => RecalculateSpanPositions(size));
Comment thread
SubhikshaSf4851 marked this conversation as resolved.
Outdated
}
else
{
RecalculateSpanPositions(size);
}

return size;
}
Expand Down
140 changes: 140 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue34504.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 34504, "[iOS] Span TapGestureRecognizer does not work on the second line of the span, if the span is wrapped to the next line", PlatformAffected.iOS)]
public class Issue34504 : NavigationPage
{
public Issue34504() : base(new FirstPage()) { }

// First page mirrors the sandbox MainPage — has the same span content so
// the layout system is exercised before navigating to the second page.
class FirstPage : ContentPage
{
public FirstPage()
{
void OnSpanTapped(object sender, TappedEventArgs e)
{
// no-op — first page just needs spans to exist
}

var label = BuildSpanLabel(OnSpanTapped);

var navigateButton = new Button
{
Text = "Navigate to Test Page",
AutomationId = "NavigateButton",
HorizontalOptions = LayoutOptions.Fill,
};
navigateButton.Clicked += async (s, e) =>
await Navigation.PushAsync(new SecondPage());

Content = new VerticalStackLayout
{
Padding = new Thickness(30, 0),
Spacing = 25,
Children =
{
new Label
{
Text = "Click the button below to navigate to the test page with wrapped span gestures.",
FontSize = 14,
TextColor = Colors.Gray,
},
navigateButton,
new Border
{
StrokeThickness = 2,
Stroke = Colors.Black,
Padding = new Thickness(10),
Content = label,
},
}
};
}
}

// Second page mirrors the sandbox TestPage — this is where the bug manifests on iOS 26+.
public class SecondPage : ContentPage
{
public SecondPage()
{
var statusLabel = new Label
{
AutomationId = "StatusLabel",
Text = "Tap status will appear here",
FontSize = 14,
TextColor = Colors.Black,
};

void OnSpanTapped(object sender, TappedEventArgs e)
{
statusLabel.Text = "Success";
}

var spanLabel = BuildSpanLabel(OnSpanTapped);
spanLabel.AutomationId = "SpanLabel";

Content = new ScrollView
{
Content = new VerticalStackLayout
{
Padding = new Thickness(30, 0),
Spacing = 25,
Children =
{
new Label
{
Text = "iOS TapGesture Issue Demonstration",
FontSize = 18,
FontAttributes = FontAttributes.Bold,
},
new Label
{
Text = "Tap on the colored text below. On iOS, gestures on wrapped lines may not work.",
FontSize = 12,
TextColor = Colors.Gray,
},
new Border
{
StrokeThickness = 2,
Stroke = Colors.Black,
Padding = new Thickness(10),
Content = spanLabel,
},
statusLabel,
}
}
};
}
}

static Label BuildSpanLabel(EventHandler<TappedEventArgs> onTapped)
{
Span MakeSpan(string text, Color color)
{
var span = new Span
{
Text = text,
TextColor = color,
TextDecorations = TextDecorations.Underline,
};
var tap = new TapGestureRecognizer();
tap.Tapped += onTapped;
span.GestureRecognizers.Add(tap);
return span;
}

var fs = new FormattedString();
fs.Spans.Add(MakeSpan("Hello,This is a test. Hello,This is a test. Hello,This is a test.", Colors.Blue));
fs.Spans.Add(MakeSpan("Hello,This is a test1. Hello,This is a test1. Hello,This is a test1.", Colors.Red));
fs.Spans.Add(MakeSpan("Hello,This is a test2. Hello,This is a test2. Hello,This is a test2.", Colors.Green));
fs.Spans.Add(MakeSpan("Hello,This is a test4. Hello,This is a test4. Hello,This is a test4.", Colors.Orange));
fs.Spans.Add(MakeSpan("Hello,This is a test3. Hello,This is a test3. Hello,This is a test3.", Colors.Purple));
fs.Spans.Add(new Span { Text = " World!", FontAttributes = FontAttributes.Bold });

return new Label
{
FormattedText = fs,
BackgroundColor = Colors.Transparent,
Comment thread
SubhikshaSf4851 marked this conversation as resolved.
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue34504 : _IssuesUITest
{
public Issue34504(TestDevice device) : base(device) { }

public override string Issue => "[iOS] Span TapGestureRecognizer does not work on the second line of the span, if the span is wrapped to the next line";

[Test]
[Category(UITestCategories.Label)]
// The bug only manifests on iOS 26+ (not Android / Windows / MacCatalyst),
// but the test is safe to run on all platforms — it will simply pass on unaffected ones.
public void SpanTapGestureOnSecondLineShouldWork()
{
// Navigate to the second page — the bug only reproduces on a pushed page.
App.WaitForElement("NavigateButton");
App.Tap("NavigateButton");

var labelRect = App.WaitForElement("SpanLabel").GetRect();

Comment thread
SubhikshaSf4851 marked this conversation as resolved.
// Tap near the bottom of the label to hit the second wrapped line of a span.
App.TapCoordinates(labelRect.X + labelRect.Width / 2, labelRect.Y + labelRect.Height * 0.75f);

App.WaitForElement("Success");
}
}
Loading