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 @@ -177,8 +177,11 @@ internal static void RecalculateSpanPositions(this UILabel control, Label elemen
textStorage.AddLayoutManager(layoutManager);
layoutManager.AddTextContainer(textContainer);

textContainer.Size = new(control.Bounds.Width,
control.Lines == 0 ? nfloat.MaxValue : control.Bounds.Height);
// On iOS 26+ with NavigationPage, UILabel.Bounds may still be {0,0,0,0}
// during ArrangeOverride. Use finalSize (MAUI's computed size) as fallback.
var containerWidth = control.Bounds.Width > 0 ? control.Bounds.Width : (nfloat)finalSize.Width;
var containerHeight = control.Bounds.Height > 0 ? control.Bounds.Height : (nfloat)finalSize.Height;
textContainer.Size = new(containerWidth, control.Lines == 0 ? nfloat.MaxValue : containerHeight);

textStorage.SetString(attributedText);
layoutManager.EnsureLayoutForTextContainer(textContainer);
Expand Down
142 changes: 142 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue34504.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
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.
LineBreakMode = LineBreakMode.WordWrap,
MaximumWidthRequest = 300,
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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.
// Ensure the label wrapped to multiple lines; if it's single-line the tap
// cannot exercise the second-line bug and the test would be a false-positive.
Assert.That(labelRect.Height, Is.GreaterThan(40), "SpanLabel must be tall enough to indicate multi-line text before tapping the second line.");

// 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);

Assert.That(App.WaitForElement("StatusLabel").GetText(), Is.EqualTo("Success"));
}
}
Loading