[iOS] Fix vertical CarouselView MandatorySingle snapping on iOS#34700
[iOS] Fix vertical CarouselView MandatorySingle snapping on iOS#34700Vignesh-SF3580 wants to merge 3 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 -- 34700Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34700" |
🚦 Gate — Test Verification📊 Expand Full Gate —
|
| # | Type | Test Name | Filter |
|---|---|---|---|
| 1 | UITest | Issue33308 | Issue33308 |
Verification
| Step | Expected | Actual | Result |
|---|---|---|---|
| Without fix | FAIL | FAIL | ✅ |
| With fix | PASS | FAIL | ❌ |
Details
- ❌ Failed: VerticalCarouselMandatorySingleSnapAdvancesOneCard [4 s]
- 📋 Error: VisualTestUtils.VisualTestFailedException :
Snapshot different than baseline: VerticalCarouselMandatorySingleSnapAdvancesOneCard.png (5.13% difference)
If the correct baseline has changed (this isn't...
Fix Files Reverted
eng/pipelines/ci-copilot.ymlsrc/Controls/src/Core/Handlers/Items2/iOS/LayoutFactory2.cs
Base Branch: main | Merge Base: 720a9d4
🤖 AI Summary📊 Expand Full Review —
|
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #34700 | Route vertical CarouselView through CustomUICollectionViewCompositionalLayout; fix offset/loop axis for vertical |
❌ FAILED (Gate) | LayoutFactory2.cs |
Constructor arity bug — 4 args passed, 6 required; snapshot mismatch |
🔧 Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix (claude-opus-4.6) | Fix constructor arity bug: add for missing / params; keep PRs Y-axis and loop-correction fixes | PASS | LayoutFactory2.cs |
Minimal change; completes PR's exact intent; test passed ~3s |
| 2 | try-fix (claude-sonnet-4.6) | scroll delegate override in with page-pitch arithmetic; fix Y-axis tracking in LayoutFactory2 | PASS | CarouselViewDelegator2.cs, LayoutFactory2.cs |
Touches 2 files; different mechanism from layout routing |
| 3 | try-fix (gpt-5.3-codex) | Set OrthogonalScrollingBehavior = GroupPagingCentered for all carousel FAIL |
LayoutFactory2.cs |
2.34% snapshot diff; orthogonal paging for vertical sections misaligns | orientations |
| 4 | try-fix (gpt-5.4, sub gemini) | Route ALL carousels through ; set from orientation | PASS | LayoutFactory2.cs |
Unified path; potentially affects existing horizontal carousel behavior |
| PR | PR #34700 | Route vertical CarouselView through CustomUICollectionViewCompositionalLayout; fix offset/loop FAILED (Gate) |
LayoutFactory2.cs |
Constructor arity 4 args passed, 6 required; code doesn't compile | bug axis |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| claude-opus-4.6 | 2 | Yes | PagingEnabled = true on only works if item == viewport size; doesn't handle peek insets or item spacing |
| claude-sonnet-4.6 | 2 | Yes | DecelerationRate = Fast + override ` same as what CustomUICollectionViewCompositionalLayout already does; not new |
| gpt-5.3-codex | 2 | Yes | Use VisibleItemsInvalidationHandler to compute nearest complex; wrong lifecycle; not a cleaner solution |
| gpt-5.4 | 2 | Yes | Sort LayoutAttributesForElementsInRect by axis before ` quality improvement to ScrollSingle, not a root-cause fix |
Exhausted: all 4 models queried; no cross-pollination ideas materially better than existing passing candidatesYes
Selected Fix: Candidate #1 (Attempt fix constructor arity bug with null, null)1
Reason: Most surgical fix. Resolves the exact compile bug with the fewest changes, directly completes the PR author's intent, and has no risk of affecting horizontal carousel behavior. The PR's overall approach is architecturally it just needs the two missing null arguments.correct
📋 Report — Final Recommendation
⚠️ Final Recommendation: REQUEST CHANGES
Phase Status
| Phase | Status | Notes |
|---|---|---|
| Pre-Flight | ✅ COMPLETE | Issue #33308, iOS-only regression, LayoutFactory2.cs |
| Gate | ❌ FAILED | iOS — 5.13% screenshot diff; code doesn't compile (constructor arity bug) |
| Try-Fix | ✅ COMPLETE | 4 attempts, 3 passing; best fix identified |
| Report | ✅ COMPLETE |
Summary
PR #34700 fixes a real iOS regression (issue #33308) where vertical CarouselView with MandatorySingle snap points freely scrolls instead of snapping. The PR's conceptual approach is correct: route vertical carousels through CustomUICollectionViewCompositionalLayout so the existing TargetContentOffset pipeline handles snapping, and fix the page-calculation and loop-correction axes for vertical.
However, the PR does not compile due to a constructor arity bug. The PR calls new CustomUICollectionViewCompositionalLayout(snapInfo, sectionProvider, layoutConfiguration, itemsUpdatingScrollMode) with 4 arguments, but the constructor requires 6: (snapInfo, groupingInfo?, headerFooterInfo?, sectionProvider, configuration, itemsUpdatingScrollMode). The two nullable parameters groupingInfo and headerFooterInfo are omitted and should be null. This prevents the fix from building, which is why the Gate failed with a 5.13% screenshot mismatch (the app ran without the fix applied).
Root Cause
Vertical CarouselView in the CV2 iOS handler was not routed through CustomUICollectionViewCompositionalLayout, which overrides TargetContentOffset to implement MandatorySingle snapping. Without this routing, snapping was silently ignored. Additionally, the page-offset calculation used offset.X / width (horizontal) instead of offset.Y / height (vertical), causing Position to stay stale, and the loop correction used UICollectionViewScrollPosition.Left instead of .Top for vertical.
Fix Quality
The PR's fix logic is sound and well-targeted. The only required change to make it work is adding null, null for the two missing constructor arguments. Once corrected, the test VerticalCarouselMandatorySingleSnapAdvancesOneCard passes.
Required changes (to unblock the PR):
- var layout = linearItemsLayout?.Orientation == ItemsLayoutOrientation.Vertical
- ? new CustomUICollectionViewCompositionalLayout(
- new LayoutSnapInfo
- {
- SnapType = linearItemsLayout.SnapPointsType,
- SnapAligment = linearItemsLayout.SnapPointsAlignment
- },
- sectionProvider,
- layoutConfiguration,
- linearItemsLayout.ItemsUpdatingScrollMode)
- : new UICollectionViewCompositionalLayout(sectionProvider, layoutConfiguration);
+ var layout = linearItemsLayout?.Orientation == ItemsLayoutOrientation.Vertical
+ ? new CustomUICollectionViewCompositionalLayout(
+ new LayoutSnapInfo
+ {
+ SnapType = linearItemsLayout.SnapPointsType,
+ SnapAligment = linearItemsLayout.SnapPointsAlignment
+ },
+ null,
+ null,
+ sectionProvider,
+ layoutConfiguration,
+ linearItemsLayout.ItemsUpdatingScrollMode)
+ : new UICollectionViewCompositionalLayout(sectionProvider, layoutConfiguration);Additional issues to address:
-
Issue33308.csHostApp — noAutomationId: The carousel view and cards have noAutomationId. The test finds elements by label text ("Card 1") which is fragile. Recommend addingAutomationId = "CardsCarousel"to the CarouselView. -
Android snapshot added for iOS-only issue:
src/Controls/tests/TestCases.Android.Tests/snapshots/android/VerticalCarouselMandatorySingleSnapAdvancesOneCard.pngwas added, but the[Issue]attribute specifiesPlatformAffected.iOSand the test has no Android-specific restriction. This snapshot may be incorrect or unnecessary. -
PR is marked Draft: The PR is still in draft state. The author should mark it ready when the constructor bug is fixed.
…icrosoft.Maui.Controls v10.0.20
2e31d74 to
2c1d4a0
Compare
|
/azp run maui-pr-uitests , maui-pr-devicetests |
|
Azure Pipelines successfully started running 2 pipeline(s). |
I have resolved the compilation errors. |
There was a problem hiding this comment.
Pull request overview
Fixes iOS vertical CarouselView with MandatorySingle snapping by aligning layout selection, page progression, and loop correction to the vertical axis, and adds a regression test/sample for issue #33308.
Changes:
- Route vertical linear
CarouselViewthroughCustomUICollectionViewCompositionalLayoutto enable snap-aware behavior. - Compute page progression and loop correction using the active axis (Y/top for vertical).
- Add UI test + host app repro page for issue 33308.
Reviewed changes
Copilot reviewed 3 out of 6 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/Controls/src/Core/Handlers/Items2/iOS/LayoutFactory2.cs | Aligns snapping/page tracking/loop correction with vertical axis; routes vertical layouts through custom compositional layout. |
| src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33308.cs | Adds UI regression test coverage for the vertical mandatory single snap scenario. |
| src/Controls/tests/TestCases.HostApp/Issues/Issue33308.cs | Adds a host app repro page creating a vertical CarouselView with MandatorySingle snap points. |
|
/azp run maui-pr-uitests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
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
After a swipe, the carousel could move visually, but it did not consistently snap to a single item or reliably update the Position.
Root cause
The vertical CarouselView path in LayoutFactory2.cs had inconsistent behavior:
Because these were not aligned to the vertical axis, the control could move visually while leaving Position updates stale or inconsistent.
Description of change
This PR updates the iOS CarouselView layout path in LayoutFactory2.cs to handle the vertical flow consistently end to end:
With these changes, snapping, page calculation, loop repositioning, and Position updates all follow a consistent vertical model.
Issues Fixed
Fixes #33308
Technical Details
The fix is implemented in src/Controls/src/Core/Handlers/Items2/iOS/LayoutFactory2.cs.
CreateCarouselLayout(...) now detects vertical LinearItemsLayout and routes it through CustomUICollectionViewCompositionalLayout.
VisibleItemsInvalidationHandler calculates page progression using vertical offset and container height for vertical carousels.
Vertical loop correction uses UICollectionViewScrollPosition.Top instead of a horizontal anchor.
This ensures that layout selection, snap behavior, page calculation, and final Position updates are all aligned along the same vertical axis.
Screenshots
33308BeforeFix.mov
33308AfterFix.mov