[iOS] Fix CollectionView ScrollOffset not resetting when ItemsSource changes#34488
[iOS] Fix CollectionView ScrollOffset not resetting when ItemsSource changes#34488SyedAbdulAzeemSF4852 wants to merge 4 commits intodotnet:mainfrom
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34488Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34488" |
There was a problem hiding this comment.
Pull request overview
Fixes iOS CollectionView VerticalOffset not resetting to 0 when ItemsSource changes, by explicitly resetting ContentOffset after ReloadData() in both Items/ and Items2/ iOS view controllers.
Changes:
- Reset
ContentOffsettoCGPoint.EmptyafterReloadData()in bothItemsViewController.csandItemsViewController2.cs, with a guard to skip when already at origin - Updated test page with a
ScrollToEndbutton (usingScrollTo) replacing unreliableDragCoordinates, and removed platform-specific test skips
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cs |
Reset ContentOffset after ReloadData in deprecated iOS handler |
src/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cs |
Reset ContentOffset after ReloadData in current iOS handler |
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/XFIssue/Issue7993.cs |
Removed platform skips, replaced DragCoordinates with ScrollToEnd tap |
src/Controls/tests/TestCases.HostApp/Issues/XFIssue/Issue7993.xaml |
Added ScrollToEnd button, AutomationIds, updated instructions |
src/Controls/tests/TestCases.HostApp/Issues/XFIssue/Issue7993.xaml.cs |
Added ScrollToEndClicked handler |
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/XFIssue/Issue7993.cs
Show resolved
Hide resolved
🤖 AI Summary📊 Expand Full Review —
|
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #34488 | New IScrollTrackingDelegator interface; ResetScrollTracking() on delegator before ContentOffset reset |
✅ PASSED (Gate) | 5 impl + 3 test | Current PR after revision |
🔧 Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix (opus-4.6) | Controller bool _scrollTrackingNeedsReset flag; delegator checks at top of Scrolled() |
✅ PASS | 4 files | No new types, no interface, no cross-namespace coupling; correctly resets both offset AND delta |
| 2 | try-fix (sonnet-4.6) | Reset ContentOffset BEFORE ReloadData() — pure order-of-operations, no delegator changes |
✅ PASS | 2 files | Most minimal (+3 lines per controller); BUT VerticalDelta in transition event is stale (not reset) |
| 3 | try-fix (gpt-5.3-codex) | Direct cast to concrete delegator type to zero fields | ❌ FAIL | 4 files | Test timeout; runtime issues with cast approach |
| 4 | try-fix (gpt-5.4, gemini unavailable) | Temporarily null CollectionView.Delegate during ContentOffset reset |
❌ FAIL | 2 files | Suppressed zero-offset callback too — label never updated |
| PR | PR #34488 | New IScrollTrackingDelegator interface; ResetScrollTracking() on delegator before ContentOffset reset |
✅ PASSED (Gate) | 5 impl + 3 test (incl. new interface file) | Correct for both offset AND delta; more abstract than Candidate 1 |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| claude-opus-4.6 | 2 | No | "NO NEW IDEAS" — design space well-covered |
| claude-sonnet-4.6 | 2 | Yes | Override SetContentOffset in MauiCollectionView platform view — too invasive, not pursued |
| gpt-5.3-codex | 2 | Yes | Recreate delegator on ItemsSource change — prior agent run already confirmed this fails |
| gpt-5.4 | 2 | Yes | Same as gpt-5.3-codex (recreate delegator) — not pursued for same reason |
Exhausted: Yes — remaining ideas are either too invasive or known failures from prior runs
Selected Fix: Candidate #1 — Controller boolean flag approach. Correctly resets both VerticalOffset AND VerticalDelta. No new files, no interface, no cross-namespace coupling. Simpler than the PR while achieving identical correctness.
📋 Report — Final Recommendation
⚠️ Final Recommendation: REQUEST CHANGES
Phase Status
| Phase | Status | Notes |
|---|---|---|
| Pre-Flight | ✅ COMPLETE | iOS/MacCatalyst bug; 5 impl files + 3 test files; re-review (prior: s/agent-changes-requested) |
| Gate | ✅ PASSED | iOS — tests FAIL without fix, PASS with fix |
| Try-Fix | ✅ COMPLETE | 4 attempts; 2 passing (Candidates 1 & 2); best alternative is Candidate 1 |
| Report | ✅ COMPLETE |
Summary
PR #34488 correctly fixes the iOS CollectionView VerticalOffset stale reporting bug. Gate passed and the test quality is good (the previously-missing App.WaitForNoElement("VerticalOffset: 0") assertion was added, resolving the prior inline review comment). However, Try-Fix found a simpler passing alternative (Candidate 1: controller boolean flag, 4 files, no new abstractions) vs the PR's interface-based approach (5 files + 1 new interface file). This is the second agent review — the prior review also recommended REQUEST CHANGES for the same reason, and the author responded by implementing the interface-based approach rather than the simpler flag approach.
Root Cause
UICollectionView.ReloadData() on iOS does not reset ContentOffset. After ItemsSource replacement, the MAUI Scrolled event delegates (ItemsViewDelegator, ItemsViewDelegator2) retain stale PreviousVerticalOffset/PreviousHorizontalOffset values. The next scrollViewDidScroll callback computes delta from these stale values, causing VerticalOffset to remain non-zero.
Fix Quality
The PR's fix is correct — it properly resets both ContentOffset and the delegator's tracking state before ReloadData, ensuring both VerticalOffset (absolute) and VerticalDelta in the Scrolled event are correct after source replacement.
Design concern: The IScrollTrackingDelegator interface adds abstraction overhead that is not needed for this fix:
- New file:
IScrollTrackingDelegator.cs(single-method interface) - Cross-namespace coupling:
Items2importsMicrosoft.Maui.Controls.Handlers.Items - Null-safe
ascast at the call site:(Delegator as IScrollTrackingDelegator)?.ResetScrollTracking()
Best alternative (Candidate 1): Add internal bool _scrollTrackingNeedsReset to both controller classes. Set it to true before ContentOffset = CGPoint.Empty in UpdateItemsSource(). In both delegators' Scrolled(), check the flag via _viewController.TryGetTarget(out var vc), zero the Previous*Offset fields, and clear the flag. This achieves identical behavior with 4 files, no new types, no interface, and no cross-namespace dependency.
Note for maintainers: If the interface contract is preferred for future extensibility or testability, the PR's approach is functionally acceptable. The key previously-reported quality gap (missing non-zero offset assertion) has been addressed. The remaining concern is design preference — the interface vs. flag distinction is modest.
kubaflo
left a comment
There was a problem hiding this comment.
Could you please review the AI summary comment?
29d06e5 to
2b52feb
Compare
|
/azp run maui-pr-uitests , maui-pr-devicetests |
|
Azure Pipelines successfully started running 2 pipeline(s). |
@kubaflo , I have reviewed the AI summary and implemented the suggested fix. |
🚦 Gate — Test Verification📊 Expand Full Gate —
|
| # | Type | Test Name | Filter |
|---|---|---|---|
| 1 | UITest | Issue7993 | Issue7993 |
Verification
| Step | Expected | Actual | Result |
|---|---|---|---|
| Without fix | FAIL | FAIL | ✅ |
| With fix | PASS | PASS | ✅ |
Details
- ❌ Failed: CollectionViewVerticalOffset [17 s]
- 📋 Error: System.TimeoutException : Timed out waiting for element...
Fix Files Reverted
eng/pipelines/ci-copilot.ymlsrc/Controls/src/Core/Handlers/Items/iOS/ItemsViewController.cssrc/Controls/src/Core/Handlers/Items/iOS/ItemsViewDelegator.cssrc/Controls/src/Core/Handlers/Items2/iOS/ItemsViewController2.cssrc/Controls/src/Core/Handlers/Items2/iOS/ItemsViewDelegator2.cs
New Files (Not Reverted)
src/Controls/src/Core/Handlers/Items/iOS/IScrollTrackingDelegator.cs
Base Branch: main | Merge Base: 720a9d4
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!
Issue Details
Root Cause
Description of Change
Test and UI improvements:
Issues Fixed
Fixes #26366
Fixes #33500
Validated the behaviour in the following platforms
Output
Before.mov
After.mov