Skip to content

[Mac] Fix Prevent CollectionView scroll position reset on Mac Catalyst after Picker interaction - #36431

Merged
kubaflo merged 5 commits into
dotnet:inflight/currentfrom
Vignesh-SF3580:fix-34271New
Jul 17, 2026
Merged

[Mac] Fix Prevent CollectionView scroll position reset on Mac Catalyst after Picker interaction#36431
kubaflo merged 5 commits into
dotnet:inflight/currentfrom
Vignesh-SF3580:fix-34271New

Conversation

@Vignesh-SF3580

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 if this change resolves your issue. Thank you!

Note: This PR was created because the previous PR (#34356) was closed.

Root Cause

On Mac Catalyst, certain window state changes (opening/dismissing a Picker, minimizing/restoring, or resizing the window) cause UIKit to silently adjust the UICollectionView contentOffset when the view is programmatically scrolled near or to the last item.

During these transitions, UIKit recalculates scroll bounds using estimated item sizes (NSCollectionLayoutDimension.CreateEstimated(30f)). Because the estimated contentSize is smaller than the actual contentSize, UIKit calculates a lower maximum valid contentOffset and silently clamps the current offset down to that value. The scroll position does not always jump to the very top — it shifts to whatever UIKit calculates as the maximum valid offset: max(0, estimatedContentSize - frameHeight). No delegate, layout, or view controller callbacks fire — the change happens entirely inside UIKit's property mutation machinery.

Why items near the bottom are affected: Items near the bottom require a larger offset than UIKit's estimated bounds permit. Intermediate items sit within the estimated range and are unaffected.

Why only Mac Catalyst: The same window transitions on iOS do not trigger this recalculation behavior.

Why conventional fixes fail: Standard callbacks (LayoutSubviews, ViewDidLayoutSubviews, Scrolled) are not triggered because this is not a layout pass or a user scroll. KVO on contentOffset is the only mechanism that fires synchronously at the moment UIKit mutates the property.

Description of Change

KVO-Based Scroll Restore

The fix uses NSObject.AddObserver("contentOffset", ...) (Key-Value Observing) to detect the silent reset and restore the scroll position.

Flow:

  1. After a non-animated ScrollTo, SetPendingScrollRestore(section, item, position) is called, storing the target index path as plain int fields and enabling tracking.
  2. KVO OnContentOffsetChanged monitors every contentOffset change:
    • While the new Y is within 10px of the last known Y: treat as normal movement, update reference.
    • If Y drops more than 10px below the last known Y: silent reset detected → async-dispatches ScrollToItem to restore position.
  3. DraggingStarted clears the restore target when the user manually scrolls (respects user intent).
  4. KVO lifecycle is managed in MovedToWindow — started when view attaches, stopped when it detaches.

Why relative threshold (not Y < 1.0): UIKit does not always reset to Y=0. It clamps to max(0, estimatedContentSize - frameHeight), which can be a non-zero value. A fixed Y < 1.0 check would miss non-zero resets entirely. Comparing against the last known Y detects any sudden downward shift regardless of the absolute value.

Why plain int storage (not NSIndexPath): NSIndexPath is created inside a using block in ScrollToRequested and is disposed before the KVO callback fires. Storing section and item as int fields avoids this lifetime issue.

Why ScrollToItem instead of SetContentOffset: ScrollToItem recalculates the pixel offset using UIKit's current layout (including actual item heights), yielding the exact visual position. SetContentOffset uses a raw pixel value that becomes stale after layout changes.

Issues Fixed

Fixes #34271

Files Changed

File Change
src/Controls/src/Core/Handlers/Items/iOS/MauiCollectionView.cs KVO observer infrastructure, scroll restore state, SetPendingScrollRestore, ClearPendingScrollRestore, MovedToWindow lifecycle
src/Controls/src/Core/Handlers/Items2/ItemsViewHandler2.iOS.cs Call SetPendingScrollRestore after non-animated ScrollToItem; capture section/item as ints inside using block
src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs DraggingStarted override to clear restore target on user drag
src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt New public override: ItemsViewDelegator2.DraggingStarted

Total: fix spans 4 production files

Screenshots

Before Issue Fix After Issue Fix
34271BeforeFix.mov
34271AfterFix.mov

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

🚀 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 -- 36431

Or

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

@dotnet-policy-service dotnet-policy-service Bot added the partner/syncfusion Issues / PR's with Syncfusion collaboration label Jul 7, 2026
@Tamilarasan-Paranthaman Tamilarasan-Paranthaman added community ✨ Community Contribution platform/macos macOS / Mac Catalyst area-controls-collectionview CollectionView, CarouselView, IndicatorView labels Jul 7, 2026
@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Jul 7, 2026

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 2 findings

See inline comments for details.

Comment thread src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs
Comment thread src/Controls/src/Core/Handlers/Items/iOS/MauiCollectionView.cs
@MauiBot MauiBot added s/agent-gate-failed AI could not verify tests catch the bug s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels Jul 7, 2026
MauiBot

This comment was marked as outdated.

@MauiBot MauiBot removed the s/agent-review-in-progress AI review is currently running for this PR label Jul 7, 2026
@Vignesh-SF3580
Vignesh-SF3580 marked this pull request as ready for review July 8, 2026 06:01
Copilot AI review requested due to automatic review settings July 8, 2026 06:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR targets a Mac Catalyst-specific UIKit behavior where UICollectionView silently clamps contentOffset (typically after a non-animated ScrollTo near the end of the list) during window state transitions like Picker dismissal, which makes CollectionView appear to “jump”/lose its scroll position.

Changes:

  • Adds a Mac Catalyst-only KVO observer on contentOffset in MauiCollectionView to detect sudden downward clamps and restore the intended position via ScrollToItem.
  • Arms/clears the pending “restore target” around non-animated ScrollTo operations and clears the restore state when the user begins dragging.
  • Adds a new UI test page + UITest for Issue #34271 and updates Mac Catalyst PublicAPI entries for new overrides.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/Controls/src/Core/Handlers/Items/iOS/MauiCollectionView.cs Mac Catalyst-only KVO tracking and restore logic for silent contentOffset clamps; observer lifecycle in MovedToWindow.
src/Controls/src/Core/Handlers/Items2/ItemsViewHandler2.iOS.cs Arms pending restore after non-animated ScrollToItem; clears restore on ItemsSource/layout changes (Mac Catalyst).
src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs Clears pending restore on user drag start (Mac Catalyst).
src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt Records new/updated Mac Catalyst override signatures introduced by the fix.
src/Controls/tests/TestCases.HostApp/Issues/Issue34271.cs Adds a HostApp reproduction page for the scenario (scroll-to-last + Picker interaction + layout recomputation trigger).
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue34271.cs Adds an Appium-based UITest to validate scroll position is preserved across Picker dismissal and forced layout recomputation.

Comment thread src/Controls/tests/TestCases.HostApp/Issues/Issue34271.cs
@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Jul 8, 2026

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 3 findings

See inline comments for details.

Comment thread src/Controls/src/Core/Handlers/Items/iOS/MauiCollectionView.cs
Comment thread src/Controls/src/Core/Handlers/Items2/ItemsViewHandler2.iOS.cs Outdated
Comment thread src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs
MauiBot

This comment was marked as outdated.

@MauiBot MauiBot removed the s/agent-review-in-progress AI review is currently running for this PR label Jul 8, 2026
@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Jul 8, 2026
@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Jul 14, 2026

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Review Summary

@Vignesh-SF3580 — new AI review results are available based on this last commit: 8e85bae. To request a fresh review after new comments or commits, comment /review rerun.

Gate Partial Confidence Low Platform Catalyst


🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After Fix

Gate Result: ❌ FAILED

Platform: CATALYST · Base: main · Merge base: 0395a53b

🩺 Test does not reproduce the bug — ran the same in both states (PASS without fix, PASS with fix). The repro test is not exercising the issue. Strengthen the test before reviewing the fix.

Test Without Fix (expect FAIL) With Fix (expect PASS)
🖥️ Issue34271 Issue34271 ❌ PASS — 449s ✅ PASS — 121s
🔴 Without fix — 🖥️ Issue34271: PASS ❌ · 449s
  Determining projects to restore...
  Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 627 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 5.19 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Core/maps/src/Maps.csproj (in 6.97 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 6.97 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj (in 6.32 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 6.97 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Xaml/Controls.Xaml.csproj (in 6.97 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/Foldable/src/Controls.Foldable.csproj (in 6.99 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/Maps/src/Controls.Maps.csproj (in 6.97 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj (in 7.15 sec).
  1 of 11 projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.dll
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Maps/Debug/net10.0-maccatalyst26.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.100-ci+azdo.14657503
  Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Controls.Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Controls.Maps.dll
  Controls.Xaml -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-maccatalyst26.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-maccatalyst26.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
  Controls.Foldable -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Controls.Foldable.dll
  Detected signing identity:
    Code Signing Key: "" (-)
    Provisioning Profile: "" () - no entitlements
    Bundle Id: com.microsoft.maui.uitests
    App Id: com.microsoft.maui.uitests
  Detected signing identity:
    Code Signing Key: "" (-)
    Provisioning Profile: "" () - no entitlements
    Bundle Id: com.microsoft.maui.uitests
    App Id: com.microsoft.maui.uitests
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Controls.TestCases.HostApp -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-maccatalyst/maccatalyst-x64/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.
  Detected signing identity:
    Code Signing Key: "" (-)
    Provisioning Profile: "" () - no entitlements
    Bundle Id: com.microsoft.maui.uitests
    App Id: com.microsoft.maui.uitests
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Controls.TestCases.HostApp -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-maccatalyst/maccatalyst-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.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:01:42.76
  Determining projects to restore...
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/VisualTestUtils/VisualTestUtils.csproj (in 811 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/tests/CustomAttributes/Controls.CustomAttributes.csproj (in 751 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.Core/UITest.Core.csproj (in 746 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.NUnit/UITest.NUnit.csproj (in 1.88 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.Appium/UITest.Appium.csproj (in 2.87 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.Analyzers/UITest.Analyzers.csproj (in 3.95 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/VisualTestUtils.MagickNet/VisualTestUtils.MagickNet.csproj (in 7 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.Mac.Tests/Controls.TestCases.Mac.Tests.csproj (in 7.04 sec).
  5 of 13 projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  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.100-ci+azdo.14657503
  Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  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.100-ci+azdo.14657503
  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.Mac.Tests -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.Mac.Tests/Debug/net10.0/Controls.TestCases.Mac.Tests.dll
Test run for /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.Mac.Tests/Debug/net10.0/Controls.TestCases.Mac.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.
[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.08]   Discovering: Controls.TestCases.Mac.Tests
[xUnit.net 00:00:00.20]   Discovered:  Controls.TestCases.Mac.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.Mac.Tests/Debug/net10.0/Controls.TestCases.Mac.Tests.dll
   NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 7/14/2026 4:26:15 PM FixtureSetup for Issue34271(Mac)
>>>>> 7/14/2026 4:26:16 PM CollectionViewScrollPositionPreservedAfterPickerDismiss Start
>>>>> 7/14/2026 4:26:29 PM CollectionViewScrollPositionPreservedAfterPickerDismiss Stop
NUnit Adapter 4.5.0.0: Test execution complete
  Passed CollectionViewScrollPositionPreservedAfterPickerDismiss [13 s]
Results File: /Users/cloudtest/vss/_work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue34271.trx

Test Run Successful.
Total tests: 1
     Passed: 1
 Total time: 35.7911 Seconds
>>> TRX_RESULT_FILE: /Users/cloudtest/vss/_work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue34271.trx

🟢 With fix — 🖥️ Issue34271: PASS ✅ · 121s
  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.dll
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  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-maccatalyst26.0/Microsoft.Maui.Maps.dll
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Microsoft.AspNetCore.Components.WebView.Maui -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-maccatalyst26.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
  Controls.Xaml -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Controls.Xaml.dll
  Controls.Foldable -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Controls.Foldable.dll
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Controls.Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-maccatalyst26.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
  Detected signing identity:
    Code Signing Key: "" (-)
    Provisioning Profile: "" () - no entitlements
    Bundle Id: com.microsoft.maui.uitests
    App Id: com.microsoft.maui.uitests
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Controls.TestCases.HostApp -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-maccatalyst/maccatalyst-x64/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.
  Detected signing identity:
    Code Signing Key: "" (-)
    Provisioning Profile: "" () - no entitlements
    Bundle Id: com.microsoft.maui.uitests
    App Id: com.microsoft.maui.uitests
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Controls.TestCases.HostApp -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-maccatalyst/maccatalyst-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.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:01:13.11
  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  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.100-ci+azdo.14657503
  Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  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.100-ci+azdo.14657503
  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
  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.Mac.Tests -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.Mac.Tests/Debug/net10.0/Controls.TestCases.Mac.Tests.dll
Test run for /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.Mac.Tests/Debug/net10.0/Controls.TestCases.Mac.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.
[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.Mac.Tests
[xUnit.net 00:00:00.18]   Discovered:  Controls.TestCases.Mac.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.Mac.Tests/Debug/net10.0/Controls.TestCases.Mac.Tests.dll
   NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 7/14/2026 4:32:40 PM FixtureSetup for Issue34271(Mac)
>>>>> 7/14/2026 4:32:40 PM CollectionViewScrollPositionPreservedAfterPickerDismiss Start
>>>>> 7/14/2026 4:32:54 PM CollectionViewScrollPositionPreservedAfterPickerDismiss Stop
  Passed CollectionViewScrollPositionPreservedAfterPickerDismiss [14 s]
NUnit Adapter 4.5.0.0: Test execution complete
Results File: /Users/cloudtest/vss/_work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue34271.trx

Test Run Successful.
Total tests: 1
     Passed: 1
 Total time: 24.0253 Seconds
>>> TRX_RESULT_FILE: /Users/cloudtest/vss/_work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue34271.trx

⚠️ Failure Details

  • Issue34271 PASSED without fix (should fail) — tests don't catch the bug
📁 Fix files reverted (6 files)
  • src/Controls/src/Core/Handlers/Items/iOS/MauiCollectionView.cs
  • src/Controls/src/Core/Handlers/Items2/CarouselViewHandler2.iOS.cs
  • src/Controls/src/Core/Handlers/Items2/ItemsViewHandler2.iOS.cs
  • src/Controls/src/Core/Handlers/Items2/iOS/CarouselViewController2.cs
  • src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs
  • src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt

📱 UI Tests — CollectionView

Detected UI test categories: CollectionView

Deep UI tests — 309 passed, 81 failed across 1 category on platform-pool agent (replaces in-process counts above).

🧪 UI Test Execution Results (deep, platform pool)

Category Tests Snapshot diffs
CollectionView 309/391 (81 ❌) 162 diff PNGs
CollectionView — 81 failed tests
CollectionViewHeaderBlankWhenLastItemRemoved
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: CollectionViewHeaderBlankWhenLastItemRemoved.png (43.53% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullabl
...
SelectedItemVisualIsCleared
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: SelectedItemVisualIsCleared.png (42.70% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, N
...
CheckEmptyViewMargin
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: CheckEmptyViewMargin.png (29.68% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, Nullable
...
ItemShouldbeScrolledbasedOnGroupHeader
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: ItemShouldbeScrolledbasedOnGroupHeader.png (40.15% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 re
...
ItemsWrapGridShouldUpdateBasedOnCollectionViewSize
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: ItemsWrapGridWithDefaultWidth.png (37.63% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at Microsoft.Maui.TestCases.Tests.Issues.Issue26083.ItemsWrapGridShouldUpdateBasedOnCollectionViewSize() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue26083.cs:line 28
   at System.RuntimeMethodHandle.InvokeMethod(ObjectHandleOnStack target, Void** arguments, ObjectHandleOnStack sig, BOOL isConstructor, ObjectHandleOnStack result)
   at System.RuntimeMethodHandle.InvokeMethod(ObjectHandleOnStack target, Void** arguments, ObjectHandleOnStack sig, BOOL isConstructor, ObjectHandleOnStack result)
   at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
   at System.Reflection.RuntimeMetho
...
VerifyScrollToByItemWithEndPositionAndVerticalList_Carrot
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerifyScrollToByItemWithEndPositionAndVerticalList_Carrot.png (42.70% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String 
...
VerifyScrollToByItemWithMakeVisiblePositionAndVerticalList_Carrot
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerifyScrollToByItemWithMakeVisiblePositionAndVerticalList_Carrot.png (42.70% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot
...
VerifyMeasureAllItemsWithObservableCollection
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerifyMeasureAllItemsWithObservableCollection.png (30.81% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, Nullable`1 retryTimeout, Int32 cropLeft, Int32 cropRight, Int32 cropTop, Int32 cropBottom, Double tolerance, Boolean includeTitleBar) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 296
   at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyMeasureAllItemsWithObservableCollection() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 53
   at System.RuntimeMethodHandle.InvokeMethod(ObjectHandleOnStack target, Void** arguments, Object
...
AccessibilityTraitsSetCorrectly
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: AccessibilityTraitsSetCorrectlyNone.png (24.87% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retry
...
RefreshShouldNotChangeSize
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: RefreshShouldNotChangeSize.png (22.73% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, Nu
...
VerifyFlowDirectionRTLAndMeasureAllItemsWithObservableCollection
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerifyFlowDirectionRTLAndMeasureAllItemsWithObservableCollection.png (30.57% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(
...
VerifyCollectionViewVisualState
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerifyCollectionViewVisualState.png (40.81% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDela
...
FooterShouldDisplayAtBottomOfEmptyView
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: FooterShouldDisplayAtBottomOfEmptyView.png (18.82% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 re
...
CVHorizontalLinearItemsLayoutItemSpacing
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: CVHorizontalLinearItemsLayoutItemSpacing.png (31.48% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 
...
VerifyScrollToByIndexWithMakeVisiblePositionAndVerticalList_Carrot
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerifyScrollToByIndexWithMakeVisiblePositionAndVerticalList_Carrot.png (42.70% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreensho
...
Issue18751Test
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: Issue18751Test.png (41.16% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, Nullable`1 retryTimeout, Int32 cropLeft, Int32 cropRight, Int32 cropTop, Int32 cropBottom, Double tolerance, Boolean includeTitleBar) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 296
   at Microsoft.Maui.TestCases.Tests.Issues.Issue18751.Issue18751Test() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue18751.cs:line 22
   at System.RuntimeMethodHandle.InvokeMethod(ObjectHandleOnStack target, Void** arguments, ObjectHandleOnStack sig, BOOL isConstructor, ObjectHandleOnStack result)
   at System.RuntimeMethodHandle.InvokeMethod(O
...
VerifyCustomSizedEmptyViewDisplaysCorrectly_WithRightToLeftFlowDirection
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerifyCustomSizedEmptyViewDisplaysCorrectly_WithRightToLeftFlowDirection.png (42.37% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScr
...
FlowdirectionShouldWorkForHeaderFooter
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: FlowdirectionShouldWorkForHeaderFooter.png (22.92% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 re
...
VerifyModelItemsGroupedListWhenSingleModePreSelection
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerifyModelItemsGroupedListWhenSingleModePreSelection.png (39.69% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, Nullable`1 retryTimeout, Int32 cropLeft, Int32 cropRight, Int32 cropTop, Int32 cropBottom, Double tolerance, Boolean includeTitleBar) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 296
   at Microsoft.Maui.TestCases.Tests.CollectionView_ItemsSourceFeatureTests.VerifyModelItemsGroupedListWhenSingleModePreSelection() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ItemsSourceFeatureTests.cs:line 748
   at System.RuntimeMethodHandle.InvokeMethod(ObjectHandleOnStack target, Voi
...
CollectionViewSelectedItemBackgroundShouldPersistAfterModalNavigation
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: CollectionViewSelectedItemBackgroundShouldPersistAfterModalNavigation.png (40.89% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreen
...
CollectionViewSelectedItemBackgroundLost
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: CollectionViewSelectedItemBackgroundLost.png (41.50% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 
...
VerifyMeasureAllItemsWithGroupedList
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerifyMeasureAllItemsWithGroupedList.png (35.60% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, Nullable`1 retryTimeout, Int32 cropLeft, Int32 cropRight, Int32 cropTop, Int32 cropBottom, Double tolerance, Boolean includeTitleBar) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 296
   at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyMeasureAllItemsWithGroupedList() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 105
   at System.RuntimeMethodHandle.InvokeMethod(ObjectHandleOnStack target, Void** arguments, ObjectHandleOnStack sig
...
PointerOverWithSelectedStateShouldWork
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: PointerOverWithSelectedStateShouldWork.png (42.74% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 re
...
GroupedCollectionViewItems
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: GroupedCollectionViewItems.png (40.23% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, Nu
...
CollectionviewFooterHideswhenDynamicallyAddorRemoveItems
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: CollectionviewFooterHideswhenDynamicallyAddorRemoveItems.png (43.47% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String n
...
CollectionViewMeasureFirstItem
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: CollectionViewMeasureFirstItem.png (26.31% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay
...
VerticalGridCollectionViewRTLColumnMirroringShouldWork
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerticalGridCollectionViewRTLColumnMirroringShouldWork.png (37.08% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String nam
...
VerifyCVBackgroundAndBackgroundColorWithVSM
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerifyCVBackgroundAndBackgroundColorWithVSM.png (40.82% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable
...
VerifyStringItemsObservableCollectionWhenMultipleModePreSelection
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerifyStringItemsObservableCollectionWhenMultipleModePreSelection.png (41.11% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, Nullable`1 retryTimeout, Int32 cropLeft, Int32 cropRight, Int32 cropTop, Int32 cropBottom, Double tolerance, Boolean includeTitleBar) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 296
   at Microsoft.Maui.TestCases.Tests.CollectionView_ItemsSourceFeatureTests.VerifyStringItemsObservableCollectionWhenMultipleModePreSelection() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ItemsSourceFeatureTests.cs:line 630
   at System.RuntimeMethodHandle.InvokeMethod(ObjectH
...
CollectionViewHeaderSizewithIsVisibleBinding
VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: CollectionViewHeaderSizewithIsVisibleBinding.png (32.51% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullabl
...

(+51 more — see TRX in artifact)

🔍 AI analysis of failures — PR-related vs unrelated

🔍 AI-generated triage (GitHub Copilot CLI) — a heuristic judgement of whether each deep UI test failure is connected to this PR's changes. Verify before relying on it.

Likely PR-related: one or more failures appear connected to this PR's changes.

  • ✗ PR-related — Mac Catalyst CollectionView visual snapshots (81 tests): every reported failure is in the CollectionView category on the catalyst run, and this PR changes Mac Catalyst-compiled CollectionView/ItemsView2 scroll behavior in MauiCollectionView and related iOS handler code, making broad CollectionView screenshot shifts plausibly caused by the new content-offset restore logic.
  • ✗ PR-related — Programmatic scroll and position-preservation scenarios (~many tests): representative failures like VerifyScrollToByItemWithEndPositionAndVerticalList_Carrot exercise ScrollTo/scroll positioning, which is the exact behavior the PR modifies for non-animated Mac Catalyst CollectionView scrolls.
  • ✗ PR-related — Header/footer, empty view, grouping, selection, and layout CollectionView screenshots (~many tests): although these are not all direct scroll-restore tests, they render through the same modified Mac Catalyst CollectionView handler and show large, consistent snapshot differences rather than an unrelated driver/session error.

Strongest signal: the failures are narrowly concentrated in CollectionView on the platform targeted by the PR, not spread across unrelated controls or infrastructure patterns.

📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)


📋 Pre-Flight — Context & Validation

Issue: #34271 - [Unstable] I9_Scrolling - Changing/Hovering the 'ScrollToPosition' would cause the item to scroll without clicking the button
PR: #36431 - [Mac] Fix Prevent CollectionView scroll position reset on Mac Catalyst after Picker interaction
Platforms Affected: Mac Catalyst
Files Changed: 6 implementation, 2 test

Key Findings

  • PR current fix uses Mac Catalyst-only KVO on MauiCollectionView.contentOffset after non-animated programmatic ScrollToItem, then asynchronously restores the saved index path.
  • Linked issue reproduces on Mac Catalyst by scrolling near the end of a CollectionView, opening/hovering/dismissing a Picker, and seeing UIKit silently clamp the CollectionView offset.
  • GitHub CLI was unauthenticated; PR/issue context was gathered through the public GitHub API and local branch/diff.
  • EstablishBrokenBaseline.ps1 could not run because unrelated .github files were already dirty; try-fix attempts were isolated by patching/restoring only PR target files and saving each candidate diff.

Code Review Summary

Verdict: NEEDS_CHANGES
Confidence: low
Errors: 3 | Warnings: 0 | Suggestions: 0

Key code review findings:

  • CarouselViewDelegator2.DraggingStarted bypasses the new ItemsViewDelegator2.DraggingStarted clear, so user swipes can leave stale restore state armed.
  • ✗ The async restore closure can execute after the target is cleared or replaced because it does not verify generation/current target before ScrollToItem.
  • ✗ Horizontal CarouselView paths are armed, but the detector only compares Y offset, so horizontal resets are missed.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #36431 Mac Catalyst KVO detects contentOffset drops and asynchronously calls ScrollToItem for the last programmatic target. ❌ Gate already failed before this phase MauiCollectionView.cs, Items2 handler/controller/delegator files, PublicAPI, UI test files Original PR; code review found stale-target, user-drag, and horizontal-axis risks.

🔬 Code Review — Deep Analysis

Code Review — PR #36431

Independent Assessment

What this changes: Adds Mac Catalyst-only contentOffset KVO restore logic for MauiCollectionView, then arms/clears it around non-animated ScrollToItem paths in Items2/CarouselView.
Inferred motivation: Work around UIKit silently clamping/resetting CollectionView scroll position after Picker/window transitions.

Reconciliation with PR Narrative

Author claims: Fixes Mac Catalyst CollectionView scroll reset after Picker dismissal using KVO-based restore.
Agreement/disagreement: The motivation matches the code. The approach is plausible, but current edge cases can restore stale targets or miss CarouselView/horizontal/user-scroll paths.

Prior Review Reconciliation

Prior ❌ Error Finding Source Status Evidence
CarouselView bypasses DraggingStarted clear MauiBot inline comments ❌ Unresolved ItemsViewDelegator2.DraggingStarted added, but CarouselViewDelegator2.DraggingStarted still overrides without base.
Async restore can run after target changed/cleared MauiBot inline comments ❌ Unresolved Queued restore closure still calls ScrollToItem without checking generation/current target.
Old restore armed during new ScrollToItem MauiBot inline comments ✅ Fixed Current code clears before scroll in ItemsViewHandler2.iOS.cs.
CarouselView controller scroll paths not armed MauiBot inline comments ✅ Fixed Current code arms in CarouselViewController2.cs.

Blast Radius Assessment

  • Runs for all instances: Mac Catalyst CollectionView/CarouselView handler paths using Items2.
  • Startup impact: no.
  • Static/shared state: no static state, but per-view state survives until explicitly cleared.
  • Handler/platform impact: yes, confidence max medium before CI cap.

CI Status

  • Required-check result: unavailable via gh because GitHub CLI is unauthenticated in this environment.
  • Classification: undetermined.
  • Action taken: confidence capped low; no LGTM.

Findings

❌ Error — CarouselView user drag does not clear pending restore

src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs

The new clear-on-drag hook is bypassed for CarouselView2 because CarouselViewDelegator2.DraggingStarted overrides this method and does not call base. Since this PR now arms restores from CarouselView paths, a user drag/swipe can leave stale restore state armed and later snap back to the old programmatic target.

❌ Error — Queued restore can execute after cancellation or a newer target

src/Controls/src/Core/Handlers/Items/iOS/MauiCollectionView.cs

The dispatched closure captures section/item/scrollPosition, but before calling ScrollToItem it only checks handle/window/dragging and index bounds. If user drag, layout/source change, or a newer ScrollTo clears/rearms the restore before the queued block runs, the stale closure can still scroll to the old target.

❌ Error — Horizontal CarouselView restores are armed but Y-only detection cannot see them

src/Controls/src/Core/Handlers/Items/iOS/MauiCollectionView.cs

The detector only compares ContentOffset.Y, while this PR arms restore from CarouselView paths that can be horizontal. A horizontal reset/clamp can leave Y == 0, so the reset is not detected and the feature silently fails for those armed paths.

Failure-Mode Probing

  • User drags CarouselView after programmatic scroll: pending restore remains armed because the override skips the base hook.
  • New ScrollTo(B) occurs while restore for A is queued: stale closure can still restore A.
  • Horizontal CarouselView reset: KVO fires, but Y comparison treats it as normal/no-op.

Verdict: NEEDS_CHANGES

Confidence: low due undetermined CI; findings confidence high.
Summary: The fix targets the right Mac Catalyst failure mode, but unresolved handler and async-race issues can override user intent or miss CarouselView scenarios.


🛠️ Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix Layout-pass visible-cell anchor restore in ItemsViewController2; no KVO. ❌ Failed 6 files Build fixed after one iteration, then actual UI test timed out waiting for target item amid external image load failures.
2 try-fix Cached previous CollectionViewContentSize to prevent transient estimated-size clamp. ⚠️ Rejected after pass 4 files Target test passed, but self-review found stale oversized content-size risk on legitimate same-count shrink.
3 try-fix Current viewport content-size floor based on visible layout attributes. ⚠️ Rejected after pass 4 files Target test passed, but self-review found legitimate shrink/removal can still leave blank content.
4 try-fix Mac Catalyst-only larger dynamic layout estimates (128f) for Items2 list/grid compositional layouts. ✅ Passed 4 files for independent test; production core is 1 file Target test passed; self-review clean.
PR PR #36431 KVO detects contentOffset drops and async-restores saved ScrollToItem target. ❌ Gate already failed 6 implementation + 2 test files Original PR; code review found stale async target, Carousel user-drag, and horizontal-axis risks.

Cross-Pollination

Model Round New Ideas? Details
gpt-5.5 / maui-expert-reviewer 1 Yes Candidate 1: layout-pass visible-cell anchor restore.
gpt-5.5 / maui-expert-reviewer 2 Yes Candidate 2: cached content-size stabilization after Candidate 1 failure.
gpt-5.5 / maui-expert-reviewer 3 Yes Candidate 3: non-cached viewport content-size floor after Candidate 2 self-review failure.
gpt-5.5 / maui-expert-reviewer 4 Yes Candidate 4: Mac Catalyst-only larger dynamic estimates after Candidate 3 self-review failure.

Exhausted: No — Candidate 4 passed the targeted regression and had a clean expert self-review.
Selected Fix: Candidate #4 — It avoids the PR's KVO observer/async stale-target/user-drag/horizontal-axis failure modes, avoids Candidate 2/3 stale content-size floors, requires no public API, and changes the estimated-size input that triggers the clamp rather than restoring after the fact.


📝 Recommended PR Title & Description

Assessment: ✏️ Recommend updating — the current title/description accurately describe the submitted KVO PR, but the winning candidate is the layout-estimate fix (try-fix-4), so the metadata would be stale if that candidate is used.

Recommended title

[MacCatalyst] CollectionView: Preserve scroll position after Picker interaction

Recommended description

### Root Cause

On Mac Catalyst, opening/dismissing a Picker or other window-state transitions can cause UIKit to recalculate `UICollectionView` bounds using compositional layout estimated item sizes before the realized item heights are available.

Items2 list/grid layouts currently use `NSCollectionLayoutDimension.CreateEstimated(30f)` for dynamic dimensions. For taller item templates, that estimate can make UIKit's transient `contentSize` smaller than the actual content size. When the CollectionView has been programmatically scrolled near the bottom, UIKit clamps `contentOffset` to the smaller estimated maximum (`max(0, estimatedContentSize - frameHeight)`), shifting the visible item upward after the Picker interaction.

### Description of Change

Increase the Items2 dynamic compositional layout estimate on Mac Catalyst from `30f` to `128f` through a platform-specific `EstimatedDynamicDimension` constant in `LayoutFactory2`.

This fixes the issue at the source of the clamp by giving UIKit a closer transient content-size estimate during Mac Catalyst layout recalculation, instead of observing `contentOffset` and restoring the position after UIKit has already moved it.

The estimate remains `30f` on non-Mac Catalyst platforms.

### Key Technical Details

- `LayoutFactory2` now uses `EstimatedDynamicDimension` for dynamic list/grid item and group dimensions.
- The change is Mac Catalyst-specific (`#if MACCATALYST`) and affects Items2 compositional layouts.
- No new public API is required.

### What NOT to Do (for future agents)

- Don't restore this with long-lived `contentOffset` KVO unless all user-scroll, stale-target, and handler lifecycle paths are guarded; that approach can fight legitimate user input and queued scroll requests.
- Don't cache or floor `CollectionViewContentSize` indefinitely; legitimate item shrink/removal can otherwise leave blank trailing scroll space.

### Issues Fixed

Fixes #34271

### Platforms Tested

- [x] Mac Catalyst

🏁 Report — Final Recommendation

Comparative Report — PR #36431

Candidates

Rank Candidate Result Assessment
1 try-fix-4 ✅ Passed targeted regression Best candidate. It changes the Mac Catalyst Items2 compositional layout estimate from 30f to 128f, reducing the underestimated content-size window that lets UIKit clamp the offset. It avoids KVO, public API changes, stale async restore races, and user-scroll misclassification.
2 try-fix-2 ✅ Passed targeted regression, then rejected by self-review The cached content-size floor passed the test, but it can preserve an oversized stale content size when item heights legitimately shrink without an item-count change, leaving blank trailing scroll space.
3 try-fix-3 ✅ Passed targeted regression, then rejected by self-review The viewport content-size floor passed the test, but visible attributes can still exist after legitimate shrink/removal, so it can also block valid UIKit clamping and show nonexistent/blank content.
4 pr-plus-reviewer Not validated after reviewer changes Safer than the raw PR if the reviewer feedback is applied, but it still relies on KVO and async restore in platform scroll plumbing. It would need fixes for CarouselView drag clearing, stale queued restores, horizontal-axis tracking, and an unprotected direct CarouselView loop-correction ScrollToItem path, then a fresh regression run.
5 pr ❌ Gate failed / code review found major issues Raw PR fix targets the right behavior but leaves stale restore/user intent risks, misses horizontal detection, and leaves an adjacent direct non-animated CarouselView loop-correction scroll unprotected. The gate also reported that the submitted regression test passed without the fix, so it did not prove the bug.
6 try-fix-1 ❌ Failed targeted regression Layout-pass anchor restore timed out waiting for the target item and had broader layout-pass blast radius. Failed candidates rank below passing candidates.

Winner

Winner: try-fix-4

try-fix-4 is the strongest candidate because it addresses the cause described in the PR itself: UIKit clamps against an underestimated compositional layout content size during Mac Catalyst window/Picker transitions. Raising the Mac Catalyst dynamic estimate is simpler and less invasive than installing a persistent KVO observer and asynchronously restoring scroll state. It passed the targeted regression while avoiding the stale-target, CarouselView drag-clearing, horizontal-axis, adjacent-scroll-call-site, and public API risks found in the PR/KVO approach.

Notes

  • The overall gate for the PR failed because the regression test passed without the fix; do not treat the PR's test as proof of correctness.
  • Passing candidates are ranked above candidates that failed regression tests, per requirement.
  • try-fix-4 still needs final product review for the chosen estimate value and smoke coverage across grouped/horizontal layouts, but it has the best correctness/risk tradeoff among the explored candidates.

🧭 Next Steps — alternative fix proposed (try-fix-4)

Automated review — alternative fix proposed

The expert-reviewer evaluation compared the PR fix against automatically generated candidates and selected try-fix-4 as the strongest fix.

Why: try-fix-4 wins because it passed the targeted Mac Catalyst regression and fixes the underestimated compositional-layout size input that causes UIKit to clamp the scroll offset. It avoids the raw PR's KVO observer, async stale-target restore, and user-scroll misclassification risks.

Please consider applying the candidate diff below (or use it as guidance). Once you push an update, this workflow will re-trigger and re-evaluate.

Candidate diff (try-fix-4)
diff --git a/src/Controls/src/Core/Handlers/Items2/iOS/LayoutFactory2.cs b/src/Controls/src/Core/Handlers/Items2/iOS/LayoutFactory2.cs
--- a/src/Controls/src/Core/Handlers/Items2/iOS/LayoutFactory2.cs
+++ b/src/Controls/src/Core/Handlers/Items2/iOS/LayoutFactory2.cs
@@
 internal static class LayoutFactory2
 {
+#if MACCATALYST
+	const float EstimatedDynamicDimension = 128f;
+#else
+	const float EstimatedDynamicDimension = 30f;
+#endif
+
@@
-			NSCollectionLayoutDimension.CreateEstimated(30f),
+			NSCollectionLayoutDimension.CreateEstimated(EstimatedDynamicDimension),
@@
-			NSCollectionLayoutDimension.CreateEstimated(30f),
+			NSCollectionLayoutDimension.CreateEstimated(EstimatedDynamicDimension),
@@
-			NSCollectionLayoutDimension.CreateEstimated(30f),
+			NSCollectionLayoutDimension.CreateEstimated(EstimatedDynamicDimension),
@@
-			NSCollectionLayoutDimension.CreateEstimated(30f),
+			NSCollectionLayoutDimension.CreateEstimated(EstimatedDynamicDimension),
@@
-			NSCollectionLayoutDimension.CreateEstimated(30f),
+			NSCollectionLayoutDimension.CreateEstimated(EstimatedDynamicDimension),
@@
-			NSCollectionLayoutDimension.CreateEstimated(30f),
+			NSCollectionLayoutDimension.CreateEstimated(EstimatedDynamicDimension),
@@
-			NSCollectionLayoutDimension.CreateEstimated(30f),
+			NSCollectionLayoutDimension.CreateEstimated(EstimatedDynamicDimension),
@@
-			NSCollectionLayoutDimension.CreateEstimated(30f),
+			NSCollectionLayoutDimension.CreateEstimated(EstimatedDynamicDimension),

@MauiBot MauiBot removed the s/agent-review-in-progress AI review is currently running for this PR label Jul 15, 2026
@Vignesh-SF3580

Copy link
Copy Markdown
Contributor Author

AI Review Summary

@Vignesh-SF3580 — new AI review results are available based on this last commit: 8e85bae. To request a fresh review after new comments or commits, comment /review rerun.

Gate Partial Confidence Low Platform Catalyst

🗂️ Review Sessions — click to expand

🚦 Gate — Test Before & After Fix

Gate Result: ❌ FAILED

Platform: CATALYST · Base: main · Merge base: 0395a53b

🩺 Test does not reproduce the bug — ran the same in both states (PASS without fix, PASS with fix). The repro test is not exercising the issue. Strengthen the test before reviewing the fix.

Test Without Fix (expect FAIL) With Fix (expect PASS)
🖥️ Issue34271 Issue34271 ❌ PASS — 449s ✅ PASS — 121s
🔴 Without fix — 🖥️ Issue34271: PASS ❌ · 449s

  Determining projects to restore...
  Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 627 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 5.19 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Core/maps/src/Maps.csproj (in 6.97 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 6.97 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj (in 6.32 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 6.97 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Xaml/Controls.Xaml.csproj (in 6.97 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/Foldable/src/Controls.Foldable.csproj (in 6.99 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/Maps/src/Controls.Maps.csproj (in 6.97 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj (in 7.15 sec).
  1 of 11 projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.dll
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Maps/Debug/net10.0-maccatalyst26.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.100-ci+azdo.14657503
  Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Controls.Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Controls.Maps.dll
  Controls.Xaml -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-maccatalyst26.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-maccatalyst26.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
  Controls.Foldable -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Controls.Foldable.dll
  Detected signing identity:
    Code Signing Key: "" (-)
    Provisioning Profile: "" () - no entitlements
    Bundle Id: com.microsoft.maui.uitests
    App Id: com.microsoft.maui.uitests
  Detected signing identity:
    Code Signing Key: "" (-)
    Provisioning Profile: "" () - no entitlements
    Bundle Id: com.microsoft.maui.uitests
    App Id: com.microsoft.maui.uitests
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Controls.TestCases.HostApp -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-maccatalyst/maccatalyst-x64/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.
  Detected signing identity:
    Code Signing Key: "" (-)
    Provisioning Profile: "" () - no entitlements
    Bundle Id: com.microsoft.maui.uitests
    App Id: com.microsoft.maui.uitests
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Controls.TestCases.HostApp -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-maccatalyst/maccatalyst-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.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:01:42.76
  Determining projects to restore...
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/VisualTestUtils/VisualTestUtils.csproj (in 811 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/tests/CustomAttributes/Controls.CustomAttributes.csproj (in 751 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.Core/UITest.Core.csproj (in 746 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.NUnit/UITest.NUnit.csproj (in 1.88 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.Appium/UITest.Appium.csproj (in 2.87 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.Analyzers/UITest.Analyzers.csproj (in 3.95 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/VisualTestUtils.MagickNet/VisualTestUtils.MagickNet.csproj (in 7 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.Mac.Tests/Controls.TestCases.Mac.Tests.csproj (in 7.04 sec).
  5 of 13 projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  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.100-ci+azdo.14657503
  Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  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.100-ci+azdo.14657503
  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.Mac.Tests -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.Mac.Tests/Debug/net10.0/Controls.TestCases.Mac.Tests.dll
Test run for /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.Mac.Tests/Debug/net10.0/Controls.TestCases.Mac.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.
[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.08]   Discovering: Controls.TestCases.Mac.Tests
[xUnit.net 00:00:00.20]   Discovered:  Controls.TestCases.Mac.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.Mac.Tests/Debug/net10.0/Controls.TestCases.Mac.Tests.dll
   NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 7/14/2026 4:26:15 PM FixtureSetup for Issue34271(Mac)
>>>>> 7/14/2026 4:26:16 PM CollectionViewScrollPositionPreservedAfterPickerDismiss Start
>>>>> 7/14/2026 4:26:29 PM CollectionViewScrollPositionPreservedAfterPickerDismiss Stop
NUnit Adapter 4.5.0.0: Test execution complete
  Passed CollectionViewScrollPositionPreservedAfterPickerDismiss [13 s]
Results File: /Users/cloudtest/vss/_work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue34271.trx

Test Run Successful.
Total tests: 1
     Passed: 1
 Total time: 35.7911 Seconds
>>> TRX_RESULT_FILE: /Users/cloudtest/vss/_work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue34271.trx

🟢 With fix — 🖥️ Issue34271: PASS ✅ · 121s

  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.dll
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  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-maccatalyst26.0/Microsoft.Maui.Maps.dll
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Microsoft.AspNetCore.Components.WebView.Maui -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-maccatalyst26.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
  Controls.Xaml -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Controls.Xaml.dll
  Controls.Foldable -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-maccatalyst26.0/Microsoft.Maui.Controls.Foldable.dll
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Controls.Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-maccatalyst26.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
  Detected signing identity:
    Code Signing Key: "" (-)
    Provisioning Profile: "" () - no entitlements
    Bundle Id: com.microsoft.maui.uitests
    App Id: com.microsoft.maui.uitests
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Controls.TestCases.HostApp -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-maccatalyst/maccatalyst-x64/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.
  Detected signing identity:
    Code Signing Key: "" (-)
    Provisioning Profile: "" () - no entitlements
    Bundle Id: com.microsoft.maui.uitests
    App Id: com.microsoft.maui.uitests
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  Controls.TestCases.HostApp -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-maccatalyst/maccatalyst-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.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:01:13.11
  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  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.100-ci+azdo.14657503
  Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.100-ci+azdo.14657503
  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.100-ci+azdo.14657503
  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
  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.Mac.Tests -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.Mac.Tests/Debug/net10.0/Controls.TestCases.Mac.Tests.dll
Test run for /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.Mac.Tests/Debug/net10.0/Controls.TestCases.Mac.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.
[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.Mac.Tests
[xUnit.net 00:00:00.18]   Discovered:  Controls.TestCases.Mac.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.Mac.Tests/Debug/net10.0/Controls.TestCases.Mac.Tests.dll
   NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 7/14/2026 4:32:40 PM FixtureSetup for Issue34271(Mac)
>>>>> 7/14/2026 4:32:40 PM CollectionViewScrollPositionPreservedAfterPickerDismiss Start
>>>>> 7/14/2026 4:32:54 PM CollectionViewScrollPositionPreservedAfterPickerDismiss Stop
  Passed CollectionViewScrollPositionPreservedAfterPickerDismiss [14 s]
NUnit Adapter 4.5.0.0: Test execution complete
Results File: /Users/cloudtest/vss/_work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue34271.trx

Test Run Successful.
Total tests: 1
     Passed: 1
 Total time: 24.0253 Seconds
>>> TRX_RESULT_FILE: /Users/cloudtest/vss/_work/1/s/CustomAgentLogsTmp/UITests/TestResults/Issue34271.trx

⚠️ Failure Details

  • Issue34271 PASSED without fix (should fail) — tests don't catch the bug

📁 Fix files reverted (6 files)

  • src/Controls/src/Core/Handlers/Items/iOS/MauiCollectionView.cs
  • src/Controls/src/Core/Handlers/Items2/CarouselViewHandler2.iOS.cs
  • src/Controls/src/Core/Handlers/Items2/ItemsViewHandler2.iOS.cs
  • src/Controls/src/Core/Handlers/Items2/iOS/CarouselViewController2.cs
  • src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs
  • src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt

📱 UI Tests — CollectionView

Detected UI test categories: CollectionView

Deep UI tests — 309 passed, 81 failed across 1 category on platform-pool agent (replaces in-process counts above).

🧪 UI Test Execution Results (deep, platform pool)

Category Tests Snapshot diffs
CollectionView 309/391 (81 ❌) 162 diff PNGs
CollectionView — 81 failed tests

CollectionViewHeaderBlankWhenLastItemRemoved

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: CollectionViewHeaderBlankWhenLastItemRemoved.png (43.53% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullabl
...

SelectedItemVisualIsCleared

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: SelectedItemVisualIsCleared.png (42.70% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, N
...

CheckEmptyViewMargin

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: CheckEmptyViewMargin.png (29.68% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, Nullable
...

ItemShouldbeScrolledbasedOnGroupHeader

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: ItemShouldbeScrolledbasedOnGroupHeader.png (40.15% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 re
...

ItemsWrapGridShouldUpdateBasedOnCollectionViewSize

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: ItemsWrapGridWithDefaultWidth.png (37.63% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at Microsoft.Maui.TestCases.Tests.Issues.Issue26083.ItemsWrapGridShouldUpdateBasedOnCollectionViewSize() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue26083.cs:line 28
   at System.RuntimeMethodHandle.InvokeMethod(ObjectHandleOnStack target, Void** arguments, ObjectHandleOnStack sig, BOOL isConstructor, ObjectHandleOnStack result)
   at System.RuntimeMethodHandle.InvokeMethod(ObjectHandleOnStack target, Void** arguments, ObjectHandleOnStack sig, BOOL isConstructor, ObjectHandleOnStack result)
   at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
   at System.Reflection.RuntimeMetho
...

VerifyScrollToByItemWithEndPositionAndVerticalList_Carrot

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerifyScrollToByItemWithEndPositionAndVerticalList_Carrot.png (42.70% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String 
...

VerifyScrollToByItemWithMakeVisiblePositionAndVerticalList_Carrot

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerifyScrollToByItemWithMakeVisiblePositionAndVerticalList_Carrot.png (42.70% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot
...

VerifyMeasureAllItemsWithObservableCollection

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerifyMeasureAllItemsWithObservableCollection.png (30.81% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, Nullable`1 retryTimeout, Int32 cropLeft, Int32 cropRight, Int32 cropTop, Int32 cropBottom, Double tolerance, Boolean includeTitleBar) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 296
   at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyMeasureAllItemsWithObservableCollection() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 53
   at System.RuntimeMethodHandle.InvokeMethod(ObjectHandleOnStack target, Void** arguments, Object
...

AccessibilityTraitsSetCorrectly

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: AccessibilityTraitsSetCorrectlyNone.png (24.87% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retry
...

RefreshShouldNotChangeSize

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: RefreshShouldNotChangeSize.png (22.73% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, Nu
...

VerifyFlowDirectionRTLAndMeasureAllItemsWithObservableCollection

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerifyFlowDirectionRTLAndMeasureAllItemsWithObservableCollection.png (30.57% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(
...

VerifyCollectionViewVisualState

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerifyCollectionViewVisualState.png (40.81% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDela
...

FooterShouldDisplayAtBottomOfEmptyView

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: FooterShouldDisplayAtBottomOfEmptyView.png (18.82% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 re
...

CVHorizontalLinearItemsLayoutItemSpacing

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: CVHorizontalLinearItemsLayoutItemSpacing.png (31.48% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 
...

VerifyScrollToByIndexWithMakeVisiblePositionAndVerticalList_Carrot

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerifyScrollToByIndexWithMakeVisiblePositionAndVerticalList_Carrot.png (42.70% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreensho
...

Issue18751Test

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: Issue18751Test.png (41.16% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, Nullable`1 retryTimeout, Int32 cropLeft, Int32 cropRight, Int32 cropTop, Int32 cropBottom, Double tolerance, Boolean includeTitleBar) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 296
   at Microsoft.Maui.TestCases.Tests.Issues.Issue18751.Issue18751Test() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue18751.cs:line 22
   at System.RuntimeMethodHandle.InvokeMethod(ObjectHandleOnStack target, Void** arguments, ObjectHandleOnStack sig, BOOL isConstructor, ObjectHandleOnStack result)
   at System.RuntimeMethodHandle.InvokeMethod(O
...

VerifyCustomSizedEmptyViewDisplaysCorrectly_WithRightToLeftFlowDirection

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerifyCustomSizedEmptyViewDisplaysCorrectly_WithRightToLeftFlowDirection.png (42.37% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScr
...

FlowdirectionShouldWorkForHeaderFooter

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: FlowdirectionShouldWorkForHeaderFooter.png (22.92% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 re
...

VerifyModelItemsGroupedListWhenSingleModePreSelection

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerifyModelItemsGroupedListWhenSingleModePreSelection.png (39.69% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, Nullable`1 retryTimeout, Int32 cropLeft, Int32 cropRight, Int32 cropTop, Int32 cropBottom, Double tolerance, Boolean includeTitleBar) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 296
   at Microsoft.Maui.TestCases.Tests.CollectionView_ItemsSourceFeatureTests.VerifyModelItemsGroupedListWhenSingleModePreSelection() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ItemsSourceFeatureTests.cs:line 748
   at System.RuntimeMethodHandle.InvokeMethod(ObjectHandleOnStack target, Voi
...

CollectionViewSelectedItemBackgroundShouldPersistAfterModalNavigation

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: CollectionViewSelectedItemBackgroundShouldPersistAfterModalNavigation.png (40.89% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreen
...

CollectionViewSelectedItemBackgroundLost

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: CollectionViewSelectedItemBackgroundLost.png (41.50% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 
...

VerifyMeasureAllItemsWithGroupedList

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerifyMeasureAllItemsWithGroupedList.png (35.60% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, Nullable`1 retryTimeout, Int32 cropLeft, Int32 cropRight, Int32 cropTop, Int32 cropBottom, Double tolerance, Boolean includeTitleBar) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 296
   at Microsoft.Maui.TestCases.Tests.CollectionView_ScrollingFeatureTests.VerifyMeasureAllItemsWithGroupedList() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ScrollingFeatureTests.cs:line 105
   at System.RuntimeMethodHandle.InvokeMethod(ObjectHandleOnStack target, Void** arguments, ObjectHandleOnStack sig
...

PointerOverWithSelectedStateShouldWork

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: PointerOverWithSelectedStateShouldWork.png (42.74% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 re
...

GroupedCollectionViewItems

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: GroupedCollectionViewItems.png (40.23% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, Nu
...

CollectionviewFooterHideswhenDynamicallyAddorRemoveItems

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: CollectionviewFooterHideswhenDynamicallyAddorRemoveItems.png (43.47% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String n
...

CollectionViewMeasureFirstItem

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: CollectionViewMeasureFirstItem.png (26.31% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay
...

VerticalGridCollectionViewRTLColumnMirroringShouldWork

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerticalGridCollectionViewRTLColumnMirroringShouldWork.png (37.08% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String nam
...

VerifyCVBackgroundAndBackgroundColorWithVSM

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerifyCVBackgroundAndBackgroundColorWithVSM.png (40.82% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable
...

VerifyStringItemsObservableCollectionWhenMultipleModePreSelection

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: VerifyStringItemsObservableCollectionWhenMultipleModePreSelection.png (41.11% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullable`1 retryDelay, Nullable`1 retryTimeout, Int32 cropLeft, Int32 cropRight, Int32 cropTop, Int32 cropBottom, Double tolerance, Boolean includeTitleBar) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 296
   at Microsoft.Maui.TestCases.Tests.CollectionView_ItemsSourceFeatureTests.VerifyStringItemsObservableCollectionWhenMultipleModePreSelection() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/CollectionView_ItemsSourceFeatureTests.cs:line 630
   at System.RuntimeMethodHandle.InvokeMethod(ObjectH
...

CollectionViewHeaderSizewithIsVisibleBinding

VisualTestUtils.VisualTestFailedException : 
Snapshot different than baseline: CollectionViewHeaderSizewithIsVisibleBinding.png (32.51% difference)
If the correct baseline has changed (this isn't a a bug), then update the baseline image.
See test attachment or download the build artifacts to get the new snapshot file.

More info: https://aka.ms/visual-test-workflow
at VisualTestUtils.VisualRegressionTester.Fail(String message) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 162
   at VisualTestUtils.VisualRegressionTester.VerifyMatchesSnapshot(String name, ImageSnapshot actualImage, String environmentName, ITestContext testContext) in /_/src/TestUtils/src/VisualTestUtils/VisualRegressionTester.cs:line 123
   at Microsoft.Maui.TestCases.Tests.UITest.<VerifyScreenshot>g__Verify|13_0(String name, <>c__DisplayClass13_0&) in /_/src/Controls/tests/TestCases.Shared.Tests/UITest.cs:line 477
   at Microsoft.Maui.TestCases.Tests.UITest.VerifyScreenshot(String name, Nullabl
...

(+51 more — see TRX in artifact)

🔍 AI analysis of failures — PR-related vs unrelated

🔍 AI-generated triage (GitHub Copilot CLI) — a heuristic judgement of whether each deep UI test failure is connected to this PR's changes. Verify before relying on it.

Likely PR-related: one or more failures appear connected to this PR's changes.

  • ✗ PR-related — Mac Catalyst CollectionView visual snapshots (81 tests): every reported failure is in the CollectionView category on the catalyst run, and this PR changes Mac Catalyst-compiled CollectionView/ItemsView2 scroll behavior in MauiCollectionView and related iOS handler code, making broad CollectionView screenshot shifts plausibly caused by the new content-offset restore logic.
  • ✗ PR-related — Programmatic scroll and position-preservation scenarios (~many tests): representative failures like VerifyScrollToByItemWithEndPositionAndVerticalList_Carrot exercise ScrollTo/scroll positioning, which is the exact behavior the PR modifies for non-animated Mac Catalyst CollectionView scrolls.
  • ✗ PR-related — Header/footer, empty view, grouping, selection, and layout CollectionView screenshots (~many tests): although these are not all direct scroll-restore tests, they render through the same modified Mac Catalyst CollectionView handler and show large, consistent snapshot differences rather than an unrelated driver/session error.

Strongest signal: the failures are narrowly concentrated in CollectionView on the platform targeted by the PR, not spread across unrelated controls or infrastructure patterns.

📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)

📋 Pre-Flight — Context & Validation

Issue: #34271 - [Unstable] I9_Scrolling - Changing/Hovering the 'ScrollToPosition' would cause the item to scroll without clicking the button PR: #36431 - [Mac] Fix Prevent CollectionView scroll position reset on Mac Catalyst after Picker interaction Platforms Affected: Mac Catalyst Files Changed: 6 implementation, 2 test

Key Findings

  • PR current fix uses Mac Catalyst-only KVO on MauiCollectionView.contentOffset after non-animated programmatic ScrollToItem, then asynchronously restores the saved index path.
  • Linked issue reproduces on Mac Catalyst by scrolling near the end of a CollectionView, opening/hovering/dismissing a Picker, and seeing UIKit silently clamp the CollectionView offset.
  • GitHub CLI was unauthenticated; PR/issue context was gathered through the public GitHub API and local branch/diff.
  • EstablishBrokenBaseline.ps1 could not run because unrelated .github files were already dirty; try-fix attempts were isolated by patching/restoring only PR target files and saving each candidate diff.

Code Review Summary

Verdict: NEEDS_CHANGES Confidence: low Errors: 3 | Warnings: 0 | Suggestions: 0

Key code review findings:

  • CarouselViewDelegator2.DraggingStarted bypasses the new ItemsViewDelegator2.DraggingStarted clear, so user swipes can leave stale restore state armed.
  • ✗ The async restore closure can execute after the target is cleared or replaced because it does not verify generation/current target before ScrollToItem.
  • ✗ Horizontal CarouselView paths are armed, but the detector only compares Y offset, so horizontal resets are missed.

Fix Candidates

Source Approach Test Result Files Changed Notes

PR PR #36431 Mac Catalyst KVO detects contentOffset drops and asynchronously calls ScrollToItem for the last programmatic target. ❌ Gate already failed before this phase MauiCollectionView.cs, Items2 handler/controller/delegator files, PublicAPI, UI test files Original PR; code review found stale-target, user-drag, and horizontal-axis risks.
🔬 Code Review — Deep Analysis

Code Review — PR #36431

Independent Assessment

What this changes: Adds Mac Catalyst-only contentOffset KVO restore logic for MauiCollectionView, then arms/clears it around non-animated ScrollToItem paths in Items2/CarouselView. Inferred motivation: Work around UIKit silently clamping/resetting CollectionView scroll position after Picker/window transitions.

Reconciliation with PR Narrative

Author claims: Fixes Mac Catalyst CollectionView scroll reset after Picker dismissal using KVO-based restore. Agreement/disagreement: The motivation matches the code. The approach is plausible, but current edge cases can restore stale targets or miss CarouselView/horizontal/user-scroll paths.

Prior Review Reconciliation

Prior ❌ Error Finding Source Status Evidence
CarouselView bypasses DraggingStarted clear MauiBot inline comments ❌ Unresolved ItemsViewDelegator2.DraggingStarted added, but CarouselViewDelegator2.DraggingStarted still overrides without base.
Async restore can run after target changed/cleared MauiBot inline comments ❌ Unresolved Queued restore closure still calls ScrollToItem without checking generation/current target.
Old restore armed during new ScrollToItem MauiBot inline comments ✅ Fixed Current code clears before scroll in ItemsViewHandler2.iOS.cs.
CarouselView controller scroll paths not armed MauiBot inline comments ✅ Fixed Current code arms in CarouselViewController2.cs.

Blast Radius Assessment

  • Runs for all instances: Mac Catalyst CollectionView/CarouselView handler paths using Items2.
  • Startup impact: no.
  • Static/shared state: no static state, but per-view state survives until explicitly cleared.
  • Handler/platform impact: yes, confidence max medium before CI cap.

CI Status

  • Required-check result: unavailable via gh because GitHub CLI is unauthenticated in this environment.
  • Classification: undetermined.
  • Action taken: confidence capped low; no LGTM.

Findings

❌ Error — CarouselView user drag does not clear pending restore

src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs

The new clear-on-drag hook is bypassed for CarouselView2 because CarouselViewDelegator2.DraggingStarted overrides this method and does not call base. Since this PR now arms restores from CarouselView paths, a user drag/swipe can leave stale restore state armed and later snap back to the old programmatic target.

❌ Error — Queued restore can execute after cancellation or a newer target

src/Controls/src/Core/Handlers/Items/iOS/MauiCollectionView.cs

The dispatched closure captures section/item/scrollPosition, but before calling ScrollToItem it only checks handle/window/dragging and index bounds. If user drag, layout/source change, or a newer ScrollTo clears/rearms the restore before the queued block runs, the stale closure can still scroll to the old target.

❌ Error — Horizontal CarouselView restores are armed but Y-only detection cannot see them

src/Controls/src/Core/Handlers/Items/iOS/MauiCollectionView.cs

The detector only compares ContentOffset.Y, while this PR arms restore from CarouselView paths that can be horizontal. A horizontal reset/clamp can leave Y == 0, so the reset is not detected and the feature silently fails for those armed paths.

Failure-Mode Probing

  • User drags CarouselView after programmatic scroll: pending restore remains armed because the override skips the base hook.
  • New ScrollTo(B) occurs while restore for A is queued: stale closure can still restore A.
  • Horizontal CarouselView reset: KVO fires, but Y comparison treats it as normal/no-op.

Verdict: NEEDS_CHANGES

Confidence: low due undetermined CI; findings confidence high. Summary: The fix targets the right Mac Catalyst failure mode, but unresolved handler and async-race issues can override user intent or miss CarouselView scenarios.

🛠️ Fix — Analysis & Comparison

Fix Candidates

Source Approach Test Result Files Changed Notes

1 try-fix Layout-pass visible-cell anchor restore in ItemsViewController2; no KVO. ❌ Failed 6 files Build fixed after one iteration, then actual UI test timed out waiting for target item amid external image load failures.
2 try-fix Cached previous CollectionViewContentSize to prevent transient estimated-size clamp. ⚠️ Rejected after pass 4 files Target test passed, but self-review found stale oversized content-size risk on legitimate same-count shrink.
3 try-fix Current viewport content-size floor based on visible layout attributes. ⚠️ Rejected after pass 4 files Target test passed, but self-review found legitimate shrink/removal can still leave blank content.
4 try-fix Mac Catalyst-only larger dynamic layout estimates (128f) for Items2 list/grid compositional layouts. ✅ Passed 4 files for independent test; production core is 1 file Target test passed; self-review clean.
PR PR #36431 KVO detects contentOffset drops and async-restores saved ScrollToItem target. ❌ Gate already failed 6 implementation + 2 test files Original PR; code review found stale async target, Carousel user-drag, and horizontal-axis risks.

Cross-Pollination

Model Round New Ideas? Details
gpt-5.5 / maui-expert-reviewer 1 Yes Candidate 1: layout-pass visible-cell anchor restore.
gpt-5.5 / maui-expert-reviewer 2 Yes Candidate 2: cached content-size stabilization after Candidate 1 failure.
gpt-5.5 / maui-expert-reviewer 3 Yes Candidate 3: non-cached viewport content-size floor after Candidate 2 self-review failure.
gpt-5.5 / maui-expert-reviewer 4 Yes Candidate 4: Mac Catalyst-only larger dynamic estimates after Candidate 3 self-review failure.
Exhausted: No — Candidate 4 passed the targeted regression and had a clean expert self-review. Selected Fix: Candidate #4 — It avoids the PR's KVO observer/async stale-target/user-drag/horizontal-axis failure modes, avoids Candidate 2/3 stale content-size floors, requires no public API, and changes the estimated-size input that triggers the clamp rather than restoring after the fact.

📝 Recommended PR Title & Description

Assessment: ✏️ Recommend updating — the current title/description accurately describe the submitted KVO PR, but the winning candidate is the layout-estimate fix (try-fix-4), so the metadata would be stale if that candidate is used.

Recommended title

[MacCatalyst] CollectionView: Preserve scroll position after Picker interaction

Recommended description

### Root Cause

On Mac Catalyst, opening/dismissing a Picker or other window-state transitions can cause UIKit to recalculate `UICollectionView` bounds using compositional layout estimated item sizes before the realized item heights are available.

Items2 list/grid layouts currently use `NSCollectionLayoutDimension.CreateEstimated(30f)` for dynamic dimensions. For taller item templates, that estimate can make UIKit's transient `contentSize` smaller than the actual content size. When the CollectionView has been programmatically scrolled near the bottom, UIKit clamps `contentOffset` to the smaller estimated maximum (`max(0, estimatedContentSize - frameHeight)`), shifting the visible item upward after the Picker interaction.

### Description of Change

Increase the Items2 dynamic compositional layout estimate on Mac Catalyst from `30f` to `128f` through a platform-specific `EstimatedDynamicDimension` constant in `LayoutFactory2`.

This fixes the issue at the source of the clamp by giving UIKit a closer transient content-size estimate during Mac Catalyst layout recalculation, instead of observing `contentOffset` and restoring the position after UIKit has already moved it.

The estimate remains `30f` on non-Mac Catalyst platforms.

### Key Technical Details

- `LayoutFactory2` now uses `EstimatedDynamicDimension` for dynamic list/grid item and group dimensions.
- The change is Mac Catalyst-specific (`#if MACCATALYST`) and affects Items2 compositional layouts.
- No new public API is required.

### What NOT to Do (for future agents)

- Don't restore this with long-lived `contentOffset` KVO unless all user-scroll, stale-target, and handler lifecycle paths are guarded; that approach can fight legitimate user input and queued scroll requests.
- Don't cache or floor `CollectionViewContentSize` indefinitely; legitimate item shrink/removal can otherwise leave blank trailing scroll space.

### Issues Fixed

Fixes #34271

### Platforms Tested

- [x] Mac Catalyst

🏁 Report — Final Recommendation

Comparative Report — PR #36431

Candidates

Rank Candidate Result Assessment
1 try-fix-4 ✅ Passed targeted regression Best candidate. It changes the Mac Catalyst Items2 compositional layout estimate from 30f to 128f, reducing the underestimated content-size window that lets UIKit clamp the offset. It avoids KVO, public API changes, stale async restore races, and user-scroll misclassification.
2 try-fix-2 ✅ Passed targeted regression, then rejected by self-review The cached content-size floor passed the test, but it can preserve an oversized stale content size when item heights legitimately shrink without an item-count change, leaving blank trailing scroll space.
3 try-fix-3 ✅ Passed targeted regression, then rejected by self-review The viewport content-size floor passed the test, but visible attributes can still exist after legitimate shrink/removal, so it can also block valid UIKit clamping and show nonexistent/blank content.
4 pr-plus-reviewer Not validated after reviewer changes Safer than the raw PR if the reviewer feedback is applied, but it still relies on KVO and async restore in platform scroll plumbing. It would need fixes for CarouselView drag clearing, stale queued restores, horizontal-axis tracking, and an unprotected direct CarouselView loop-correction ScrollToItem path, then a fresh regression run.
5 pr ❌ Gate failed / code review found major issues Raw PR fix targets the right behavior but leaves stale restore/user intent risks, misses horizontal detection, and leaves an adjacent direct non-animated CarouselView loop-correction scroll unprotected. The gate also reported that the submitted regression test passed without the fix, so it did not prove the bug.
6 try-fix-1 ❌ Failed targeted regression Layout-pass anchor restore timed out waiting for the target item and had broader layout-pass blast radius. Failed candidates rank below passing candidates.

Winner

Winner: try-fix-4

try-fix-4 is the strongest candidate because it addresses the cause described in the PR itself: UIKit clamps against an underestimated compositional layout content size during Mac Catalyst window/Picker transitions. Raising the Mac Catalyst dynamic estimate is simpler and less invasive than installing a persistent KVO observer and asynchronously restoring scroll state. It passed the targeted regression while avoiding the stale-target, CarouselView drag-clearing, horizontal-axis, adjacent-scroll-call-site, and public API risks found in the PR/KVO approach.

Notes

  • The overall gate for the PR failed because the regression test passed without the fix; do not treat the PR's test as proof of correctness.
  • Passing candidates are ranked above candidates that failed regression tests, per requirement.
  • try-fix-4 still needs final product review for the chosen estimate value and smoke coverage across grouped/horizontal layouts, but it has the best correctness/risk tradeoff among the explored candidates.

🧭 Next Steps — alternative fix proposed (try-fix-4)

  • CarouselView drag not clearing the pending restore — This is a known issue. A previous attempt to call base.DraggingStarted(scrollView) caused four existing iOS CarouselView2 gesture-handling tests (IsSwipeEnabled*, MandatorySingleSnap, and BounceTest) to fail due to native UIKit delegate dispatch behavior. The current implementation remains unchanged, and a safer Scrolled-based approach is being evaluated separately.
  • Queued restore stale-target race — Confirmed as a real but narrow race condition that requires two ScrollTo calls within a very small timing window while the item count remains unchanged. A generation/version token was considered but intentionally not added to keep the fix minimal. It is not considered a blocking issue.
  • Horizontal CarouselView Y-axis-only detection — Confirmed. A dual-axis (X/Y) implementation was prototyped and build-verified, but the current changes intentionally remain scoped to Y-axis tracking. Support for horizontal CollectionView/CarouselView is outside the scope of this change.

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you please check if test failures are related?

@Vignesh-SF3580

Copy link
Copy Markdown
Contributor Author

Could you please check if test failures are related?

@kubaflo I verified the failed UI tests on iOS and macOS, and they all pass locally. The remaining failures are on Android and Windows, while the changes in this PR are specific to macOS.

@kubaflo
kubaflo changed the base branch from main to inflight/current July 16, 2026 11:48

@kubaflo kubaflo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can you please resolve conflicts?

@Vignesh-SF3580

Copy link
Copy Markdown
Contributor Author

Can you please resolve conflicts?

@kubaflo I have resolved the conflicts.

@kubaflo
kubaflo merged commit 8fb7722 into dotnet:inflight/current Jul 17, 2026
4 of 36 checks passed
@github-actions github-actions Bot added this to the .NET 10 SR10 milestone Jul 17, 2026
kubaflo pushed a commit that referenced this pull request Jul 22, 2026
…t after Picker interaction (#36431)

> [!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 if this change resolves your issue.
Thank you!

**Note:** This PR was created because the previous PR
(#34356) was closed.

### Root Cause

On Mac Catalyst, certain window state changes (opening/dismissing a
Picker, minimizing/restoring, or resizing the window) cause UIKit to
silently adjust the `UICollectionView` `contentOffset` when the view is
programmatically scrolled near or to the last item.

During these transitions, UIKit recalculates scroll bounds using
estimated item sizes
(`NSCollectionLayoutDimension.CreateEstimated(30f)`). Because the
estimated `contentSize` is smaller than the actual `contentSize`, UIKit
calculates a lower maximum valid `contentOffset` and silently clamps the
current offset down to that value. The scroll position does not always
jump to the very top — it shifts to whatever UIKit calculates as the
maximum valid offset: `max(0, estimatedContentSize - frameHeight)`. No
delegate, layout, or view controller callbacks fire — the change happens
entirely inside UIKit's property mutation machinery.

**Why items near the bottom are affected:** Items near the bottom
require a larger offset than UIKit's estimated bounds permit.
Intermediate items sit within the estimated range and are unaffected.

**Why only Mac Catalyst:** The same window transitions on iOS do not
trigger this recalculation behavior.

**Why conventional fixes fail:** Standard callbacks (`LayoutSubviews`,
`ViewDidLayoutSubviews`, `Scrolled`) are not triggered because this is
not a layout pass or a user scroll. KVO on `contentOffset` is the only
mechanism that fires synchronously at the moment UIKit mutates the
property.

### Description of Change

**KVO-Based Scroll Restore**

The fix uses `NSObject.AddObserver("contentOffset", ...)` (Key-Value
Observing) to detect the silent reset and restore the scroll position.

**Flow:**
1. After a **non-animated** `ScrollTo`,
`SetPendingScrollRestore(section, item, position)` is called, storing
the target index path as plain `int` fields and enabling tracking.
2. KVO `OnContentOffsetChanged` monitors every `contentOffset` change:
- While the new Y is within 10px of the last known Y: treat as normal
movement, update reference.
- If Y drops more than 10px below the last known Y: **silent reset
detected** → async-dispatches `ScrollToItem` to restore position.
3. `DraggingStarted` clears the restore target when the user manually
scrolls (respects user intent).
4. KVO lifecycle is managed in `MovedToWindow` — started when view
attaches, stopped when it detaches.

**Why relative threshold (not `Y < 1.0`):** UIKit does not always reset
to Y=0. It clamps to `max(0, estimatedContentSize - frameHeight)`, which
can be a non-zero value. A fixed `Y < 1.0` check would miss non-zero
resets entirely. Comparing against the last known Y detects any sudden
downward shift regardless of the absolute value.

**Why plain int storage (not NSIndexPath):** `NSIndexPath` is created
inside a `using` block in `ScrollToRequested` and is disposed before the
KVO callback fires. Storing `section` and `item` as `int` fields avoids
this lifetime issue.

**Why `ScrollToItem` instead of `SetContentOffset`:** `ScrollToItem`
recalculates the pixel offset using UIKit's current layout (including
actual item heights), yielding the exact visual position.
`SetContentOffset` uses a raw pixel value that becomes stale after
layout changes.

### Issues Fixed
Fixes #34271

**Files Changed**

| File | Change | 
|------|--------|
| `src/Controls/src/Core/Handlers/Items/iOS/MauiCollectionView.cs` | KVO
observer infrastructure, scroll restore state,
`SetPendingScrollRestore`, `ClearPendingScrollRestore`, `MovedToWindow`
lifecycle |
| `src/Controls/src/Core/Handlers/Items2/ItemsViewHandler2.iOS.cs` |
Call `SetPendingScrollRestore` after non-animated `ScrollToItem`;
capture `section`/`item` as ints inside `using` block |
| `src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs` |
`DraggingStarted` override to clear restore target on user drag |
|
`src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt`
| New public override: `ItemsViewDelegator2.DraggingStarted` |

**Total**: fix spans 4 production files

### Screenshots

| Before Issue Fix | After Issue Fix |
|----------|----------|
| <video width="300" height="600"
src="https://github.com/user-attachments/assets/ab398886-d90c-43d1-bd88-270a8e3925c1">
| <video width="300" height="600"
src="https://github.com/user-attachments/assets/e0b01b70-4959-406b-b94f-afd5601fa7d0">)
|
kubaflo pushed a commit that referenced this pull request Jul 28, 2026
…t after Picker interaction (#36431)

> [!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 if this change resolves your issue.
Thank you!

**Note:** This PR was created because the previous PR
(#34356) was closed.

### Root Cause

On Mac Catalyst, certain window state changes (opening/dismissing a
Picker, minimizing/restoring, or resizing the window) cause UIKit to
silently adjust the `UICollectionView` `contentOffset` when the view is
programmatically scrolled near or to the last item.

During these transitions, UIKit recalculates scroll bounds using
estimated item sizes
(`NSCollectionLayoutDimension.CreateEstimated(30f)`). Because the
estimated `contentSize` is smaller than the actual `contentSize`, UIKit
calculates a lower maximum valid `contentOffset` and silently clamps the
current offset down to that value. The scroll position does not always
jump to the very top — it shifts to whatever UIKit calculates as the
maximum valid offset: `max(0, estimatedContentSize - frameHeight)`. No
delegate, layout, or view controller callbacks fire — the change happens
entirely inside UIKit's property mutation machinery.

**Why items near the bottom are affected:** Items near the bottom
require a larger offset than UIKit's estimated bounds permit.
Intermediate items sit within the estimated range and are unaffected.

**Why only Mac Catalyst:** The same window transitions on iOS do not
trigger this recalculation behavior.

**Why conventional fixes fail:** Standard callbacks (`LayoutSubviews`,
`ViewDidLayoutSubviews`, `Scrolled`) are not triggered because this is
not a layout pass or a user scroll. KVO on `contentOffset` is the only
mechanism that fires synchronously at the moment UIKit mutates the
property.

### Description of Change

**KVO-Based Scroll Restore**

The fix uses `NSObject.AddObserver("contentOffset", ...)` (Key-Value
Observing) to detect the silent reset and restore the scroll position.

**Flow:**
1. After a **non-animated** `ScrollTo`,
`SetPendingScrollRestore(section, item, position)` is called, storing
the target index path as plain `int` fields and enabling tracking.
2. KVO `OnContentOffsetChanged` monitors every `contentOffset` change:
- While the new Y is within 10px of the last known Y: treat as normal
movement, update reference.
- If Y drops more than 10px below the last known Y: **silent reset
detected** → async-dispatches `ScrollToItem` to restore position.
3. `DraggingStarted` clears the restore target when the user manually
scrolls (respects user intent).
4. KVO lifecycle is managed in `MovedToWindow` — started when view
attaches, stopped when it detaches.

**Why relative threshold (not `Y < 1.0`):** UIKit does not always reset
to Y=0. It clamps to `max(0, estimatedContentSize - frameHeight)`, which
can be a non-zero value. A fixed `Y < 1.0` check would miss non-zero
resets entirely. Comparing against the last known Y detects any sudden
downward shift regardless of the absolute value.

**Why plain int storage (not NSIndexPath):** `NSIndexPath` is created
inside a `using` block in `ScrollToRequested` and is disposed before the
KVO callback fires. Storing `section` and `item` as `int` fields avoids
this lifetime issue.

**Why `ScrollToItem` instead of `SetContentOffset`:** `ScrollToItem`
recalculates the pixel offset using UIKit's current layout (including
actual item heights), yielding the exact visual position.
`SetContentOffset` uses a raw pixel value that becomes stale after
layout changes.

### Issues Fixed
Fixes #34271

**Files Changed**

| File | Change | 
|------|--------|
| `src/Controls/src/Core/Handlers/Items/iOS/MauiCollectionView.cs` | KVO
observer infrastructure, scroll restore state,
`SetPendingScrollRestore`, `ClearPendingScrollRestore`, `MovedToWindow`
lifecycle |
| `src/Controls/src/Core/Handlers/Items2/ItemsViewHandler2.iOS.cs` |
Call `SetPendingScrollRestore` after non-animated `ScrollToItem`;
capture `section`/`item` as ints inside `using` block |
| `src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs` |
`DraggingStarted` override to clear restore target on user drag |
|
`src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt`
| New public override: `ItemsViewDelegator2.DraggingStarted` |

**Total**: fix spans 4 production files

### Screenshots

| Before Issue Fix | After Issue Fix |
|----------|----------|
| <video width="300" height="600"
src="https://github.com/user-attachments/assets/ab398886-d90c-43d1-bd88-270a8e3925c1">
| <video width="300" height="600"
src="https://github.com/user-attachments/assets/e0b01b70-4959-406b-b94f-afd5601fa7d0">)
|
kubaflo pushed a commit that referenced this pull request Jul 29, 2026
…t after Picker interaction (#36431)

> [!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 if this change resolves your issue.
Thank you!

**Note:** This PR was created because the previous PR
(#34356) was closed.

### Root Cause

On Mac Catalyst, certain window state changes (opening/dismissing a
Picker, minimizing/restoring, or resizing the window) cause UIKit to
silently adjust the `UICollectionView` `contentOffset` when the view is
programmatically scrolled near or to the last item.

During these transitions, UIKit recalculates scroll bounds using
estimated item sizes
(`NSCollectionLayoutDimension.CreateEstimated(30f)`). Because the
estimated `contentSize` is smaller than the actual `contentSize`, UIKit
calculates a lower maximum valid `contentOffset` and silently clamps the
current offset down to that value. The scroll position does not always
jump to the very top — it shifts to whatever UIKit calculates as the
maximum valid offset: `max(0, estimatedContentSize - frameHeight)`. No
delegate, layout, or view controller callbacks fire — the change happens
entirely inside UIKit's property mutation machinery.

**Why items near the bottom are affected:** Items near the bottom
require a larger offset than UIKit's estimated bounds permit.
Intermediate items sit within the estimated range and are unaffected.

**Why only Mac Catalyst:** The same window transitions on iOS do not
trigger this recalculation behavior.

**Why conventional fixes fail:** Standard callbacks (`LayoutSubviews`,
`ViewDidLayoutSubviews`, `Scrolled`) are not triggered because this is
not a layout pass or a user scroll. KVO on `contentOffset` is the only
mechanism that fires synchronously at the moment UIKit mutates the
property.

### Description of Change

**KVO-Based Scroll Restore**

The fix uses `NSObject.AddObserver("contentOffset", ...)` (Key-Value
Observing) to detect the silent reset and restore the scroll position.

**Flow:**
1. After a **non-animated** `ScrollTo`,
`SetPendingScrollRestore(section, item, position)` is called, storing
the target index path as plain `int` fields and enabling tracking.
2. KVO `OnContentOffsetChanged` monitors every `contentOffset` change:
- While the new Y is within 10px of the last known Y: treat as normal
movement, update reference.
- If Y drops more than 10px below the last known Y: **silent reset
detected** → async-dispatches `ScrollToItem` to restore position.
3. `DraggingStarted` clears the restore target when the user manually
scrolls (respects user intent).
4. KVO lifecycle is managed in `MovedToWindow` — started when view
attaches, stopped when it detaches.

**Why relative threshold (not `Y < 1.0`):** UIKit does not always reset
to Y=0. It clamps to `max(0, estimatedContentSize - frameHeight)`, which
can be a non-zero value. A fixed `Y < 1.0` check would miss non-zero
resets entirely. Comparing against the last known Y detects any sudden
downward shift regardless of the absolute value.

**Why plain int storage (not NSIndexPath):** `NSIndexPath` is created
inside a `using` block in `ScrollToRequested` and is disposed before the
KVO callback fires. Storing `section` and `item` as `int` fields avoids
this lifetime issue.

**Why `ScrollToItem` instead of `SetContentOffset`:** `ScrollToItem`
recalculates the pixel offset using UIKit's current layout (including
actual item heights), yielding the exact visual position.
`SetContentOffset` uses a raw pixel value that becomes stale after
layout changes.

### Issues Fixed
Fixes #34271

**Files Changed**

| File | Change | 
|------|--------|
| `src/Controls/src/Core/Handlers/Items/iOS/MauiCollectionView.cs` | KVO
observer infrastructure, scroll restore state,
`SetPendingScrollRestore`, `ClearPendingScrollRestore`, `MovedToWindow`
lifecycle |
| `src/Controls/src/Core/Handlers/Items2/ItemsViewHandler2.iOS.cs` |
Call `SetPendingScrollRestore` after non-animated `ScrollToItem`;
capture `section`/`item` as ints inside `using` block |
| `src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs` |
`DraggingStarted` override to clear restore target on user drag |
|
`src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt`
| New public override: `ItemsViewDelegator2.DraggingStarted` |

**Total**: fix spans 4 production files

### Screenshots

| Before Issue Fix | After Issue Fix |
|----------|----------|
| <video width="300" height="600"
src="https://github.com/user-attachments/assets/ab398886-d90c-43d1-bd88-270a8e3925c1">
| <video width="300" height="600"
src="https://github.com/user-attachments/assets/e0b01b70-4959-406b-b94f-afd5601fa7d0">)
|
kubaflo pushed a commit that referenced this pull request Aug 7, 2026
…t after Picker interaction (#36431)

> [!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 if this change resolves your issue.
Thank you!

**Note:** This PR was created because the previous PR
(#34356) was closed.

### Root Cause

On Mac Catalyst, certain window state changes (opening/dismissing a
Picker, minimizing/restoring, or resizing the window) cause UIKit to
silently adjust the `UICollectionView` `contentOffset` when the view is
programmatically scrolled near or to the last item.

During these transitions, UIKit recalculates scroll bounds using
estimated item sizes
(`NSCollectionLayoutDimension.CreateEstimated(30f)`). Because the
estimated `contentSize` is smaller than the actual `contentSize`, UIKit
calculates a lower maximum valid `contentOffset` and silently clamps the
current offset down to that value. The scroll position does not always
jump to the very top — it shifts to whatever UIKit calculates as the
maximum valid offset: `max(0, estimatedContentSize - frameHeight)`. No
delegate, layout, or view controller callbacks fire — the change happens
entirely inside UIKit's property mutation machinery.

**Why items near the bottom are affected:** Items near the bottom
require a larger offset than UIKit's estimated bounds permit.
Intermediate items sit within the estimated range and are unaffected.

**Why only Mac Catalyst:** The same window transitions on iOS do not
trigger this recalculation behavior.

**Why conventional fixes fail:** Standard callbacks (`LayoutSubviews`,
`ViewDidLayoutSubviews`, `Scrolled`) are not triggered because this is
not a layout pass or a user scroll. KVO on `contentOffset` is the only
mechanism that fires synchronously at the moment UIKit mutates the
property.

### Description of Change

**KVO-Based Scroll Restore**

The fix uses `NSObject.AddObserver("contentOffset", ...)` (Key-Value
Observing) to detect the silent reset and restore the scroll position.

**Flow:**
1. After a **non-animated** `ScrollTo`,
`SetPendingScrollRestore(section, item, position)` is called, storing
the target index path as plain `int` fields and enabling tracking.
2. KVO `OnContentOffsetChanged` monitors every `contentOffset` change:
- While the new Y is within 10px of the last known Y: treat as normal
movement, update reference.
- If Y drops more than 10px below the last known Y: **silent reset
detected** → async-dispatches `ScrollToItem` to restore position.
3. `DraggingStarted` clears the restore target when the user manually
scrolls (respects user intent).
4. KVO lifecycle is managed in `MovedToWindow` — started when view
attaches, stopped when it detaches.

**Why relative threshold (not `Y < 1.0`):** UIKit does not always reset
to Y=0. It clamps to `max(0, estimatedContentSize - frameHeight)`, which
can be a non-zero value. A fixed `Y < 1.0` check would miss non-zero
resets entirely. Comparing against the last known Y detects any sudden
downward shift regardless of the absolute value.

**Why plain int storage (not NSIndexPath):** `NSIndexPath` is created
inside a `using` block in `ScrollToRequested` and is disposed before the
KVO callback fires. Storing `section` and `item` as `int` fields avoids
this lifetime issue.

**Why `ScrollToItem` instead of `SetContentOffset`:** `ScrollToItem`
recalculates the pixel offset using UIKit's current layout (including
actual item heights), yielding the exact visual position.
`SetContentOffset` uses a raw pixel value that becomes stale after
layout changes.

### Issues Fixed
Fixes #34271

**Files Changed**

| File | Change | 
|------|--------|
| `src/Controls/src/Core/Handlers/Items/iOS/MauiCollectionView.cs` | KVO
observer infrastructure, scroll restore state,
`SetPendingScrollRestore`, `ClearPendingScrollRestore`, `MovedToWindow`
lifecycle |
| `src/Controls/src/Core/Handlers/Items2/ItemsViewHandler2.iOS.cs` |
Call `SetPendingScrollRestore` after non-animated `ScrollToItem`;
capture `section`/`item` as ints inside `using` block |
| `src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs` |
`DraggingStarted` override to clear restore target on user drag |
|
`src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt`
| New public override: `ItemsViewDelegator2.DraggingStarted` |

**Total**: fix spans 4 production files

### Screenshots

| Before Issue Fix | After Issue Fix |
|----------|----------|
| <video width="300" height="600"
src="https://github.com/user-attachments/assets/ab398886-d90c-43d1-bd88-270a8e3925c1">
| <video width="300" height="600"
src="https://github.com/user-attachments/assets/e0b01b70-4959-406b-b94f-afd5601fa7d0">)
|
kubaflo pushed a commit that referenced this pull request Aug 12, 2026
…t after Picker interaction (#36431)

> [!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 if this change resolves your issue.
Thank you!

**Note:** This PR was created because the previous PR
(#34356) was closed.

### Root Cause

On Mac Catalyst, certain window state changes (opening/dismissing a
Picker, minimizing/restoring, or resizing the window) cause UIKit to
silently adjust the `UICollectionView` `contentOffset` when the view is
programmatically scrolled near or to the last item.

During these transitions, UIKit recalculates scroll bounds using
estimated item sizes
(`NSCollectionLayoutDimension.CreateEstimated(30f)`). Because the
estimated `contentSize` is smaller than the actual `contentSize`, UIKit
calculates a lower maximum valid `contentOffset` and silently clamps the
current offset down to that value. The scroll position does not always
jump to the very top — it shifts to whatever UIKit calculates as the
maximum valid offset: `max(0, estimatedContentSize - frameHeight)`. No
delegate, layout, or view controller callbacks fire — the change happens
entirely inside UIKit's property mutation machinery.

**Why items near the bottom are affected:** Items near the bottom
require a larger offset than UIKit's estimated bounds permit.
Intermediate items sit within the estimated range and are unaffected.

**Why only Mac Catalyst:** The same window transitions on iOS do not
trigger this recalculation behavior.

**Why conventional fixes fail:** Standard callbacks (`LayoutSubviews`,
`ViewDidLayoutSubviews`, `Scrolled`) are not triggered because this is
not a layout pass or a user scroll. KVO on `contentOffset` is the only
mechanism that fires synchronously at the moment UIKit mutates the
property.

### Description of Change

**KVO-Based Scroll Restore**

The fix uses `NSObject.AddObserver("contentOffset", ...)` (Key-Value
Observing) to detect the silent reset and restore the scroll position.

**Flow:**
1. After a **non-animated** `ScrollTo`,
`SetPendingScrollRestore(section, item, position)` is called, storing
the target index path as plain `int` fields and enabling tracking.
2. KVO `OnContentOffsetChanged` monitors every `contentOffset` change:
- While the new Y is within 10px of the last known Y: treat as normal
movement, update reference.
- If Y drops more than 10px below the last known Y: **silent reset
detected** → async-dispatches `ScrollToItem` to restore position.
3. `DraggingStarted` clears the restore target when the user manually
scrolls (respects user intent).
4. KVO lifecycle is managed in `MovedToWindow` — started when view
attaches, stopped when it detaches.

**Why relative threshold (not `Y < 1.0`):** UIKit does not always reset
to Y=0. It clamps to `max(0, estimatedContentSize - frameHeight)`, which
can be a non-zero value. A fixed `Y < 1.0` check would miss non-zero
resets entirely. Comparing against the last known Y detects any sudden
downward shift regardless of the absolute value.

**Why plain int storage (not NSIndexPath):** `NSIndexPath` is created
inside a `using` block in `ScrollToRequested` and is disposed before the
KVO callback fires. Storing `section` and `item` as `int` fields avoids
this lifetime issue.

**Why `ScrollToItem` instead of `SetContentOffset`:** `ScrollToItem`
recalculates the pixel offset using UIKit's current layout (including
actual item heights), yielding the exact visual position.
`SetContentOffset` uses a raw pixel value that becomes stale after
layout changes.

### Issues Fixed
Fixes #34271

**Files Changed**

| File | Change | 
|------|--------|
| `src/Controls/src/Core/Handlers/Items/iOS/MauiCollectionView.cs` | KVO
observer infrastructure, scroll restore state,
`SetPendingScrollRestore`, `ClearPendingScrollRestore`, `MovedToWindow`
lifecycle |
| `src/Controls/src/Core/Handlers/Items2/ItemsViewHandler2.iOS.cs` |
Call `SetPendingScrollRestore` after non-animated `ScrollToItem`;
capture `section`/`item` as ints inside `using` block |
| `src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs` |
`DraggingStarted` override to clear restore target on user drag |
|
`src/Controls/src/Core/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt`
| New public override: `ItemsViewDelegator2.DraggingStarted` |

**Total**: fix spans 4 production files

### Screenshots

| Before Issue Fix | After Issue Fix |
|----------|----------|
| <video width="300" height="600"
src="https://github.com/user-attachments/assets/ab398886-d90c-43d1-bd88-270a8e3925c1">
| <video width="300" height="600"
src="https://github.com/user-attachments/assets/e0b01b70-4959-406b-b94f-afd5601fa7d0">)
|
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-controls-collectionview CollectionView, CarouselView, IndicatorView community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration platform/macos macOS / Mac Catalyst s/agent-fix-win AI found a better alternative fix than the PR s/agent-gate-failed AI could not verify tests catch the bug 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.

[Unstable] I9_Scrolling - Changing/Hovering the 'ScrollToPosition' would cause the item to scroll without clicking the button

6 participants