Skip to content

[iOS] Fix ScrollView with RTL FlowDirection and Horizontal Orientation scrolls in the wrong direction#32529

Open
devanathan-vaithiyanathan wants to merge 9 commits intodotnet:inflight/currentfrom
devanathan-vaithiyanathan:fix-32271
Open

[iOS] Fix ScrollView with RTL FlowDirection and Horizontal Orientation scrolls in the wrong direction#32529
devanathan-vaithiyanathan wants to merge 9 commits intodotnet:inflight/currentfrom
devanathan-vaithiyanathan:fix-32271

Conversation

@devanathan-vaithiyanathan
Copy link
Copy Markdown
Contributor

@devanathan-vaithiyanathan devanathan-vaithiyanathan commented Nov 12, 2025

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

When a ScrollView is set to RightToLeft (RTL) flow direction and its Orientation is changed at runtime, the scrolling behavior is not working on iOS.

Description of Change

Added orientation tracking and enabled the logic to perform RTL layout handling during cross-platform arrange.

Issues Fixed

Fixes #32271

Tested the behavior in the following platforms.

  • Android
  • Windows
  • iOS
  • Mac
Before After
iOS
Before.mov
iOS
After.mov

@dotnet-policy-service dotnet-policy-service bot added the partner/syncfusion Issues / PR's with Syncfusion collaboration label Nov 12, 2025
@sheiksyedm sheiksyedm marked this pull request as ready for review November 17, 2025 14:10
Copilot AI review requested due to automatic review settings November 17, 2025 14:10
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR fixes an iOS-specific issue where ScrollView with RTL (Right-To-Left) FlowDirection fails to scroll correctly when the Orientation property is changed at runtime from Vertical to Horizontal. The fix adds orientation change tracking to ensure RTL layout adjustments are properly recalculated.

Key changes:

  • Added OnOrientationChanged() method to MauiScrollView to reset RTL layout state
  • Updated MapOrientation handler to call OnOrientationChanged() when orientation changes at runtime
  • Added UI test coverage with Issue32271 test case

Reviewed Changes

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

File Description
src/Core/src/Platform/iOS/MauiScrollView.cs Adds OnOrientationChanged() method to reset cached RTL layout direction and trigger re-layout when orientation changes
src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs Calls OnOrientationChanged() in MapOrientation when the platform view is loaded to handle runtime orientation changes
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue32271.cs Adds NUnit test that toggles orientation and verifies scrolling behavior via screenshot
src/Controls/tests/TestCases.HostApp/Issues/Issue32271.cs Adds test host page with RTL ScrollView that supports runtime orientation toggling and scroll-to commands

internal void OnOrientationChanged()
{
// Reset the previous layout direction to force re-evaluation of RTL layout
if (_previousEffectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirection.RightToLeft)
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

The condition if (_previousEffectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirection.RightToLeft) only triggers when the cached direction was RTL. However, this means that if the orientation changes while the layout direction is LTR, no re-layout will occur.

Consider what happens in this scenario:

  1. ScrollView starts with RTL + Horizontal orientation (works fine on initial load)
  2. User changes to Vertical orientation (resets _previousEffectiveUserInterfaceLayoutDirection to null)
  3. User changes back to Horizontal orientation (RTL layout adjustments won't apply because _previousEffectiveUserInterfaceLayoutDirection is now null, not RTL)

The fix should trigger re-layout whenever the current EffectiveUserInterfaceLayoutDirection is RTL, regardless of the previous cached value:

if (EffectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirection.RightToLeft)
{
    _previousEffectiveUserInterfaceLayoutDirection = null;
    SetNeedsLayout();
    LayoutIfNeeded();
}
Suggested change
if (_previousEffectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirection.RightToLeft)
if (EffectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirection.RightToLeft)

Copilot uses AI. Check for mistakes.
Comment on lines +36 to +37
FlowDirection = FlowDirection.RightToLeft,
Orientation = ScrollOrientation.Horizontal
Copy link

Copilot AI Nov 17, 2025

Choose a reason for hiding this comment

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

There's a mismatch between the ScrollView's initial orientation declaration and the ViewModel's initial state. The ScrollView is declared with Orientation = ScrollOrientation.Horizontal (line 37), but the ViewModel initializes with _orientation = ScrollOrientation.Vertical (line 88 in HostApp).

This creates confusion about the actual initial state. The binding on line 39 will override the hardcoded value with the ViewModel's Vertical value.

Either:

  1. Remove line 37's Orientation = ScrollOrientation.Horizontal and rely solely on the binding, OR
  2. Initialize the ViewModel's _orientation to ScrollOrientation.Horizontal to match the declaration

Option 1 is preferred for clarity since the orientation is data-bound.

Suggested change
FlowDirection = FlowDirection.RightToLeft,
Orientation = ScrollOrientation.Horizontal
FlowDirection = FlowDirection.RightToLeft

Copilot uses AI. Check for mistakes.
@karthikraja-arumugam karthikraja-arumugam added the community ✨ Community Contribution label Dec 4, 2025
@rmarinho
Copy link
Copy Markdown
Member

rmarinho commented Feb 16, 2026

🤖 AI Summary

📊 Expand Full Review
🔍 Pre-Flight — Context & Validation
📝 Review SessionUpdate Issue32271.cs · dc92906

Issue: #32271 - ScrollView with RTL FlowDirection and Horizontal Orientation scrolls in the wrong direction on iOS
PR: #32529 by @devanathan-vaithiyanathan (Syncfusion partner)
Platforms Affected: iOS only
Files Changed: 2 implementation files, 2 test files

Issue Summary

When a ScrollView is set to FlowDirection = RightToLeft and its Orientation is changed at runtime (Vertical → Horizontal), the RTL scrolling logic in MauiScrollView.CrossPlatformArrange() is not re-applied. The RTL layout block is gated on _previousEffectiveUserInterfaceLayoutDirection != EffectiveUserInterfaceLayoutDirection, which after the initial layout pass evaluates to RTL != RTL (false), so it never fires again even when orientation changes. The result: the content can only be scrolled into empty space instead of the correct RTL direction.

Expected: Dragging right-to-left reveals content on the right side
Actual: Scroll direction is reversed; scrolling into empty space

Prior Agent Review (Same Commit dc92906)

A prior agent review exists from the same commit dc92906. Labels: s/agent-changes-requested, s/agent-gate-failed, s/agent-fix-pr-picked.

  • Pre-Flight: COMPLETE
  • Gate: FAILED (missing VerifyScreenshot baseline snapshot)
  • Fix: SKIPPED (rate limit), code analysis confirms fix is correct
  • Report: REQUEST CHANGES (baseline snapshot missing)

Reviewer Feedback

File:Line Reviewer Says Status
MauiScrollView.cs:113 Check EffectiveUserInterfaceLayoutDirection == RTL instead of _previousEffectiveUserInterfaceLayoutDirection == RTL 🔍 ANALYZED - NOT A REAL BUG (see Fix phase)
TestCases.HostApp/Issues/Issue32271.cs Orientation mismatch between ScrollView declaration and ViewModel ✅ FIXED (commit dc92906, thread is Outdated)

Files Changed

Implementation (Fix):

  • src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs (+8) — Calls mauiScrollView.OnOrientationChanged() in MapOrientation when view is loaded
  • src/Core/src/Platform/iOS/MauiScrollView.cs (+14) — Adds OnOrientationChanged() method that resets _previous to null and forces layout

Tests:

  • src/Controls/tests/TestCases.HostApp/Issues/Issue32271.cs (+146) — UI test host page with RTL ScrollView + Toggle Orientation button
  • src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue32271.cs (+20) — NUnit test: tap Toggle → tap Scroll → VerifyScreenshot

Test Type: UI Tests (Appium-based, VerifyScreenshot)

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #32529 Reset _previousEffectiveUserInterfaceLayoutDirection to null in OnOrientationChanged(), force layout ⏳ PENDING (Gate) ScrollViewHandler.iOS.cs (+8), MauiScrollView.cs (+14) Logic analyzed as correct

🚦 Gate — Test Verification
📝 Review SessionUpdate Issue32271.cs · dc92906

Result: ❌ FAILED
Platform: ios
Mode: Full Verification

  • Tests FAIL without fix ✅ (expected - bug detected)
  • Tests PASS with fix ❌ (unexpected - fix doesn't resolve test)

Root Cause of Gate Failure

The test VerifyScrollViewDirection() calls VerifyScreenshot() which requires a committed baseline snapshot file. No baseline exists in the repository:

src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyScrollViewDirection.png

(or similar path — file does not exist)

Both test runs (with and without fix) fail because the snapshot infrastructure cannot find a baseline to compare against. This is NOT a bug in the fix — the build succeeds and the fix is logically correct. The test cannot pass until the PR author:

  1. Runs the test locally on iOS to generate the baseline snapshot
  2. Commits the baseline snapshot PNG to the repository

Note

This is the SECOND review on the same commit dc92906. The prior agent review (prior run, same commit) also failed Gate for this same reason. The PR author has not yet addressed the missing baseline.


🔧 Fix — Analysis & Comparison
📝 Review SessionUpdate Issue32271.cs · dc92906

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #32529 Reset _previousEffectiveUserInterfaceLayoutDirection to null via OnOrientationChanged() when orientation changes at runtime; force layout via SetNeedsLayout() + LayoutIfNeeded() ❌ NOT VERIFIED (Gate baseline missing) ScrollViewHandler.iOS.cs (+8), MauiScrollView.cs (+14) Code analysis confirms logic is correct (see below)

Exhausted: No (Fix phase skipped — Gate did not pass)

Selected Fix: PR's fix — Code analysis (below) confirms the approach is logically sound.

Code Analysis of PR's Fix

Root cause: In MauiScrollView.CrossPlatformArrange() (line 486), the RTL content offset logic is gated on:

if (_previousEffectiveUserInterfaceLayoutDirection != EffectiveUserInterfaceLayoutDirection)

After the initial layout pass, _previous = RightToLeft. On subsequent layout passes (even after orientation change), RTL != RTL evaluates to false — the RTL positioning is never re-applied for the new orientation dimensions.

Fix logic (OnOrientationChanged):

if (_previousEffectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirection.RightToLeft)
{
    _previousEffectiveUserInterfaceLayoutDirection = null;    // Reset to force re-evaluation
    SetNeedsLayout();
    LayoutIfNeeded();                                         // Synchronous layout pass
}

Setting _previous = null makes the condition null != RTL → true, forcing the RTL layout block to execute for the new orientation dimensions.

Correctness trace (repeated toggles):

  1. Initial (RTL, Horizontal): null != RTL → RTL layout applied → _previous = RTL
  2. Toggle to Vertical: OnOrientationChanged()_previous = null → layout pass → null != RTL → RTL layout applied → _previous = RTL
  3. Toggle back to Horizontal: OnOrientationChanged()_previous = RTL (TRUE) → _previous = null → layout pass → null != RTL → RTL layout applied → _previous = RTL
  4. Repeated toggles: same pattern ✅

Copilot reviewer's suggestion analysis: The Copilot reviewer suggested changing the condition to EffectiveUserInterfaceLayoutDirection == RightToLeft. This concern is based on an incorrect assumption that _previous becomes null after step 2 and stays null. In reality, the LayoutIfNeeded() call in OnOrientationChanged() triggers ArrangeContent() which sets _previous = RTL before returning. So on step 3, _previous == RTL is TRUE and the fix works correctly. The Copilot suggestion is not needed.

Minor concerns:

  • Double layout pass: OnOrientationChanged() calls LayoutIfNeeded() synchronously, then MapOrientation calls InvalidateMeasure(). Minor performance cost for complex layouts.
  • No platform guard on NUnit test: PlatformAffected.iOS in HostApp page but test has no [Ignore] for other platforms.

📋 Report — Final Recommendation
📝 Review SessionUpdate Issue32271.cs · dc92906

⚠️ Final Recommendation: REQUEST CHANGES

Summary

PR #32529 fixes a real and confirmed iOS bug where a ScrollView with FlowDirection = RightToLeft loses its RTL scrolling behavior after the Orientation is changed at runtime. The fix approach is logically sound and minimal. However:

  1. [Blocking] The VerifyScrollViewDirection test calls VerifyScreenshot() but no baseline snapshot has been committed to the repository — the test will fail in CI on every run until this is added.
  2. [Non-blocking] The Copilot reviewer's inline suggestion at MauiScrollView.cs:113 has NOT been addressed (still open/unresolved), though code analysis confirms the suggestion is actually not needed (see analysis below).
  3. [Minor] The Issue32271.cs NUnit test file is missing a newline at end of file.
  4. [Minor] The NUnit test has no platform guard, but the HostApp page declares PlatformAffected.iOS.

This is the second agent review on the same commit (dc92906). The prior review also failed Gate for the identical reason. The PR author has not yet addressed the missing baseline.

Root Cause

In MauiScrollView.CrossPlatformArrange(), the RTL content offset logic is protected by:

if (_previousEffectiveUserInterfaceLayoutDirection != EffectiveUserInterfaceLayoutDirection)

After the initial layout pass sets _previous = RightToLeft, this condition always evaluates to false on subsequent passes — the RTL positioning is never re-applied when orientation changes (which changes content dimensions, requiring a new offset calculation).

Fix Analysis

PR's Approach: Add OnOrientationChanged() to MauiScrollView that resets _previousEffectiveUserInterfaceLayoutDirection = null (when it was RTL), then calls SetNeedsLayout() + LayoutIfNeeded() to synchronously re-trigger the RTL layout block.

Is the fix correct? Yes. The logic handles repeated orientation toggles correctly:

  • After each toggle, LayoutIfNeeded() restores _previous = RTL before the method returns
  • So on the next toggle, _previous == RTL is always true → the reset works for all subsequent toggles

Copilot reviewer's suggestion: The suggestion to change the condition to EffectiveUserInterfaceLayoutDirection == RightToLeft is based on an incorrect assumption that _previous stays null after a toggle. In reality, ArrangeContent() (called by LayoutIfNeeded()) sets _previous = RTL before returning. The original PR's condition is functionally equivalent for the bug scenario. The suggestion is NOT required.

Issues Requiring Changes

[Blocking] Missing baseline snapshot for VerifyScrollViewDirection test

Evidence from Gate: Both test runs (WITH and WITHOUT fix) failed because the baseline snapshot does not exist:

  • src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/VerifyScrollViewDirection.pngMISSING

Required action:

  1. Run the test locally on iOS to generate the baseline screenshot
  2. Commit the baseline PNG to the PR branch
  3. See visual test workflow

[Minor] Missing newline at EOF

src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue32271.cs — no newline at end of file.

[Minor] NUnit test runs on all platforms but HostApp page is iOS-only

The NUnit test has no [Category] or [Ignore] to restrict it to iOS, while Issue32271 : ContentPage declares PlatformAffected.iOS. This may cause failures on other platforms if the test infrastructure can't find the page.

Phases Completed

Phase Status Notes
Pre-Flight ✅ COMPLETE Context gathered; prior review imported
Gate ❌ FAILED Missing baseline snapshot (same as prior review)
Fix ⏩ SKIPPED Gate did not pass; code analysis performed manually
Report ✅ COMPLETE REQUEST CHANGES for baseline snapshot

PR Quality Assessment

Aspect Assessment
Fix correctness ✅ Logically correct
Fix scope ✅ Minimal, iOS-only
Code quality ✅ Well-commented, follows conventions
Test structure ✅ Correct (orientation mismatch fixed in prior commit)
Test baseline ❌ Missing — CI will fail every run
Title accuracy ✅ Accurate

📋 Expand PR Finalization Review
Title: ✅ Good

Current: [iOS] Fix ScrollView with RTL FlowDirection and Horizontal Orientation scrolls in the wrong direction

Description: ✅ Good

Description needs updates. See details below.

✨ Suggested PR Description

[!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!

Root Cause

When a ScrollView has FlowDirection=RightToLeft and its Orientation is changed at runtime, the RTL content-offset adjustment in MauiScrollView.CrossPlatformArrange is not re-triggered.

The RTL adjustment logic (which repositions content and sets ContentOffset for right-to-left scrolling) only runs when _previousEffectiveUserInterfaceLayoutDirection != EffectiveUserInterfaceLayoutDirection. On initial load this fires once (previous is null, current is RightToLeft), then _previousEffectiveUserInterfaceLayoutDirection is set to RightToLeft. On subsequent layout passes (including those triggered by an orientation change), both values are RightToLeft, so the condition is false and the RTL adjustment is skipped — leaving the content offset wrong for the new orientation.

Description of Change

MauiScrollView.cs (iOS platform layer)

Added internal void OnOrientationChanged(). When the current layout direction is RightToLeft, this method resets _previousEffectiveUserInterfaceLayoutDirection to null, then calls SetNeedsLayout() and LayoutIfNeeded(). This tricks the RTL adjustment logic into believing the layout direction "changed" on the next arrange pass, causing it to recalculate the horizontal content offset for the new orientation.

ScrollViewHandler.iOS.cs

In MapOrientation, after updating the enabled state and before calling InvalidateMeasure, the handler now calls mauiScrollView.OnOrientationChanged() when the platform view is a MauiScrollView and IsLoaded() is true. The IsLoaded() guard prevents interference during the initial load path, where the RTL adjustment already works correctly.

Test files

Added Issue32271.cs in TestCases.HostApp/Issues/ and TestCases.Shared.Tests/Tests/Issues/. The host app page sets up a ScrollView with FlowDirection=RightToLeft, binds Orientation to a viewmodel, and provides a "Toggle Orientation" button and a "Scroll" button. The NUnit test taps "Toggle Orientation" (Vertical → Horizontal), taps "Scroll", and verifies via screenshot.

Key Technical Details

Relevant field: _previousEffectiveUserInterfaceLayoutDirection (UIUserInterfaceLayoutDirection?) in MauiScrollView

  • Tracks last-seen layout direction to gate the RTL one-time adjustment
  • Setting it to null re-arms the RTL adjustment for the next layout pass

Affected scenario: RTL + runtime Orientation change only. Initial load (Vertical or Horizontal with RTL) is unaffected because the direction tracking fires normally on first arrange.

IsLoaded() guard: Prevents calling OnOrientationChanged() during initial mapper setup, where _previousEffectiveUserInterfaceLayoutDirection is already null and no reset is needed.

Issues Fixed

Fixes #32271

Platforms Tested

  • Android
  • Windows
  • iOS
  • Mac
Code Review: ✅ Passed

Code Review — PR #32529

🟡 Suggestions


1. LayoutIfNeeded() in OnOrientationChanged() may be redundant

File: src/Core/src/Platform/iOS/MauiScrollView.cs

internal void OnOrientationChanged()
{
    if (_previousEffectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirection.RightToLeft)
    {
        _previousEffectiveUserInterfaceLayoutDirection = null;
        SetNeedsLayout();
        LayoutIfNeeded();  // <-- concern
    }
}

LayoutIfNeeded() forces a synchronous layout pass. At this point the orientation hasn't actually been applied to the cross-platform layout yet — InvalidateMeasure (the call that follows in MapOrientation) is what drives the full re-measure+arrange cycle. If the scroll view's frame hasn't changed, LayoutSubviews will see frameChanged = false and skip CrossPlatformArrange, meaning the _previousEffectiveUserInterfaceLayoutDirection = null reset will persist until the InvalidateMeasure-triggered pass anyway.

The LayoutIfNeeded() call is not harmful but its value is unclear. It could force an intermediate layout pass with stale dimensions. At minimum, a comment explaining why the immediate synchronous layout is necessary (vs. letting InvalidateMeasure handle it) would make this clearer.


2. VerifyScreenshot() without a stability wait after scroll

File: src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue32271.cs

public void VerifyScrollViewDirection()
{
    App.WaitForElement("ToggleOrientationButton");
    App.Tap("ToggleOrientationButton");
    App.Tap("ScrollToEndButton");
    VerifyScreenshot();  // called immediately after tap
}

The "Scroll" button triggers ScrollToAsync(..., animated: true). The animated scroll may not complete before VerifyScreenshot() is called, making the screenshot non-deterministic. Consider using VerifyScreenshot(retryTimeout: TimeSpan.FromSeconds(2)) to allow the animation to settle:

VerifyScreenshot(retryTimeout: TimeSpan.FromSeconds(2));

3. Screenshot-only assertion does not verify scroll direction correctness

File: src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue32271.cs

The test verifies visual appearance via screenshot but does not verify the actual scroll direction programmatically. If the scroll position is wrong (the original bug), the screenshot would differ from the baseline — but only after a baseline has been established with the fix applied. This is reasonable for a regression test, though a GetRect()-based position check would be more robust.


4. Missing newline at end of test file

File: src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue32271.cs

The diff shows \ No newline at end of file. All source files should end with a newline.


5. Nullable annotation gap in test viewmodel

File: src/Controls/tests/TestCases.HostApp/Issues/Issue32271.cs

View _content;  // not nullable, but initialized via constructor path

The field _content is a reference type declared without ?. The compiler may emit a nullable warning since it's not initialized in the field declaration. It's initialized via the Content setter in the constructor (Content = new Label {...}), but the declaration should use View? _content; to match the nullable intent:

View? _content;

Similarly, the PropertyChanged event:

public event PropertyChangedEventHandler PropertyChanged;
// should be:
public event PropertyChangedEventHandler? PropertyChanged;

6. Dead code: ContentText property in test viewmodel

File: src/Controls/tests/TestCases.HostApp/Issues/Issue32271.cs

public string ContentText
{
    get => _contentText;
    set { ... Content = new Label { Text = _contentText }; ... }
}

ContentText (and the backing field _contentText) is never bound or used in the test page. It appears to be carry-over from the issue reporter's original reproduction. It creates a dead Label on set and can be removed.


✅ Looks Good

  • IsLoaded() guard in MapOrientation correctly prevents the orientation-change path from firing during initial mapper setup — good defensive coding.
  • OnOrientationChanged() scope — correctly scoped to internal, not public API.
  • RTL-only guardif (_previousEffectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirection.RightToLeft) correctly limits the reset to RTL views, avoiding interference with LTR scrollviews.
  • Test page structure — uses viewmodel + binding for orientation, matching real-world usage patterns that trigger the bug.
  • [Category(UITestCategories.ScrollView)] — correct category for the test.
  • platformView is MauiScrollView type check — safe cast before calling the new method.

@rmarinho rmarinho added s/agent-changes-requested AI agent recommends changes - found a better alternative or issues 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) s/agent-fix-lose Author adopted the agent's fix and it turned out to be bad labels Feb 16, 2026
@kubaflo kubaflo added s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates and removed s/agent-fix-lose Author adopted the agent's fix and it turned out to be bad labels Feb 20, 2026
kubaflo

This comment was marked as resolved.

@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Mar 29, 2026

🚦 Gate - Test Before and After Fix

📊 Expand Full Gate5d6fd9c · snapshot added

Gate Result: ❌ FAILED

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

Test Without Fix (expect FAIL) With Fix (expect PASS)
🖥️ Issue32271 Issue32271 ❌ PASS — 203s ✅ PASS — 88s
🔴 Without fix — 🖥️ Issue32271: PASS ❌ · 203s
  Determining projects to restore...
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 607 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 711 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 6.23 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/Foldable/src/Controls.Foldable.csproj (in 6.45 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 6.44 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Xaml/Controls.Xaml.csproj (in 6.44 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 6.47 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/BlazorWebView/src/Maui/Microsoft.AspNetCore.Components.WebView.Maui.csproj (in 6.48 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj (in 6.47 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/Maps/src/Controls.Maps.csproj (in 6.55 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Core/maps/src/Maps.csproj (in 6.56 sec).
/Users/cloudtest/vss/_work/1/s/.dotnet/packs/Microsoft.iOS.Sdk.net10.0_26.0/26.0.11017/targets/Xamarin.Shared.Sdk.targets(309,3): warning : RuntimeIdentifier was set on the command line, and will override the value for RuntimeIdentifiers set in the project file. [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-ios]
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0-ios26.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0-ios26.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0-ios26.0/Microsoft.Maui.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Maps/Debug/net10.0-ios26.0/Microsoft.Maui.Maps.dll
  Controls.BindingSourceGen -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  Controls.Foldable -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Foldable.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  Controls.Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Maps.dll
  Controls.Xaml -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Xaml.dll
  Microsoft.AspNetCore.Components.WebView.Maui -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-ios26.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
  Detected signing identity:
    Code Signing Key: "" (-)
    Provisioning Profile: "" () - no entitlements
    Bundle Id: com.microsoft.maui.uitests
    App Id: com.microsoft.maui.uitests
  Controls.TestCases.HostApp -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-ios/iossimulator-arm64/Controls.TestCases.HostApp.dll
  Optimizing assemblies for size may change the behavior of the app. Be sure to test after publishing. See: https://aka.ms/dotnet-illink
  Optimizing assemblies for size. This process might take a while.

Build succeeded.

/Users/cloudtest/vss/_work/1/s/.dotnet/packs/Microsoft.iOS.Sdk.net10.0_26.0/26.0.11017/targets/Xamarin.Shared.Sdk.targets(309,3): warning : RuntimeIdentifier was set on the command line, and will override the value for RuntimeIdentifiers set in the project file. [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-ios]
    1 Warning(s)
    0 Error(s)

Time Elapsed 00:01:39.20
  Determining projects to restore...
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/tests/CustomAttributes/Controls.CustomAttributes.csproj (in 670 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 671 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 671 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 671 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/VisualTestUtils/VisualTestUtils.csproj (in 0.8 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.Core/UITest.Core.csproj (in 1 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 1.01 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 1.04 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.NUnit/UITest.NUnit.csproj (in 1.41 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.Appium/UITest.Appium.csproj (in 1.68 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/UITest.Analyzers/UITest.Analyzers.csproj (in 2.21 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/TestUtils/src/VisualTestUtils.MagickNet/VisualTestUtils.MagickNet.csproj (in 2.67 sec).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.iOS.Tests/Controls.TestCases.iOS.Tests.csproj (in 2.69 sec).
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
  Controls.CustomAttributes -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
  UITest.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
  VisualTestUtils -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
  UITest.NUnit -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
  VisualTestUtils.MagickNet -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
  UITest.Appium -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
  UITest.Analyzers -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
  Controls.TestCases.iOS.Tests -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
Test run for /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (arm64)

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
/Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.04]   Discovering: Controls.TestCases.iOS.Tests
[xUnit.net 00:00:00.13]   Discovered:  Controls.TestCases.iOS.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
   NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 4/2/2026 3:02:33 AM FixtureSetup for Issue32271(iOS)
>>>>> 4/2/2026 3:02:37 AM VerifyScrollViewDirection Start
>>>>> 4/2/2026 3:02:39 AM VerifyScrollViewDirection Stop
  Passed VerifyScrollViewDirection [2 s]
NUnit Adapter 4.5.0.0: Test execution complete

Test Run Successful.
Total tests: 1
     Passed: 1
 Total time: 1.0349 Minutes

🟢 With fix — 🖥️ Issue32271: PASS ✅ · 88s
  Determining projects to restore...
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 395 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 415 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 416 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 457 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 469 ms).
  6 of 11 projects are up-to-date for restore.
/Users/cloudtest/vss/_work/1/s/.dotnet/packs/Microsoft.iOS.Sdk.net10.0_26.0/26.0.11017/targets/Xamarin.Shared.Sdk.targets(309,3): warning : RuntimeIdentifier was set on the command line, and will override the value for RuntimeIdentifiers set in the project file. [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-ios]
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0-ios26.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0-ios26.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0-ios26.0/Microsoft.Maui.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  Controls.BindingSourceGen -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Maps/Debug/net10.0-ios26.0/Microsoft.Maui.Maps.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  Controls.Maps -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Maps.dll
  Microsoft.AspNetCore.Components.WebView.Maui -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Microsoft.AspNetCore.Components.WebView.Maui/Debug/net10.0-ios26.0/Microsoft.AspNetCore.Components.WebView.Maui.dll
  Controls.Xaml -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Xaml.dll
  Controls.Foldable -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Foldable/Debug/net10.0-ios26.0/Microsoft.Maui.Controls.Foldable.dll
  Detected signing identity:
    Code Signing Key: "" (-)
    Provisioning Profile: "" () - no entitlements
    Bundle Id: com.microsoft.maui.uitests
    App Id: com.microsoft.maui.uitests
  Controls.TestCases.HostApp -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.HostApp/Debug/net10.0-ios/iossimulator-arm64/Controls.TestCases.HostApp.dll
  Optimizing assemblies for size may change the behavior of the app. Be sure to test after publishing. See: https://aka.ms/dotnet-illink
  Optimizing assemblies for size. This process might take a while.

Build succeeded.

/Users/cloudtest/vss/_work/1/s/.dotnet/packs/Microsoft.iOS.Sdk.net10.0_26.0/26.0.11017/targets/Xamarin.Shared.Sdk.targets(309,3): warning : RuntimeIdentifier was set on the command line, and will override the value for RuntimeIdentifiers set in the project file. [/Users/cloudtest/vss/_work/1/s/src/Controls/tests/TestCases.HostApp/Controls.TestCases.HostApp.csproj::TargetFramework=net10.0-ios]
    1 Warning(s)
    0 Error(s)

Time Elapsed 00:00:43.74
  Determining projects to restore...
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/BindingSourceGen/Controls.BindingSourceGen.csproj (in 463 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 424 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Essentials/src/Essentials.csproj (in 463 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 484 ms).
  Restored /Users/cloudtest/vss/_work/1/s/src/Core/src/Core.csproj (in 496 ms).
  8 of 13 projects are up-to-date for restore.
  Controls.CustomAttributes -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.CustomAttributes/Debug/net10.0/Controls.CustomAttributes.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  Graphics -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  Essentials -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
  Controls.BindingSourceGen -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
  ##vso[build.updatebuildnumber]10.0.60-ci+azdo.13723349
  Controls.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
  UITest.Core -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Core/Debug/net10.0/UITest.Core.dll
  VisualTestUtils -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils/Debug/netstandard2.0/VisualTestUtils.dll
  UITest.Appium -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Appium/Debug/net10.0/UITest.Appium.dll
  UITest.NUnit -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.NUnit/Debug/net10.0/UITest.NUnit.dll
  VisualTestUtils.MagickNet -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/VisualTestUtils.MagickNet/Debug/netstandard2.0/VisualTestUtils.MagickNet.dll
  UITest.Analyzers -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/UITest.Analyzers/Debug/netstandard2.0/UITest.Analyzers.dll
  Controls.TestCases.iOS.Tests -> /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
Test run for /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll (.NETCoreApp,Version=v10.0)
VSTest version 18.0.1 (arm64)

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
/Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.03]   Discovering: Controls.TestCases.iOS.Tests
[xUnit.net 00:00:00.12]   Discovered:  Controls.TestCases.iOS.Tests
NUnit Adapter 4.5.0.0: Test execution started
Running selected tests in /Users/cloudtest/vss/_work/1/s/artifacts/bin/Controls.TestCases.iOS.Tests/Debug/net10.0/Controls.TestCases.iOS.Tests.dll
   NUnit3TestExecutor discovered 1 of 1 NUnit test cases using Current Discovery mode, Non-Explicit run
>>>>> 4/2/2026 3:04:01 AM FixtureSetup for Issue32271(iOS)
>>>>> 4/2/2026 3:04:05 AM VerifyScrollViewDirection Start
>>>>> 4/2/2026 3:04:07 AM VerifyScrollViewDirection Stop
  Passed VerifyScrollViewDirection [1 s]
NUnit Adapter 4.5.0.0: Test execution complete

Test Run Successful.
Total tests: 1
     Passed: 1
 Total time: 18.5537 Seconds

⚠️ Issues found
  • Issue32271 PASSED without fix (should fail) — tests don't catch the bug
📁 Fix files reverted (3 files)
  • eng/pipelines/ci-copilot.yml
  • src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs
  • src/Core/src/Platform/iOS/MauiScrollView.cs

@MauiBot
Copy link
Copy Markdown
Collaborator

MauiBot commented Mar 29, 2026

🤖 AI Summary

📊 Expand Full Review5d6fd9c · snapshot added
🔍 Pre-Flight — Context & Validation

Issue: #32271 - ScrollView with RTL FlowDirection and Horizontal Orientation scrolls in the wrong direction on iOS
PR: #32529 - [iOS] Fix ScrollView with RTL FlowDirection and Horizontal Orientation scrolls in the wrong direction
Platforms Affected: iOS only (PlatformAffected.iOS)
Files Changed: 2 implementation, 2 test, 3 snapshot files

Key Findings

  • Root Cause: MauiScrollView.CrossPlatformArrange() has RTL offset logic gated on bool isDirectionChange = _previousEffectiveUserInterfaceLayoutDirection != EffectiveUserInterfaceLayoutDirection. After the initial layout pass sets _previous = RTL, subsequent arrange calls (triggered by orientation change) see RTL == RTLisDirectionChange = false, so the RTL ContentOffset correction is never re-applied.
  • PR's Fix: Adds OnOrientationChanged() that resets _previous to null (when _previous == RTL) and calls SetNeedsLayout()+LayoutIfNeeded() to trigger a fresh layout. Called from MapOrientation handler.
  • Gate Failure: Test PASSED without the fix (should have FAILED). Root cause: snapshot was added with PR in place; VerifyScreenshot() compares to stored image of buggy behavior, so it always passes.
  • Known code bug (prior Copilot reviewer): OnOrientationChanged checks _previousEffectiveUserInterfaceLayoutDirection == RTL but should check EffectiveUserInterfaceLayoutDirection == RTL (current, not cached) — if _previous is null (e.g., view never laid out in RTL before), the reset won't fire.
  • HostApp mismatch (prior Copilot reviewer, now outdated): ScrollView's inline Orientation = Horizontal conflicts with ViewModel's Vertical default; resolved in later commit.
  • Prior agent review: Commit dc92906 reviewed; Gate FAILED (missing snapshot); report requested changes. Current commit 5d6fd9c adds snapshots but gate still fails due to snapshot quality issue.

Reviewer Feedback

File:Line Issue Status
MauiScrollView.cs:113 Check current EffectiveUserInterfaceLayoutDirection not _previous 🔍 OPEN — real bug in PR code
Issue32271.cs HostApp Orientation mismatch (ScrollView hardcodes Horizontal, ViewModel starts Vertical) ✅ Outdated thread

Test Analysis

  • Test type: UI Test (Appium + VerifyScreenshot())
  • Test command: pwsh .github/scripts/BuildAndRunHostApp.ps1 -Platform ios -TestFilter "FullyQualifiedName~Issue32271"
  • Gate failure reason: Snapshot snapshots/ios/VerifyScrollViewDirection.png was generated with the PR fix applied; test passes regardless of fix because visual appearance matches the stored (potentially incorrect) baseline.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #32529 Add OnOrientationChanged() resetting _previous to null when it was RTL; call from MapOrientation ❌ Gate FAILED (test passes without fix) MauiScrollView.cs, ScrollViewHandler.iOS.cs Has bug: condition checks _previous instead of EffectiveUserInterfaceLayoutDirection

🔧 Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix (claude-opus-4.6) Self-contained orientation tracking in CrossPlatformArrange — store _previousScrollOrientation, reset _previous direction on orientation change in RTL mode ✅ PASS MauiScrollView.cs (1 file) No handler changes needed; fixes condition bug too
2 try-fix (claude-sonnet-4.6) Handler-only: in MapOrientation, force layout + directly set ContentOffset to max horizontal when RTL+Horizontal — bypasses isDirectionChange entirely ✅ PASS ScrollViewHandler.iOS.cs (1 file) Clean handler-only fix; zero MauiScrollView changes
3 try-fix (gpt-5.3-codex) One-shot _rtlOffsetPending flag — set in MapOrientation, consumed in CrossPlatformArrange to re-apply RTL offset ✅ PASS MauiScrollView.cs, ScrollViewHandler.iOS.cs Explicit flag pattern; cleaner intent but 2 files
4 try-fix (gpt-5.1, gemini unavailable) In CrossPlatformArrange: also apply RTL offset when ContentOffset.X ≈ 0 AND RTL AND Horizontal — "at wrong start position" heuristic ✅ PASS MauiScrollView.cs (1 file) Weakest — heuristic could false-positive if user scrolled back to 0
PR PR #32529 Add OnOrientationChanged() that resets _previous to null when it was RTL; call from MapOrientation ❌ Gate FAILED (test passes without fix — snapshot quality issue) MauiScrollView.cs, ScrollViewHandler.iOS.cs Bug: checks _previous instead of current EffectiveUserInterfaceLayoutDirection

Cross-Pollination

Model Round New Ideas? Details
claude-opus-4.6 2 No NO NEW IDEAS
claude-sonnet-4.6 2 No NO NEW IDEAS
gpt-5.3-codex 2 No NO NEW IDEAS
gpt-5.1 2 No NO NEW IDEAS

Exhausted: Yes
Selected Fix: Attempt 1 (claude-opus-4.6) — Self-contained orientation tracking in CrossPlatformArrange. Strictly better than PR's fix: 1 file (vs 2), fixes the condition bug (checks current EffectiveUserInterfaceLayoutDirection not stale _previous), and no handler-side lifecycle dependency.


📋 Report — Final Recommendation

⚠️ Final Recommendation: REQUEST CHANGES

Phase Status

Phase Status Notes
Pre-Flight ✅ COMPLETE Issue #32271, iOS only, 2 impl + 2 test + 3 snapshot files
Gate ❌ FAILED iOS — test passed WITHOUT fix (should have failed)
Try-Fix ✅ COMPLETE 4 attempts, all 4 passing; better alternative found
Report ✅ COMPLETE

Summary

PR #32529 addresses a real iOS bug: ScrollView with FlowDirection = RightToLeft fails to apply the correct RTL scroll offset when the Orientation property is changed at runtime (Vertical → Horizontal). The core fix logic is sound in concept but has two issues that need to be addressed before merging: a code correctness bug in the condition check, and a test that doesn't actually validate the bug is fixed.

Root Cause

MauiScrollView.CrossPlatformArrange() applies the RTL start scroll offset (ContentOffset = (contentSize.Width - bounds.Width, 0)) only when isDirectionChange = true (i.e., _previousEffectiveUserInterfaceLayoutDirection != EffectiveUserInterfaceLayoutDirection). After the initial layout sets _previous = RTL, all subsequent calls see RTL == RTLisDirectionChange = false. When orientation changes at runtime, no direction change occurs, so the RTL offset is never re-applied.

Fix Quality

The PR's fix is directionally correct — resetting _previous to null forces isDirectionChange = true on the next layout pass, causing the RTL offset to fire. However it has two concrete issues:

Issue 1 — Code bug in OnOrientationChanged condition:

// PR's fix — WRONG: checks cached _previous instead of current direction
if (_previousEffectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirection.RightToLeft)

// Correct: check current effective direction
if (EffectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirection.RightToLeft)

If _previousEffectiveUserInterfaceLayoutDirection is null (view never arranged while RTL, or was reset), the condition is false and no re-layout fires. This is the exact scenario highlighted by the prior Copilot reviewer.

Issue 2 — Gate FAILED (test doesn't catch the bug):
The test (VerifyScrollViewDirection) uses VerifyScreenshot() which compares against a snapshot file added by this PR. The snapshot was likely captured with the fix applied (showing correct behavior) OR the visual difference between correct and incorrect RTL scroll position is too small to detect in a single-frame screenshot. Either way, the test passed without the fix, meaning it cannot reliably gate this bug.

Issue 3 — Unnecessary handler changes:
The PR adds code to ScrollViewHandler.iOS.cs (MapOrientation) to call OnOrientationChanged(). Try-Fix Attempt 1 shows the fix can be made fully self-contained in MauiScrollView.cs alone, requiring zero handler changes.

Better Alternative (Try-Fix Attempt 1 ✅)

Instead of adding an OnOrientationChanged() method and calling it from the handler:

// In MauiScrollView.cs — add field:
ScrollOrientation? _previousScrollOrientation;

// In CrossPlatformArrange, before isDirectionChange check:
if (View is IScrollView scrollViewForOrientation)
{
    var currentOrientation = scrollViewForOrientation.Orientation;
    if (_previousScrollOrientation.HasValue 
        && _previousScrollOrientation.Value != currentOrientation
        && EffectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirection.RightToLeft)
    {
        _previousEffectiveUserInterfaceLayoutDirection = null;
    }
    _previousScrollOrientation = currentOrientation;
}

Advantages over PR's fix:

  • 1 file changed (MauiScrollView.cs) vs 2 files
  • Fixes the condition bug (checks current EffectiveUserInterfaceLayoutDirection, not stale _previous)
  • Self-contained in layout code — no IsLoaded() lifecycle guard needed
  • No SetNeedsLayout()/LayoutIfNeeded() in OnOrientationChanged (the arrange is already being called)

Required Changes

  1. Fix the condition in OnOrientationChanged (or adopt Try-Fix Attempt 1's approach):

    • Change _previousEffectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirection.RightToLeft
    • To EffectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirection.RightToLeft
  2. Fix the test snapshot:

    • The VerifyScrollViewDirection snapshot must represent the correct RTL behavior (scroll position at right end after orientation toggle + scroll)
    • Consider adding a programmatic position assertion (e.g., verify content is scrolled correctly) in addition to or instead of pure screenshot comparison so the test gates the fix properly
  3. Remove redundant handler changes (optional but cleaner):

    • If the orientation-tracking approach is adopted, ScrollViewHandler.iOS.cs changes can be removed entirely

@MauiBot MauiBot added the s/agent-fix-win AI found a better alternative fix than the PR label Mar 29, 2026
@MauiBot MauiBot removed the s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates label Mar 29, 2026
Copy link
Copy Markdown
Contributor

@kubaflo kubaflo left a comment

Choose a reason for hiding this comment

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

Could you please add snapshot?

@sheiksyedm
Copy link
Copy Markdown
Contributor

/azp run maui-pr-uitests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

@sheiksyedm
Copy link
Copy Markdown
Contributor

/azp run maui-pr-uitests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

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

Labels

community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration s/agent-changes-requested AI agent recommends changes - found a better alternative or issues 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.

ScrollView with RTL FlowDirection and Horizontal Orientation scrolls in the wrong direction on iOS

7 participants