[iOS] Fix span Tap gesture on wrapped Label lines in iOS 26+#34640
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34640Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34640" |
There was a problem hiding this comment.
Pull request overview
Fixes incorrect hit-testing for Span TapGestureRecognizer on wrapped Label lines on iOS 26+ by deferring span-region recalculation until the native UILabel bounds have been finalized, and adds a HostApp repro + automated UI test for issue #34504.
Changes:
- Update iOS
Label.ArrangeOverrideto deferRecalculateSpanPositionswhenUILabel.Boundsis still unset on iOS/MacCatalyst 26+. - Add a new HostApp issue page (
Issue34504) reproducing the NavigationPage + wrapped-span tap scenario. - Add a new Appium/NUnit UI test (
Issue34504) validating taps on wrapped span lines after navigation.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/Controls/src/Core/Label/Label.iOS.cs | Defers span-region recalculation on iOS/MacCatalyst 26+ when UILabel.Bounds isn’t finalized yet. |
| src/Controls/tests/TestCases.HostApp/Issues/Issue34504.cs | Adds a two-page NavigationPage-based repro with formatted spans and a success indicator. |
| src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34504.cs | Adds an automated UI test that navigates and taps within the label to trigger a span tap. |
kubaflo
left a comment
There was a problem hiding this comment.
Looks like the test couldn't catch a bug before fix
|
@kubaflo The test was executed locally and failed on iOS 26 as expected without the fix. The test results for iOS 18 and iOS 26, both with and without the fix, have been shared below. This issue is specific to iOS 26 and does not reproduce on iOS 18, which is why the test passes on iOS 18 with and without the fix. iOS 18 version test results :
iOS 26 version test results :
|
🚦 Gate — Test Before and After Fix
🚦 Gate Session —
|
| Test | Without Fix (expect FAIL) | With Fix (expect PASS) |
|---|---|---|
🖥️ Issue34504 Issue34504 |
❌ PASS — 221s | ✅ PASS — 92s |
🔴 Without fix — 🖥️ Issue34504: PASS ❌ · 221s
Determining projects to restore...
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 464 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 558 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 7.3 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 17.41 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/Foldable/src/Controls.Foldable.csproj (in 17.46 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Xaml/Controls.Xaml.csproj (in 17.44 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj (in 17.42 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 17.49 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj (in 17.51 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/Core/maps/src/Maps.csproj (in 17.52 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/Maps/src/Controls.Maps.csproj (in 17.52 sec).
/Users/cloudtest/vss/_work/1/s/.dotnet/packs/Microsoft.iOS.Sdk.net10.0_26.0/26.0.11017/targets/Xamarin.Shared.Sdk.targets(309,3): warning : RuntimeIdentifier was set on the command line, and will override the value for RuntimeIdentifiers set in the project file. [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-ios]
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0-ios26.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0-ios26.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0-ios26.0/Microsoft.Maui.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
Controls.BindingSourceGen -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Maps/Debug/net10.0-ios26.0/Microsoft.Maui.Maps.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
Controls.Xaml -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Xaml.dll
Microsoft.AspNetCore.Components.WebView.Maui -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-ios26.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
Controls.Foldable -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Foldable.dll
Controls.Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Maps.dll
Detected signing identity:
Code Signing Key: "" (-)
Provisioning Profile: "" () - no entitlements
Bundle Id: com.microsoft.maui.uitests
App Id: com.microsoft.maui.uitests
Controls.TestCases.HostApp -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-ios/iossimulator-arm64/Controls.TestCases.HostApp.dll
Optimizing assemblies for size may change the behavior of the app. Be sure to test after publishing. See: https://aka.ms/dotnet-illink
Optimizing assemblies for size. This process might take a while.
Build succeeded.
/Users/cloudtest/vss/_work/1/s/.dotnet/packs/Microsoft.iOS.Sdk.net10.0_26.0/26.0.11017/targets/Xamarin.Shared.Sdk.targets(309,3): warning : RuntimeIdentifier was set on the command line, and will override the value for RuntimeIdentifiers set in the project file. [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-ios]
1 Warning(s)
0 Error(s)
Time Elapsed 00:01:42.75
Determining projects to restore...
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 676 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 680 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.Core/UITest.Core.csproj (in 676 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/VisualTestUtils/VisualTestUtils.csproj (in 676 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/tests/CustomAttributes/Controls.CustomAttributes.csproj (in 2 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 702 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 740 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 755 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.NUnit/UITest.NUnit.csproj (in 2.08 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.Appium/UITest.Appium.csproj (in 2.4 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.Analyzers/UITest.Analyzers.csproj (in 3.58 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/VisualTestUtils.MagickNet/VisualTestUtils.MagickNet.csproj (in 3.45 sec).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.iOS.Tests/Controls.TestCases.iOS.Tests.csproj (in 4.18 sec).
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
Controls.CustomAttributes -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
UITest.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
VisualTestUtils -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
UITest.NUnit -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
VisualTestUtils.MagickNet -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
UITest.Appium -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
UITest.Analyzers -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
Controls.TestCases.iOS.Tests -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
Test run for /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (arm64)
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
/Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.05] Discovering: Controls.TestCases.iOS.Tests
[xUnit.net 00:00:00.14] Discovered: Controls.TestCases.iOS.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 4/18/2026 6:43:52 AM FixtureSetup for Issue34504(iOS)
>>>>> 4/18/2026 6:43:56 AM SpanTapGestureOnSecondLineShouldWork Start
>>>>> 4/18/2026 6:43:57 AM SpanTapGestureOnSecondLineShouldWork Stop
Passed SpanTapGestureOnSecondLineShouldWork [1 s]
NUnit Adapter 4.5.0.0: Test execution complete
Test Run Successful.
Total tests: 1
Passed: 1
Total time: 1.0367 Minutes
🟢 With fix — 🖥️ Issue34504: PASS ✅ · 92s
Determining projects to restore...
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 344 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 351 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 355 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 388 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 406 ms).
6 of 11 projects are up-to-date for restore.
/Users/cloudtest/vss/_work/1/s/.dotnet/packs/Microsoft.iOS.Sdk.net10.0_26.0/26.0.11017/targets/Xamarin.Shared.Sdk.targets(309,3): warning : RuntimeIdentifier was set on the command line, and will override the value for RuntimeIdentifiers set in the project file. [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-ios]
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0-ios26.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0-ios26.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0-ios26.0/Microsoft.Maui.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Maps/Debug/net10.0-ios26.0/Microsoft.Maui.Maps.dll
Controls.BindingSourceGen -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
Microsoft.AspNetCore.Components.WebView.Maui -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-ios26.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
Controls.Foldable -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Foldable.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
Controls.Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Maps.dll
Controls.Xaml -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Xaml.dll
Detected signing identity:
Code Signing Key: "" (-)
Provisioning Profile: "" () - no entitlements
Bundle Id: com.microsoft.maui.uitests
App Id: com.microsoft.maui.uitests
Controls.TestCases.HostApp -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-ios/iossimulator-arm64/Controls.TestCases.HostApp.dll
Optimizing assemblies for size may change the behavior of the app. Be sure to test after publishing. See: https://aka.ms/dotnet-illink
Optimizing assemblies for size. This process might take a while.
Build succeeded.
/Users/cloudtest/vss/_work/1/s/.dotnet/packs/Microsoft.iOS.Sdk.net10.0_26.0/26.0.11017/targets/Xamarin.Shared.Sdk.targets(309,3): warning : RuntimeIdentifier was set on the command line, and will override the value for RuntimeIdentifiers set in the project file. [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-ios]
1 Warning(s)
0 Error(s)
Time Elapsed 00:00:44.05
Determining projects to restore...
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 366 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 366 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 367 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 378 ms).
Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 397 ms).
8 of 13 projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
Controls.CustomAttributes -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.70-ci+azdo.13876771
Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
UITest.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
VisualTestUtils -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
UITest.Appium -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
UITest.NUnit -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
VisualTestUtils.MagickNet -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
UITest.Analyzers -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
Controls.TestCases.iOS.Tests -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
Test run for /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (arm64)
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
/Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.06] Discovering: Controls.TestCases.iOS.Tests
[xUnit.net 00:00:00.15] Discovered: Controls.TestCases.iOS.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 4/18/2026 6:45:23 AM FixtureSetup for Issue34504(iOS)
>>>>> 4/18/2026 6:45:28 AM SpanTapGestureOnSecondLineShouldWork Start
>>>>> 4/18/2026 6:45:30 AM SpanTapGestureOnSecondLineShouldWork Stop
Passed SpanTapGestureOnSecondLineShouldWork [1 s]
NUnit Adapter 4.5.0.0: Test execution complete
Test Run Successful.
Total tests: 1
Passed: 1
Total time: 20.8612 Seconds
⚠️ Issues found
- ❌ Issue34504 PASSED without fix (should fail) — tests don't catch the bug
📁 Fix files reverted (1 files)
src/Controls/src/Core/Platform/iOS/Extensions/FormattedStringExtensions.cs
🤖 AI Summary
📊 Review Session —
|
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #34640 | Fallback to finalSize when UILabel.Bounds is zero in RecalculateSpanPositions |
❌ FAILED (Gate — test passes without fix) | FormattedStringExtensions.cs |
Synchronous bounds check |
🔬 Code Review — Deep Analysis
Code Review — PR #34640
Independent Assessment
What this changes: RecalculateSpanPositions in FormattedStringExtensions.cs gains a fallback: when UILabel.Bounds.Width or .Height is 0, it substitutes the MAUI-computed finalSize dimensions (passed in from ArrangeOverride) rather than feeding a zero-width container to the NSTextContainer. The test suite adds a two-page HostApp reproduction and a corresponding NUnit UI test.
Inferred motivation: On iOS 26+ after navigating via NavigationPage.PushAsync, UILabel.Bounds hasn't been finalized by the time ArrangeOverride fires. The old code set textContainer.Size = new(0, ...), causing the text layout engine to produce incorrect glyph ranges → all gesture hit-rectangles collapsed to the top of the label → second-line span taps silently missed.
Is the approach sound? Yes, with a caveat. The finalSize is already guaranteed > 0 by the early-return guard at lines 200–203, so no division-by-zero or zero-container regression is possible. Using the MAUI-arranged size as a fallback for the text container is the right semantic anchor: it is the dimensions MAUI promised the UILabel will occupy, which is what the layout engine needs to produce correct line-fragment rects.
Reconciliation with PR Narrative
Author claims: The description says the fix "defer[s] span position recalculation to the next main run loop iteration" via BeginInvokeOnMainThread. The prior review thread confirms that was the original approach (which was reviewed by the Copilot bot and asked to be improved).
❗ Disagreement — the PR description does not match the committed code. The current diff contains no call to BeginInvokeOnMainThread and no change to Label.iOS.cs at all. The description is left over from the previous (reverted) approach. The actual implementation is a synchronous bounds-vs-zero comparison inside FormattedStringExtensions.cs. A reader relying on the description to understand the fix will be misled.
Findings
⚠️ CI — macOS Release build is failing
maui-pr (Build .NET MAUI Build macOS (Release)) is marked fail; the top-level maui-pr gate is also fail. The macOS Debug build passes, as do all Windows builds. Cannot determine from available data whether this failure is pre-existing infrastructure noise or caused by this PR. Because only Release (not Debug) fails on the same platform, it warrants investigation before merge — an optimized Release build could expose an issue in the nfloat cast or inlined path. Must be cleared or confirmed pre-existing.
⚠️ Warning — PR description describes a different fix
FormattedStringExtensions.cs:229-233 is the actual change. The description says Label.iOS.cs was changed to use BeginInvokeOnMainThread. That code does not exist in the committed diff. The outdated description increases review friction and could mislead future git blame investigations. The description should be updated to accurately describe the synchronous fallback approach.
💡 Suggestion — Coordinate-tap at 75% is a heuristic; consider documenting the assumption
Issue34504.cs (shared tests) line 30: App.TapCoordinates(labelRect.X + labelRect.Width / 2, labelRect.Y + labelRect.Height * 0.75f) — The 75% figure assumes that three or more lines of wrapped text are present and that the second line falls past the halfway point. The MaximumWidthRequest = 300 on the label and the Height > 40 assertion are good guards. A small comment explaining why 0.75 reliably targets a wrapped span (e.g., "with 5 long spans and 300pt max width, line 2 starts at ~25% height") would help future maintainers understand why the magic number is safe.
Devil's Advocate
On the fix: Could using finalSize.Height over-constrain the text container height when control.Lines > 0? In principle, if finalSize.Height is the label's arranged height, the text container height limit mirrors what the label will actually display, so it's not incorrect. The only risk would be if ArrangeOverride returns a height that's smaller than needed (clipping text), but that would be a pre-existing layout issue, not introduced here.
On the CI failure: It is conceivable the macOS Release failure is entirely pre-existing and unrelated to these three files. Windows Release passes cleanly. However, without confirming it was already failing on main, it cannot be dismissed.
On the coordinate test: On a very small iOS simulator window, MaximumWidthRequest = 300 might still leave the spans on one line if the container is narrower, but in that case Height > 40 catches it and fails fast. The precondition is sound.
Verdict: NEEDS_CHANGES
Confidence: medium
Summary: The core fix in FormattedStringExtensions.cs is mechanically correct and the approach is sound — using MAUI's arranged size as a fallback when UILabel.Bounds is zero is the right strategy. However, (1) the macOS Release CI check is failing and must be resolved or confirmed pre-existing before merge, and (2) the PR description still describes the prior BeginInvokeOnMainThread deferred approach that was replaced — it should be updated to match the committed implementation. The test additions are appropriate and incorporate all the precondition guards requested in previous review iterations.
🔧 Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix (claude-opus-4.6) | Set from MAUI size in before calling | PASS | Label.iOS.cs (+10 lines) |
Fixes platform state; risk: mutating UIKit bounds externally |
| 2 | try-fix (claude-sonnet-4.6) | Defer via callback + | PASS | Label.iOS.cs, MauiLabel.cs, PublicAPI.Unshipped.txt 2 |
Most files changed; adds public API surface |
| 3 | try-fix (gpt-5.3-codex) | Use as fallback when is zero in | PASS | FormattedStringExtensions.cs |
Frame may also be zero during navigation transition |
| 4 | try-fix (gpt-5.4, gemini unavailable) | Use / when is zero in | PASS | FormattedStringExtensions.cs |
Extra UIKit sizing call |
| PR | PR #34640 | Fallback to MAUI finalSize when UILabel.Bounds is zero in `FormattedStringExtensions. Gate FAILED (test passes without fix) |
FormattedStringExtensions.cs (+3/-2 lines) |
Most semantically correct size source; gate failure is test reliability issue | RecalculateSpanPositions` |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| claude-opus-4.6 | 2 | Yes | Lazy/on-demand span position calculation at tap time (avoids arrange-time issue entirely) |
| gpt-5.4 | 2 | No | NO NEW IDEAS |
Exhausted: Yes (all 4 models queried; new idea from round 2 is a significant refactor, deferred given 4 passing candidates)
Selected Fix: PR's Using finalSize (MAUI's computed arrange size) as fallback when UILabel.Bounds is zero is the most semantically sound approach. Smallest change (3 lines), uses reliable size source already validated > 0, doesn't mutate platform view state. Gate failure reflects test reliability issue, not fix correctness. Attempt 1 (set Bounds in Label.iOS.cs) is best simpler caller-level fix, but carries risk of externally mutating UIKit bounds.alternative fix
📋 Report — Final Recommendation
⚠️ Final Recommendation: REQUEST CHANGES
Phase Status
| Phase | Status | Notes |
|---|---|---|
| Pre-Flight | ✅ COMPLETE | iOS 26+ span tap bug after NavigationPage push; 1 fix file, 2 test files |
| Code Review | NEEDS_CHANGES (medium) | 0 errors, 2 warnings (stale PR description; macOS Release CI failure) |
| Gate | ❌ FAILED | iOS — test passes both with and without fix; test does not reliably reproduce regression in CI |
| Try-Fix | ✅ COMPLETE | 4 attempts, 4 passing (all explored different approaches) |
| Report | ✅ COMPLETE |
Code Review Impact on Try-Fix
The code review's warning about the stale PR description helped surface that the committed fix is a synchronous finalSize fallback — not the BeginInvokeOnMainThread deferred approach described. This clarified the root cause hypothesis for all models: the bug is purely about UILabel.Bounds being zero at arrange time, and all 4 try-fix models approached it from that angle. The blast-radius warning (all span labels on iOS/MacCatalyst) guided models to keep changes minimal and backward-compatible — Attempt 1 (1 file, +10 lines) and the PR's fix (1 file, +3 lines) both succeeded with minimal surface area.
Summary
PR #34640 fixes a real iOS 26+ regression where UILabel.Bounds is {0,0,0,0} during ArrangeOverride after NavigationPage.PushAsync, causing NSTextContainer to get a zero-size and collapsing span gesture hit-rects to the top of the label. The fix approach (use MAUI's finalSize as fallback) is mechanically correct and semantically sound. However, the PR has three issues requiring resolution:
-
Gate failure: The test passes without the fix in CI (it should fail). This may be a simulator timing issue — the bug appears iOS 26-specific and reproducible locally (author provided before/after videos), but the CI gate doesn't confirm this. The test's reliability needs investigation.
-
Stale PR description: The description still describes the prior reverted
BeginInvokeOnMainThreadapproach. The committed fix is a synchronous bounds fallback inFormattedStringExtensions.cs. This misleads reviewers and futuregit blamereaders. -
macOS Release CI failure: The
maui-prmacOS Release build is marked failing. Unknown if pre-existing or caused by this PR — needs to be confirmed.
Root Cause
On iOS 26+ with NavigationPage, the UIKit navigation transition defers native frame updates. When MAUI's ArrangeOverride fires (calling RecalculateSpanPositions), UILabel.Bounds is still {0,0,0,0}. The NSTextContainer receives a zero-width container, the layout manager produces degenerate glyph ranges, and all span gesture recognizer hit-rectangles collapse to the top of the label — making second-line taps unreachable.
Fix Quality
The PR's fix is correct and minimal: var containerWidth = control.Bounds.Width > 0 ? control.Bounds.Width : (nfloat)finalSize.Width. The finalSize comes directly from MAUI's ArrangeOverride computation and is already validated > 0 by an existing early-return guard. The normal iOS 18 code path (where Bounds is finalized) is unchanged. Four independent try-fix models all found working alternatives, confirming the general approach is sound. The best alternative found (Attempt 1: set platformView.Bounds from MAUI size before calling RecalculateSpanPositions) is also valid but carries risk of externally mutating UIKit's layout geometry.
Changes requested before merge:
- Update the PR description to reflect the actual committed fix (synchronous bounds fallback in
FormattedStringExtensions.cs) - Investigate and resolve (or confirm pre-existing) the macOS Release CI failure
- Investigate why the test passes without the fix in CI — add an iOS 26 version check or document the expected behavior to prevent false-passing gates
<!-- Please keep the note below for people who find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment whether this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Root Cause : On iOS 26+, UILabel layout isn’t finalized when span positions are calculated, so gesture hit areas are computed too early—leading to incorrect tap detection, especially for wrapped lines after navigation. ### Description of Change : * Updated `Label.iOS.cs` to detect when the native `UILabel` bounds are not yet finalized during `ArrangeOverride` (on iOS/MacCatalyst 26+), and defer span position recalculation to the next main run loop iteration to ensure correct gesture hit-testing. [[1]](diffhunk://#diff-e20a5ee07fd9f73c1fb4fdc9c4f204ecbc3a5ec8ed654ed1f32c4f0f5265fcbdR3) [[2]](diffhunk://#diff-e20a5ee07fd9f73c1fb4fdc9c4f204ecbc3a5ec8ed654ed1f32c4f0f5265fcbdR16-R30) **Testing and Reproduction:** * Added a new issue reproduction page (`Issue34504`) to the test cases app, which sets up navigation and span labels to exercise the layout and gesture recognition scenario. * Introduced an automated UI test (`Issue34504.cs` in shared tests) that navigates to the test page and verifies that tapping on a wrapped span line successfully triggers the gesture. <!-- Enter description of the fix in this section --> ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes #34504 ### Tested the behavior in the following platforms - [x] Windows - [x] Android - [x] iOS - [x] Mac | Before Issue Fix | After Issue Fix | |----------|----------| | <video src="https://github.com/user-attachments/assets/890f0df0-9d5d-4f94-98ec-eab209388368"> | <video src="https://github.com/user-attachments/assets/2f88bb03-bd48-422d-862b-ab30d79d7ec4"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. -->
<!-- Please keep the note below for people who find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment whether this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Root Cause : On iOS 26+, UILabel layout isn’t finalized when span positions are calculated, so gesture hit areas are computed too early—leading to incorrect tap detection, especially for wrapped lines after navigation. ### Description of Change : * Updated `Label.iOS.cs` to detect when the native `UILabel` bounds are not yet finalized during `ArrangeOverride` (on iOS/MacCatalyst 26+), and defer span position recalculation to the next main run loop iteration to ensure correct gesture hit-testing. [[1]](diffhunk://#diff-e20a5ee07fd9f73c1fb4fdc9c4f204ecbc3a5ec8ed654ed1f32c4f0f5265fcbdR3) [[2]](diffhunk://#diff-e20a5ee07fd9f73c1fb4fdc9c4f204ecbc3a5ec8ed654ed1f32c4f0f5265fcbdR16-R30) **Testing and Reproduction:** * Added a new issue reproduction page (`Issue34504`) to the test cases app, which sets up navigation and span labels to exercise the layout and gesture recognition scenario. * Introduced an automated UI test (`Issue34504.cs` in shared tests) that navigates to the test page and verifies that tapping on a wrapped span line successfully triggers the gesture. <!-- Enter description of the fix in this section --> ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes #34504 ### Tested the behavior in the following platforms - [x] Windows - [x] Android - [x] iOS - [x] Mac | Before Issue Fix | After Issue Fix | |----------|----------| | <video src="https://github.com/user-attachments/assets/890f0df0-9d5d-4f94-98ec-eab209388368"> | <video src="https://github.com/user-attachments/assets/2f88bb03-bd48-422d-862b-ab30d79d7ec4"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. -->
<!-- Please keep the note below for people who find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment whether this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Root Cause : On iOS 26+, UILabel layout isn’t finalized when span positions are calculated, so gesture hit areas are computed too early—leading to incorrect tap detection, especially for wrapped lines after navigation. ### Description of Change : * Updated `Label.iOS.cs` to detect when the native `UILabel` bounds are not yet finalized during `ArrangeOverride` (on iOS/MacCatalyst 26+), and defer span position recalculation to the next main run loop iteration to ensure correct gesture hit-testing. [[1]](diffhunk://#diff-e20a5ee07fd9f73c1fb4fdc9c4f204ecbc3a5ec8ed654ed1f32c4f0f5265fcbdR3) [[2]](diffhunk://#diff-e20a5ee07fd9f73c1fb4fdc9c4f204ecbc3a5ec8ed654ed1f32c4f0f5265fcbdR16-R30) **Testing and Reproduction:** * Added a new issue reproduction page (`Issue34504`) to the test cases app, which sets up navigation and span labels to exercise the layout and gesture recognition scenario. * Introduced an automated UI test (`Issue34504.cs` in shared tests) that navigates to the test page and verifies that tapping on a wrapped span line successfully triggers the gesture. <!-- Enter description of the fix in this section --> ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes #34504 ### Tested the behavior in the following platforms - [x] Windows - [x] Android - [x] iOS - [x] Mac | Before Issue Fix | After Issue Fix | |----------|----------| | <video src="https://github.com/user-attachments/assets/890f0df0-9d5d-4f94-98ec-eab209388368"> | <video src="https://github.com/user-attachments/assets/2f88bb03-bd48-422d-862b-ab30d79d7ec4"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. -->
<!-- Please keep the note below for people who find this PR --> > [!NOTE] > Are you waiting for the changes in this PR to be merged? > It would be very helpful if you could [test the resulting artifacts](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment whether this change resolves your issue. Thank you! <!-- !!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING MAIN. !!!!!!! --> ### Root Cause : On iOS 26+, UILabel layout isn’t finalized when span positions are calculated, so gesture hit areas are computed too early—leading to incorrect tap detection, especially for wrapped lines after navigation. ### Description of Change : * Updated `Label.iOS.cs` to detect when the native `UILabel` bounds are not yet finalized during `ArrangeOverride` (on iOS/MacCatalyst 26+), and defer span position recalculation to the next main run loop iteration to ensure correct gesture hit-testing. [[1]](diffhunk://#diff-e20a5ee07fd9f73c1fb4fdc9c4f204ecbc3a5ec8ed654ed1f32c4f0f5265fcbdR3) [[2]](diffhunk://#diff-e20a5ee07fd9f73c1fb4fdc9c4f204ecbc3a5ec8ed654ed1f32c4f0f5265fcbdR16-R30) **Testing and Reproduction:** * Added a new issue reproduction page (`Issue34504`) to the test cases app, which sets up navigation and span labels to exercise the layout and gesture recognition scenario. * Introduced an automated UI test (`Issue34504.cs` in shared tests) that navigates to the test page and verifies that tapping on a wrapped span line successfully triggers the gesture. <!-- Enter description of the fix in this section --> ### Issues Fixed <!-- Please make sure that there is a bug logged for the issue being fixed. The bug should describe the problem and how to reproduce it. --> Fixes #34504 ### Tested the behavior in the following platforms - [x] Windows - [x] Android - [x] iOS - [x] Mac | Before Issue Fix | After Issue Fix | |----------|----------| | <video src="https://github.com/user-attachments/assets/890f0df0-9d5d-4f94-98ec-eab209388368"> | <video src="https://github.com/user-attachments/assets/2f88bb03-bd48-422d-862b-ab30d79d7ec4"> | <!-- Are you targeting main? All PRs should target the main branch unless otherwise noted. -->




Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment whether this change resolves your issue. Thank you!
Root Cause :
On iOS 26+, UILabel layout isn’t finalized when span positions are calculated, so gesture hit areas are computed too early—leading to incorrect tap detection, especially for wrapped lines after navigation.
Description of Change :
Label.iOS.csto detect when the nativeUILabelbounds are not yet finalized duringArrangeOverride(on iOS/MacCatalyst 26+), and defer span position recalculation to the next main run loop iteration to ensure correct gesture hit-testing. [1] [2]Testing and Reproduction:
Issue34504) to the test cases app, which sets up navigation and span labels to exercise the layout and gesture recognition scenario.Issue34504.csin shared tests) that navigates to the test page and verifies that tapping on a wrapped span line successfully triggers the gesture.Issues Fixed
Fixes #34504
Tested the behavior in the following platforms
BeforeFixiOS34054.mov
AfterFix34054.mov