Skip to content

[iOS] Fix span Tap gesture on wrapped Label lines in iOS 26+#34640

Open
SubhikshaSf4851 wants to merge 4 commits intodotnet:mainfrom
SubhikshaSf4851:Fix-34504
Open

[iOS] Fix span Tap gesture on wrapped Label lines in iOS 26+#34640
SubhikshaSf4851 wants to merge 4 commits intodotnet:mainfrom
SubhikshaSf4851:Fix-34504

Conversation

@SubhikshaSf4851
Copy link
Copy Markdown
Contributor

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 :

  • 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] [2]

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.

Issues Fixed

Fixes #34504

Tested the behavior in the following platforms

  • Windows
  • Android
  • iOS
  • Mac
Before Issue Fix After Issue Fix
BeforeFixiOS34054.mov
AfterFix34054.mov

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 25, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34640

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34640"

@dotnet-policy-service dotnet-policy-service bot added community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration labels Mar 25, 2026
@sheiksyedm sheiksyedm marked this pull request as ready for review March 26, 2026 10:37
Copilot AI review requested due to automatic review settings March 26, 2026 10:37
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.ArrangeOverride to defer RecalculateSpanPositions when UILabel.Bounds is 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.

@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Mar 27, 2026

🚦 Gate - Test Before and After Fix

📊 Expand Full Gatecd50119 · Updated the recommended changes

Gate Result: ❌ FAILED

Platform: IOS · Base: main · Merge base: 720a9d4a

Test Without Fix (expect FAIL) With Fix (expect PASS)
🖥️ Issue34504 Issue34504 ❌ PASS — 201s ✅ PASS — 82s
🔴 Without fix — 🖥️ Issue34504: PASS ❌ · 201s
  Determining projects to restore...
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 1.93 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 2.39 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 8.95 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 13.02 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 13.02 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Xaml/Controls.Xaml.csproj (in 13.04 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj (in 13.08 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/Foldable/src/Controls.Foldable.csproj (in 13.08 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj (in 13.09 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/Maps/src/Controls.Maps.csproj (in 13.12 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Core/maps/src/Maps.csproj (in 13.14 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.60-ci+azdo.13700268
  Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0-ios26.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13700268
  Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0-ios26.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13700268
  Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0-ios26.0/Microsoft.Maui.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13700268
  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.60-ci+azdo.13700268
  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.60-ci+azdo.13700268
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13700268
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13700268
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13700268
  Controls.Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Maps.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.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:01:35.75
  Determining projects to restore...
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.Core/UITest.Core.csproj (in 762 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 762 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/VisualTestUtils/VisualTestUtils.csproj (in 762 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/tests/CustomAttributes/Controls.CustomAttributes.csproj (in 762 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 762 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 776 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 842 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 851 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 1.34 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/VisualTestUtils.MagickNet/VisualTestUtils.MagickNet.csproj (in 2.46 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.iOS.Tests/Controls.TestCases.iOS.Tests.csproj (in 3.27 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.Analyzers/UITest.Analyzers.csproj (in 3.56 sec).
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13700268
  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.60-ci+azdo.13700268
  Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13700268
  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.60-ci+azdo.13700268
  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.04]   Discovering: Controls.TestCases.iOS.Tests
[xUnit.net 00:00:00.12]   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
>>>>> 3/31/2026 8:34:07 AM FixtureSetup for Issue34504(iOS)
>>>>> 3/31/2026 8:34:12 AM SpanTapGestureOnSecondLineShouldWork Start
>>>>> 3/31/2026 8:34:13 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: 57.0925 Seconds

🟢 With fix — 🖥️ Issue34504: PASS ✅ · 82s
  Determining projects to restore...
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 477 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 501 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 506 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 533 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 547 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.60-ci+azdo.13700268
  Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0-ios26.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13700268
  Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0-ios26.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13700268
  Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0-ios26.0/Microsoft.Maui.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13700268
  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.60-ci+azdo.13700268
  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.60-ci+azdo.13700268
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13700268
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13700268
  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
  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.60-ci+azdo.13700268
  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
  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:40.29
  Determining projects to restore...
  Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 457 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 486 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 489 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 513 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 526 ms).
  8 of 13 projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13700268
  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.60-ci+azdo.13700268
  Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13700268
  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.60-ci+azdo.13700268
  Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
  VisualTestUtils -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
  UITest.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
  VisualTestUtils.MagickNet -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
  UITest.NUnit -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.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
>>>>> 3/31/2026 8:35:29 AM FixtureSetup for Issue34504(iOS)
>>>>> 3/31/2026 8:35:34 AM SpanTapGestureOnSecondLineShouldWork Start
>>>>> 3/31/2026 8:35:36 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: 18.2028 Seconds

⚠️ Issues found
  • Issue34504 PASSED without fix (should fail) — tests don't catch the bug
📁 Fix files reverted (2 files)
  • eng/pipelines/ci-copilot.yml
  • src/Controls/src/Core/Platform/iOS/Extensions/FormattedStringExtensions.cs

@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Mar 27, 2026

🤖 AI Summary

📊 Expand Full Reviewcd50119 · Updated the recommended changes
🔍 Pre-Flight — Context & Validation

Issue: #34504 - [iOS] Span TapGestureRecognizer does not work on the second line of the span, if the span is wrapped to the next line
PR: #34640 - [iOS] Fix span Tap gesture on wrapped Label lines in iOS 26+
Platforms Affected: iOS 26+ (not reproducible on iOS 18.4 or Android/Windows)
Files Changed: 1 implementation, 2 test

Key Findings

  • Bug manifests on iOS 26+: when navigating to a second page via NavigationPage, UILabel.Bounds is {0,0,0,0} during ArrangeOverride. This makes textContainer.Size width=0, causing span gesture hit-areas to be computed incorrectly.
  • Fix location: RecalculateSpanPositions in FormattedStringExtensions.cs — uses finalSize (MAUI-computed size) as fallback when control.Bounds is zeroed.
  • PR description mentions Label.iOS.cs with BeginInvokeOnMainThread deferral, but current diff only changes FormattedStringExtensions.cs. The deferral approach was abandoned in favor of the fallback approach.
  • Copilot inline review raised two valid concerns:
    1. Label.iOS.cs: unused using CoreFoundation; (may cause compiler warning/error). (No longer relevant — Label.iOS.cs is NOT in the final diff.)
    2. BeginInvokeOnMainThread capturing a potentially stale size. (No longer relevant — PR moved away from that approach.)
    3. Test's BuildSpanLabel doesn't constrain width → text may not wrap on all devices. (AddressedMaximumWidthRequest = 300 was added.)
    4. Test should assert label is multi-line before tapping. (AddressedAssert.That(labelRect.Height, Is.GreaterThan(40)) was added.)
  • Gate FAILED: Test passes BOTH with and without the fix, meaning it does not reliably catch the regression. Root cause: Appium.WaitForElement runs after full layout is complete; by then UILabel.Bounds may already be set, so subsequent interaction works even without the fix. The test doesn't capture the critical timing window during ArrangeOverride.
  • Potential concern: the fix in FormattedStringExtensions.cs uses finalSize.Width as fallback, but there is already an early-return guard if (finalSize.Width <= 0 || finalSize.Height <= 0) { return; }. If control.Bounds.Width is 0 AND finalSize.Width > 0, the fix correctly provides the width to the text container.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #34640 Fallback to finalSize when control.Bounds is zero in RecalculateSpanPositions ❌ GATE FAILED (test passes w/o fix) FormattedStringExtensions.cs Original PR

🔧 Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix (claude-opus-4.6) Deferred RecalculateSpanPositions via BeginInvokeOnMainThread in Label.iOS.cs when bounds is 0 ✅ PASS 1 file Uses real UIKit geometry after layout settles
2 try-fix (claude-sonnet-4.6) LayoutSubviews hook in MauiLabel (mirrors Android LayoutChanged pattern) ✅ PASS 4 files (incl. PublicAPI) More invasive; touches Core platform layer
3 try-fix (gpt-5.3-codex) SizeChanged-triggered span region recalculation in Label.iOS.cs ✅ PASS 1 file No bounds fallback, no deferral, no LayoutSubviews
4 try-fix (gpt-5.4) Always use finalSize unconditionally in FormattedStringExtensions.cs (removes UILabel.Bounds dependence entirely) ✅ PASS 1 file (2 lines) Simplest fix; strictly cleaner than PR's conditional
5 try-fix (claude-opus-4.6) Lazy/on-demand recalculation at hit-test time in GetChildElements — self-healing, skip when data bad ✅ PASS 3 files Most architecturally distinct; adds state complexity
PR PR #34640 Conditional fallback to finalSize in FormattedStringExtensions.cs when control.Bounds is zero ❌ GATE FAILED 1 file Fix logic is sound; test doesn't catch regression

Cross-Pollination

Model Round New Ideas? Details
claude-opus-4.6 2 Yes Lazy recalculation at hit-test time → ran as Attempt 5
gpt-5.3-codex 2 Yes Use TextKit's NSLayoutManager.UsedRectForTextContainer from measured size (variant of PR approach)
claude-sonnet-4.6 3 Yes DidMoveToWindow override in MauiLabel (variant of Attempt 2)
gpt-5.4 3 Yes SetNeedsLayout/LayoutIfNeeded flush before span calc (risky — could cause layout cycles)

Exhausted: Yes — all remaining ideas are variants of existing approaches or risky anti-patterns
Selected Fix: Attempt 4 (always use finalSize) — smallest change, strictly cleaner logic than PR's conditional. Alternatively PR's fix is fine and more conservative.
Why not PR: Gate FAILED (test passes without fix). Fix logic is correct but unconfirmed empirically.
Key concern: All 5 try-fix attempts AND the PR's fix all pass the same test — because the test doesn't actually reproduce the iOS 26+ timing bug reliably.


📋 Report — Final Recommendation

⚠️ Final Recommendation: REQUEST CHANGES

Phase Status

Phase Status Notes
Pre-Flight ✅ COMPLETE Issue #34504, iOS 26+ span gesture timing bug
Gate ❌ FAILED iOS — test passes both with and without fix
Try-Fix ✅ COMPLETE 5 attempts, all 5 passing (but same test weakness applies)
Report ✅ COMPLETE

Summary

PR #34640 fixes a real iOS 26+ bug where UILabel.Bounds is {0,0,0,0} during ArrangeOverride after navigation, causing span gesture hit-areas to be computed with zero dimensions. The fix in FormattedStringExtensions.cs is logically sound. However, the gate failed: the test passes both with and without the fix, meaning the test does not reliably reproduce the iOS 26+ timing regression. The fix cannot be confirmed empirically from this review.

Root Cause

On iOS 26+ with NavigationPage, UIKit defers committing UILabel.Bounds until after the native layout pass completes. MAUI's ArrangeOverride calls RecalculateSpanPositions during this window when control.Bounds.Width/Height is still {0,0,0,0}. The NSTextContainer.Size is then set to zero, causing NSLayoutManager to compute all glyph rects collapsed at (0,0). Span gesture regions are stored with these wrong rects, so taps on wrapped lines (which have non-zero Y offsets) never match and no gesture fires.

Fix Quality

PR's fix (conditional 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);
  • ✅ Correct fix location (FormattedStringExtensions.cs)
  • ✅ Conservative — only deviates from Bounds when it's zero
  • ✅ Minimal change (3 lines, 1 file)
  • ⚠️ Slightly more code than necessary — Attempt 4 shows finalSize can be used unconditionally since it's already validated > 0 earlier in the method
  • ❌ Gate FAILED — test does not catch the regression

Simpler alternative (Attempt 4, gpt-5.4):

textContainer.Size = new((nfloat)finalSize.Width,
    control.Lines == 0 ? nfloat.MaxValue : (nfloat)finalSize.Height);
  • ✅ Simplest possible fix (same 1 file, 2 lines changed)
  • ✅ Removes UILabel.Bounds dependence entirely for this calculation
  • finalSize is already validated > 0 by the early-return guard above
  • ⚠️ Changes semantics globally (not just iOS 26+): previously Bounds was preferred when non-zero. For span hit-area computation, finalSize (MAUI's layout measurement) is arguably more correct anyway.

Issues to Address

  1. Test reliability (blocking): The test SpanTapGestureOnSecondLineShouldWork passes without the fix on the iOS 26 simulator. This means the test exercises the UI only after layout has settled — by which time any timing-window bug has self-resolved. A reliable test would need to catch the in-layout state (e.g., checking span regions directly, or using a mock that records whether RecalculateSpanPositions produced non-zero rects). If a reliable automated test is not feasible, the PR description should document this limitation explicitly.

  2. PR description mismatch: The description mentions deferring via BeginInvokeOnMainThread in Label.iOS.cs, but the actual diff only changes FormattedStringExtensions.cs. The description should be updated to reflect the current implementation.

  3. Minor improvement: Consider using finalSize unconditionally (Attempt 4's approach) instead of the conditional fallback — it's simpler and equally correct.


@MauiBot MauiBot added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) and removed s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-review-incomplete AI agent could not complete all phases (blocker, timeout, error) labels Mar 27, 2026
Copy link
Copy Markdown
Contributor

@kubaflo kubaflo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the test couldn't catch a bug before fix

@SubhikshaSf4851
Copy link
Copy Markdown
Contributor Author

@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 :

Without Fix With Fix
WithoutFix18versioniOS WithFix18versioniOS

iOS 26 version test results :

Without Fix With Fix
WithoutFix26versioniOS WithFix26versioniOS

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-controls-label Label, Span community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration platform/ios s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[iOS]Span TapGestureRecognizer does not work on the second line of the span, if the span is wrapped to the next line

5 participants