Skip to content

[iOS & MacCatalyst] Fixed Flowdirection in Stepper#34005

Merged
kubaflo merged 5 commits intodotnet:inflight/currentfrom
SubhikshaSf4851:Fix-29704
Mar 16, 2026
Merged

[iOS & MacCatalyst] Fixed Flowdirection in Stepper#34005
kubaflo merged 5 commits intodotnet:inflight/currentfrom
SubhikshaSf4851:Fix-29704

Conversation

@SubhikshaSf4851
Copy link
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!

Root Cause:

UIStepper did not update its semantic content attribute or visual orientation when the Flow direction changed, leading to incorrect rendering in RTL scenarios.

Description of Change

  • Added mapping for FlowDirection in StepperHandler for iOS and MacCatalyst, allowing the handler to respond to flow direction changes.
  • Implemented MapFlowDirection method in StepperHandler.iOS.cs to update the platform view's flow direction.
  • Introduced UpdateFlowDirection extension method for UIStepper in StepperExtensions.cs, applying semantic content attributes and visual transforms to both the stepper and its subviews based on flow direction and parent layout.

Issues Fixed

Fixes #29704

Tested the behavior in the following platforms

  • Windows
  • Android
  • iOS
  • Mac
Before Issue Fix After Issue Fix
BeforeFix29704.mov
AfterFix18Version29704.mov

@dotnet-policy-service dotnet-policy-service bot added the community ✨ Community Contribution label Feb 12, 2026
@dotnet-policy-service
Copy link
Contributor

Hey there @@SubhikshaSf4851! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

@dotnet-policy-service dotnet-policy-service bot added the partner/syncfusion Issues / PR's with Syncfusion collaboration label Feb 12, 2026
@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-fix-lose Author adopted the agent's fix and it turned out to be bad s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels Feb 18, 2026
@sheiksyedm sheiksyedm marked this pull request as ready for review February 18, 2026 11:35
Copilot AI review requested due to automatic review settings February 18, 2026 11:35
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes RTL FlowDirection handling for Stepper on iOS and MacCatalyst by ensuring the native UIStepper updates its semantic direction (and applies an additional mirroring transform for iOS 26+ behavior changes), and enables the previously disabled screenshot test for this scenario.

Changes:

  • Added an iOS/MacCatalyst FlowDirection mapper override for StepperHandler.
  • Implemented UIStepper.UpdateFlowDirection to update SemanticContentAttribute and (for iOS 26+) apply a horizontal transform to the stepper and its subviews.
  • Re-enabled the Stepper_ChangeFlowDirection_RTL_VerifyVisualState screenshot test and added a new iOS baseline image.

Reviewed changes

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

Show a summary per file
File Description
src/Core/src/Platform/iOS/StepperExtensions.cs Adds UpdateFlowDirection behavior for UIStepper, including iOS 26+ mirroring transform logic.
src/Core/src/Handlers/Stepper/StepperHandler.iOS.cs Introduces MapFlowDirection to call the new platform extension method.
src/Core/src/Handlers/Stepper/StepperHandler.cs Hooks FlowDirection into the handler mapper for iOS/MacCatalyst builds.
src/Controls/tests/TestCases.iOS.Tests/snapshots/ios/Stepper_ChangeFlowDirection_RTL_VerifyVisualState.png Adds an iOS screenshot baseline for the newly-enabled RTL flow direction test.
src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/StepperFeatureTests.cs Removes the iOS/Catalyst disable guard so the RTL screenshot test runs.

@SubhikshaSf4851
Copy link
Contributor Author

🤖 AI Summary

📊 Expand Full Review
📋 Expand PR Finalization Review

I’ve updated the changes based on the summary. Specifically:

  • Updated indentation to match the existing codebase
  • Added a comment explaining the specific iOS 26 rendering change
  • Removed the unnecessary else after return
  • Added a missing newline at the end of the file

@kubaflo kubaflo added the s/agent-suggestions-implemented Maintainer applies when PR author adopts agent's recommendation label Feb 18, 2026
kubaflo
kubaflo previously approved these changes Feb 18, 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
@github-actions
Copy link
Contributor

github-actions bot commented Mar 4, 2026

🚀 Dogfood this PR with:

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

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

Or

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

@kubaflo kubaflo changed the base branch from main to inflight/current March 16, 2026 00:41
@kubaflo kubaflo changed the base branch from inflight/current to main March 16, 2026 00:42
@dotnet dotnet deleted a comment from rmarinho Mar 16, 2026
@kubaflo
Copy link
Contributor

kubaflo commented Mar 16, 2026

🤖 AI Summary

📊 Expand Full Review3033570 · Updated suggestion
🔍 Pre-Flight — Context & Validation

Issue: #29704 - Stepper Ignores RightToLeft FlowDirection on iOS and macOS
PR: #34005 - [iOS & MacCatalyst] Fixed Flowdirection in Stepper
Platforms Affected: iOS, MacCatalyst
Files Changed: 4 implementation/handler files, 1 test file, 2 snapshot images

Key Findings

  • Issue Stepper Ignores RightToLeft FlowDirection on iOS and macOS #29704: UIStepper renders LTR even when FlowDirection = RightToLeft; Android/Windows work correctly
  • PR adds FlowDirection to StepperHandler property mapper for __IOS__ || MACCATALYST
  • UpdateFlowDirection sets SemanticContentAttribute on stepper + subviews, and applies CGAffineTransform.MakeScale(-1, 1) on iOS 26+ where SemanticContentAttribute alone is insufficient
  • Test guard #if TEST_FAILS_ON_IOS && TEST_FAILS_ON_CATALYST removed — test now active on iOS/Catalyst
  • Snapshot baselines added for ios/ and ios-26/ but NOT for mac/
  • Outstanding issue 1: isIOS26 check uses OperatingSystem.IsIOSVersionAtLeast(26) only — missing IsMacCatalystVersionAtLeast(26) (review comment marked "resolved" but not implemented)
  • Outstanding issue 2: GetParentSemanticContentAttribute only checks immediate parent — if parent is MatchParent but ancestor is RTL, stepper won't mirror (review comment marked "resolved" but not implemented)
  • PR previously had s/agent-gate-failed — gate failed in a prior review cycle

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #34005 SemanticContentAttribute + CGAffineTransform (iOS 26+) ⏳ PENDING (Gate) StepperHandler.cs, StepperHandler.iOS.cs, StepperExtensions.cs Missing MacCatalyst check + shallow parent walk

Issue: #29704 - Stepper Ignores RightToLeft FlowDirection on iOS and macOS
PR: #34005 - [iOS & MacCatalyst] Fixed Flowdirection in Stepper
Platforms Affected: iOS, MacCatalyst
Files Changed: 3 implementation files, 1 test file, 2 snapshot images

Key Findings

  • Issue Stepper Ignores RightToLeft FlowDirection on iOS and macOS #29704: UIStepper always renders LTR even when FlowDirection = RightToLeft; Android/Windows work correctly
  • PR adds FlowDirection to StepperHandler property mapper for __IOS__ || MACCATALYST
  • UpdateFlowDirection sets SemanticContentAttribute on stepper + subviews; on iOS/MacCatalyst 26+ also applies CGAffineTransform.MakeScale(-1, 1) because SemanticContentAttribute alone is no longer sufficient
  • The test guard #if TEST_FAILS_ON_IOS && TEST_FAILS_ON_CATALYST was removed — Stepper_ChangeFlowDirection_RTL_VerifyVisualState is now active on iOS and Catalyst
  • Snapshot baselines added: ios/ and ios-26/no mac/ baseline (MacCatalyst tests may fail on Mac runner without a baseline)
  • MapFlowDirection is internal with a // TODO: Make public for .NET 11. comment — correct approach for phased API
  • Prior agent review (Copilot, ~2026-03-15) recommended REQUEST CHANGES. Two bugs were identified:
    1. isIOS26 check was missing IsMacCatalystVersionAtLeast(26)FIXED in current code
    2. GetParentSemanticContentAttribute only checks immediate parent (shallow walk) — if parent is MatchParent but an ancestor is RTL, the stepper won't mirror → STILL PRESENT ⚠️
  • Prior gate PASSED on iOS (iPhone 11 Pro sim, iossimulator-arm64) at commit 8044693; current code is further updated (added ios-26/ snapshot, IsMacCatalystVersionAtLeast(26) fix)
  • Author (SubhikshaSf4851) is a Syncfusion partner contributor

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #34005 SemanticContentAttribute + CGAffineTransform (iOS/MacCatalyst 26+); new UpdateFlowDirection extension ⏳ PENDING (Gate) StepperHandler.cs, StepperHandler.iOS.cs, StepperExtensions.cs Shallow parent walk (immediate parent only); MacCatalyst 26+ check now fixed

🚦 Gate — Test Verification

Gate Phase: Test Verification (verify-tests-fail-without-fix)

Date: 2026-03-15 18:22:53 | Platform: iOS | PR: #34005

✅ VERIFICATION PASSED

The test Stepper_ChangeFlowDirection_RTL_VerifyVisualState correctly validates the fix for PR #34005 (Stepper FlowDirection fix for iOS/MacCatalyst).

Summary

Check Expected Actual Result
Tests WITH fix (current state) PASS PASS (1/1 passed)
Tests WITHOUT fix (fix files reverted) FAIL FAIL (1/1 failed)

Verdict:VERIFICATION PASSED — Tests correctly detect the bug when fix is absent.

Test Details

Test method: Stepper_ChangeFlowDirection_RTL_VerifyVisualState
Test class: StepperFeatureTests
Filter: Stepper_ChangeFlowDirection_RTL_VerifyVisualState
Platform: iOS (iPhone 11 Pro simulator, iossimulator-arm64)

Run 1 — WITH fix (PR's code applied):

  • Total: 1 | Passed: 1 | Failed: 0
  • Result: ✅ PASSED (bug is fixed)

Run 2 — WITHOUT fix (fix files reverted to merge-base f6314b55):

  • Total: 1 | Passed: 0 | Failed: 1
  • Result: ✅ FAILED (bug is present — screenshot mismatch for RTL FlowDirection)

Fix Files Reverted for "Without Fix" Run

The following platform code files were reverted to merge-base state. The test file was intentionally not reverted (to ensure the test runs on iOS in both cases):

  • src/Core/src/Handlers/Stepper/StepperHandler.cs — FlowDirection mapper entry
  • src/Core/src/Handlers/Stepper/StepperHandler.iOS.csMapFlowDirection method
  • src/Core/src/Platform/iOS/StepperExtensions.csUpdateFlowDirection extension

Note: The test file StepperFeatureTests.cs removed a #if TEST_FAILS_ON_IOS && TEST_FAILS_ON_CATALYST guard. If reverted, the test would be skipped on iOS (not failed), so we kept the test file at HEAD to ensure the test actually runs in both scenarios.

Conclusion

The PR's fix correctly resolves the Stepper FlowDirection RTL rendering issue on iOS:

  • The VerifyScreenshot call in the test captures the visual state of the Stepper
  • Without fix: The Stepper renders incorrectly in RTL mode → screenshot mismatch → test FAILS
  • With fix: The Stepper correctly applies FlowDirection → screenshot matches → test PASSES

The test properly validates the fix. ✅

Logs

  • Verification log: CustomAgentLogsTmp/PRState/34005/PRAgent/gate/verify-tests-fail/verification-log.txt
  • Test output (with fix): CustomAgentLogsTmp/PRState/34005/PRAgent/gate/verify-tests-fail/test-output-with-fix.log
  • Test output (without fix): CustomAgentLogsTmp/PRState/34005/PRAgent/gate/verify-tests-fail/test-output-without-fix.log
  • UI test logs: CustomAgentLogsTmp/UITests/

Gate Result: ✅ PASSED

Platform: ios
Mode: Full Verification

  • Tests FAIL without fix: ✅
  • Tests PASS with fix: ✅

Summary

The verify-tests-fail-without-fix skill successfully validated that the Stepper FlowDirection RTL fix on iOS/MacCatalyst works correctly.

Verification Results

Test Scenario Expected Actual Status
Without Fix Tests FAIL Tests FAILED
With Fix Tests PASS Tests PASSED

Details

Tests FAIL without fix (bug is present)

  • Reverted fix files to merge-base (f6314b5)
  • Ran: Stepper_ChangeFlowDirection_RTL_VerifyVisualState
  • Result: Test correctly detected the missing fix

Tests PASS with fix (bug is fixed)

  • Restored fix files from HEAD
  • Ran: Stepper_ChangeFlowDirection_RTL_VerifyVisualState
  • Result: Test validated the fix works correctly

Fix Files Validated

  1. src/Core/src/Handlers/Stepper/StepperHandler.cs - Adds FlowDirection mapper entry for iOS/MacCatalyst
  2. src/Core/src/Handlers/Stepper/StepperHandler.iOS.cs - Adds MapFlowDirection method
  3. src/Core/src/Platform/iOS/StepperExtensions.cs - Adds UpdateFlowDirection extension
  4. eng/pipelines/ci-copilot.yml - Pipeline configuration
  5. eng/pipelines/common/provision.yml - Provisioning configuration

Test File

Location: src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/StepperFeatureTests.cs
Method: Stepper_ChangeFlowDirection_RTL_VerifyVisualState

Conclusion

The test properly reproduces the Stepper FlowDirection RTL rendering bug and validates that the fix resolves it. The fix implementation correctly handles RTL (right-to-left) text direction on iOS/MacCatalyst platforms.

Gate Status:PASSED - PR can proceed to next stages.


🔧 Fix — Analysis & Comparison

Gate Phase: Test Verification (verify-tests-fail-without-fix)

Date: 2026-03-15 18:22:53 | Platform: iOS | PR: #34005

✅ VERIFICATION PASSED

The test Stepper_ChangeFlowDirection_RTL_VerifyVisualState correctly validates the fix for PR #34005 (Stepper FlowDirection fix for iOS/MacCatalyst).

Summary

Check Expected Actual Result
Tests WITH fix (current state) PASS PASS (1/1 passed)
Tests WITHOUT fix (fix files reverted) FAIL FAIL (1/1 failed)

Verdict:VERIFICATION PASSED — Tests correctly detect the bug when fix is absent.

Test Details

Test method: Stepper_ChangeFlowDirection_RTL_VerifyVisualState
Test class: StepperFeatureTests
Filter: Stepper_ChangeFlowDirection_RTL_VerifyVisualState
Platform: iOS (iPhone 11 Pro simulator, iossimulator-arm64)

Run 1 — WITH fix (PR's code applied):

  • Total: 1 | Passed: 1 | Failed: 0
  • Result: ✅ PASSED (bug is fixed)

Run 2 — WITHOUT fix (fix files reverted to merge-base f6314b55):

  • Total: 1 | Passed: 0 | Failed: 1
  • Result: ✅ FAILED (bug is present — screenshot mismatch for RTL FlowDirection)

Fix Files Reverted for "Without Fix" Run

The following platform code files were reverted to merge-base state. The test file was intentionally not reverted (to ensure the test runs on iOS in both cases):

  • src/Core/src/Handlers/Stepper/StepperHandler.cs — FlowDirection mapper entry
  • src/Core/src/Handlers/Stepper/StepperHandler.iOS.csMapFlowDirection method
  • src/Core/src/Platform/iOS/StepperExtensions.csUpdateFlowDirection extension

Note: The test file StepperFeatureTests.cs removed a #if TEST_FAILS_ON_IOS && TEST_FAILS_ON_CATALYST guard. If reverted, the test would be skipped on iOS (not failed), so we kept the test file at HEAD to ensure the test actually runs in both scenarios.

Conclusion

The PR's fix correctly resolves the Stepper FlowDirection RTL rendering issue on iOS:

  • The VerifyScreenshot call in the test captures the visual state of the Stepper
  • Without fix: The Stepper renders incorrectly in RTL mode → screenshot mismatch → test FAILS
  • With fix: The Stepper correctly applies FlowDirection → screenshot matches → test PASSES

The test properly validates the fix. ✅

Logs

  • Verification log: CustomAgentLogsTmp/PRState/34005/PRAgent/gate/verify-tests-fail/verification-log.txt
  • Test output (with fix): CustomAgentLogsTmp/PRState/34005/PRAgent/gate/verify-tests-fail/test-output-with-fix.log
  • Test output (without fix): CustomAgentLogsTmp/PRState/34005/PRAgent/gate/verify-tests-fail/test-output-without-fix.log
  • UI test logs: CustomAgentLogsTmp/UITests/

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix (claude-opus-4.6-1m) Delegate to ViewHandler.MapFlowDirection + use UIView.EffectiveUserInterfaceLayoutDirection for transform decisions (no custom parent walk code) ✅ PASS 3 files Eliminates all 4 helper methods from PR; uses UIKit's built-in direction resolution; MacCatalyst 26+ covered
2 try-fix (claude-sonnet-4.6) Fix shallow parent walk (full ancestor chain walk) + derive transform from resolved attribute (eliminates redundant second parent-walk) ✅ PASS 3 files Keeps PR's structure but fixes depth of parent walk; simpler transform logic
3 try-fix (gpt-5.3-codex) Custom UIStepper subclass with MovedToWindow/LayoutSubviews override + ConnectHandler RTL reapply ❌ FAIL 3 files Screenshot diff 0.66% — lifecycle timing not sufficient; subclass approach didn't correct visual mirror
4 try-fix (gemini-3-pro-preview) Custom MauiStepper subclass with LayoutSubviews override + CustomTransform property; full ancestor walk fix ❌ FAIL 3 files 0.67% diff — UIStepper ignores Transform on iOS 16 sim; rendering bypasses standard UIView transform logic
PR PR #34005 SemanticContentAttribute + CGAffineTransform (iOS/MacCatalyst 26+); new UpdateFlowDirection extension ✅ PASS (Gate) 3 files Shallow parent walk (immediate parent only); MacCatalyst 26+ now fixed

Cross-Pollination

Model Round New Ideas? Details
claude-opus-4.6-1m 2 NO NEW IDEAS Solution space covered: delegation + EffectiveUserInterfaceLayoutDirection and full ancestor chain walk cover all viable approaches
claude-sonnet-4.6 2 NO NEW IDEAS Noted Unspecified+UIKit-inheritance angle would be equivalent to Attempt 1 in practice
gpt-5.3-codex 2 Minor variation Apply transform to subviews on ALL iOS versions (not just 26+) — too close to existing passing approaches
gemini-3-pro-preview 2 Minor variation Container UIView wrapper for transform — architectural risk, not necessary given passing approaches

Exhausted: Yes — 2/4 models explicitly say NO NEW IDEAS; 2/4 propose minor variations already covered by Attempts 1-2

Selected Fix: Attempt 2 (claude-sonnet-4.6) — Fix shallow parent walk (full ancestor chain) + derive transform from resolved SemanticContentAttribute

Reason preferred over PR:

  1. Fixes the shallow parent walk bug — MatchParent FlowDirection now correctly inherits from any ancestor (not just immediate parent)
  2. Eliminates redundant second parent-walk (GetParentTransform) by reusing the already-resolved UISemanticContentAttribute
  3. Keeps PR's structure and pattern — minimal diff, easier to review
  4. More debuggable than Attempt 1 (explicit virtual hierarchy walk visible in code)

Reason preferred over Attempt 1:

  • No dependency on ViewHandler.MapFlowDirection sequencing
  • Simpler: no intermediate delegation step
  • Keeps pattern consistent with how PR was written (explicit virtual view hierarchy management)

📋 Report — Final Recommendation

⚠️ Final Recommendation: REQUEST CHANGES

Phase Status

Phase Status Notes
Pre-Flight ✅ COMPLETE Issue #29704, 6 files changed
Gate ✅ PASSED iOS, iPhone 11 Pro simulator
Try-Fix ✅ COMPLETE 4 attempts, 3 passing (1 fail)
Report ✅ COMPLETE

Result: ✅ PASSED
Selected Fix: Attempt 1 (alternative — delegate-and-supplement via ViewHandler)

Summary

PR #34005 fixes a real bug (Stepper ignores RTL FlowDirection on iOS/macOS) and the fix works in the Gate test. However, the implementation has two concrete bugs identified in prior review and not yet addressed, and try-fix found a cleaner alternative that avoids both bugs entirely. Changes are requested to resolve the bugs or adopt the alternative approach.

Root Cause

UIStepper does not respond to RTL FlowDirection because:

  1. StepperHandler had no mapper entry for FlowDirection on iOS/MacCatalyst — changes were never applied.
  2. Even when setting SemanticContentAttribute, iOS 26+ (Liquid Glass) requires an explicit CGAffineTransform.MakeScale(-1, 1) to visually flip the control.

Fix Quality

What the PR does well:

  • Correctly identifies the two-step solution: SemanticContentAttribute + CGAffineTransform for iOS 26+
  • Properly propagates attributes to UIStepper's internal subviews
  • Removes the #if TEST_FAILS_ON_IOS && TEST_FAILS_ON_CATALYST test guard
  • Adds snapshot baselines for ios/ and ios-26/
  • Gate: tests FAIL without fix, PASS with fix ✅

Two bugs that must be fixed:

Bug 1: GetParentSemanticContentAttribute only checks the immediate parent

// PR's current code — SHALLOW (one level only):
static UISemanticContentAttribute GetParentSemanticContentAttribute(IStepper stepper)
{
    var parentView = (stepper as IView)?.Parent as IView;
    if (parentView is null) return UISemanticContentAttribute.Unspecified;
    return parentView.FlowDirection switch { ... _ => UISemanticContentAttribute.Unspecified };
    //                                                  ^^^^^^^^^^^^^^ stops here if parent is MatchParent
}

If the immediate parent has FlowDirection.MatchParent but a grandparent/ancestor is RTL, the stepper will render LTR (wrong). The prior Copilot review comment on this was marked "resolved" but the fix was not applied.

Bug 2: isIOS26 check excludes MacCatalyst

// PR's current code — MISSING MacCatalyst check:
bool isIOS26 = OperatingSystem.IsIOSVersionAtLeast(26);
// Should be:
bool isIOS26 = OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26);

On macOS 26 (Liquid Glass), the same UIKit rendering change applies to MacCatalyst. Without this check, the stepper won't visually flip on MacCatalyst 26+. The prior Copilot review comment on this was also marked "resolved" but not applied.

Missing Mac snapshot:
The test Stepper_ChangeFlowDirection_RTL_VerifyVisualState now runs on MacCatalyst (the #if guard was removed), but no baseline snapshot exists under TestCases.Mac.Tests/snapshots/mac/. This will cause screenshot comparison failures on MacCatalyst CI.

Better Alternative Found (Try-Fix Attempt 1)

Try-fix found a cleaner approach that avoids both bugs:

// StepperHandler.iOS.cs
internal static void MapFlowDirection(IStepperHandler handler, IStepper stepper)
{
    // Delegates to ViewHandler.MapFlowDirection → ViewExtensions.UpdateFlowDirection
    // which already walks the FULL ancestor chain for MatchParent resolution
    ViewHandler.MapFlowDirection(handler, stepper);

    // Supplement with stepper-specific subview propagation + iOS/MacCatalyst 26+ transform
    handler.PlatformView?.ApplyStepperRTLTransform(stepper);
}
// StepperExtensions.cs — reads SemanticContentAttribute already set by ViewHandler,
// no need to re-implement parent resolution:
internal static void ApplyStepperRTLTransform(this UIStepper platformStepper, IStepper stepper)
{
    var contentAttribute = platformStepper.SemanticContentAttribute;
    bool needsTransform = OperatingSystem.IsIOSVersionAtLeast(26)
        || OperatingSystem.IsMacCatalystVersionAtLeast(26);  // ✅ includes MacCatalyst
    bool isRTL = contentAttribute == UISemanticContentAttribute.ForceRightToLeft;
    CGAffineTransform transform = isRTL
        ? CGAffineTransform.MakeScale(-1, 1)
        : CGAffineTransform.MakeIdentity();
    if (needsTransform)
        platformStepper.Transform = transform;
    foreach (var subview in platformStepper.Subviews)
    {
        subview.SemanticContentAttribute = contentAttribute;
        if (needsTransform)
            subview.Transform = transform;
    }
}
Aspect PR's Fix Attempt 1 (Better)
Parent chain walk ❌ Immediate parent only ✅ Full ancestor chain (via ViewHandler)
MacCatalyst 26+ ❌ Missing ✅ Included
Code size ~70 lines ~28 supplementary lines
Code reuse ❌ Re-implements existing logic ✅ Delegates to existing infra
Test result ✅ PASS (Gate) ✅ PASS (try-fix)

Required Changes

  1. Fix shallow parent walk — either implement recursive ancestor walk in GetParentSemanticContentAttribute or adopt Attempt 1's delegation approach (which gets this for free via ViewHandler.MapFlowDirection)

  2. Add IsMacCatalystVersionAtLeast(26) checkbool isIOS26 = OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26);

  3. Add Mac snapshot baseline — add Stepper_ChangeFlowDirection_RTL_VerifyVisualState.png to src/Controls/tests/TestCases.Mac.Tests/snapshots/mac/ or gate the test on Mac

Try-Fix Summary

# Model Approach Result
1 claude-opus-4.6-1m Delegate to ViewHandler + supplement subviews + MacCatalyst 26+ ✅ PASS
2 claude-sonnet-4.6 Same delegation + recursive subview deep-walk ✅ PASS
3 gpt-5.3-codex ViewHandler + EffectiveUserInterfaceLayoutDirection (no extension) ❌ FAIL (0.66% diff)
4 gemini-3-pro-preview Minimal fix to PR bugs: recursive parent walk + MacCatalyst 26+ ✅ PASS

⚠️ Final Recommendation: REQUEST CHANGES

Phase Status

Phase Status Notes
Pre-Flight ✅ COMPLETE Issue #29704, 6 files changed, prior agent review found 2 bugs (1 fixed, 1 remains)
Gate ✅ PASSED iOS, Stepper_ChangeFlowDirection_RTL_VerifyVisualState FAIL without fix / PASS with fix
Try-Fix ✅ COMPLETE 4 attempts: 2 passing, 2 failing; solution space exhausted
Report ✅ COMPLETE

Result: ✅ PASSED (Gate)
Selected Fix: Attempt 2 (alternative — fix shallow parent walk + derive transform from resolved attribute)


Summary

PR #34005 fixes a real and verified bug: Stepper ignores RightToLeft FlowDirection on iOS/MacCatalyst (issue #29704). The fix is functionally correct for the common case (explicit FlowDirection = RightToLeft on the Stepper) and the gate passes. However, one known bug from the prior agent review remains unaddressed: GetParentSemanticContentAttribute only checks the immediate parent (shallow walk). When FlowDirection = MatchParent and the RTL direction comes from a grandparent or higher ancestor, the Stepper will not mirror correctly.

Try-Fix found a minimal, targeted alternative (Attempt 2) that fixes this by walking the full ancestor chain and simplifying the transform logic.


Root Cause

UIStepper does not respond to layout direction changes automatically. The fix requires:

  1. Setting SemanticContentAttribute to ForceRightToLeft on the stepper and its internal subviews
  2. On iOS/MacCatalyst 26+ (where SemanticContentAttribute alone is no longer sufficient): additionally applying CGAffineTransform.MakeScale(-1, 1)

The PR implements both, but the parent resolution for FlowDirection.MatchParent only looks one level up.


Fix Quality

What the PR gets right:

  • ✅ Maps FlowDirection in the property mapper (iOS/MacCatalyst only — correct)
  • MapFlowDirection is internal with // TODO: Make public for .NET 11. — correct phased API approach
  • ✅ Sets SemanticContentAttribute on stepper AND subviews
  • ✅ Adds CGAffineTransform for iOS/MacCatalyst 26+
  • isIOS26 check now includes IsMacCatalystVersionAtLeast(26) — prior agent bug fixed ✅
  • ✅ Test guard removed; snapshot baselines added for ios/ and ios-26/
  • ✅ Gate passes: test correctly detects bug / fix

What needs improvement:

  1. Shallow parent walk bug (unresolved)GetParentSemanticContentAttribute only checks (stepper as IView)?.Parent as IView and then stops. If the immediate parent has FlowDirection.MatchParent, the function returns Unspecified even if a grandparent is RTL:

    // Current (BUG): only checks immediate parent
    var parentView = (stepper as IView)?.Parent as IView;
    if (parentView is null) return UISemanticContentAttribute.Unspecified;
    return parentView.FlowDirection switch { ... };
    
    // Fix: walk full ancestor chain
    var current = (stepper as IView)?.Parent as IView;
    while (current is not null) {
        if (current.FlowDirection == FlowDirection.LeftToRight) return ForceLeftToRight;
        if (current.FlowDirection == FlowDirection.RightToLeft) return ForceRightToLeft;
        current = current.Parent as IView;
    }
    return UISemanticContentAttribute.Unspecified;
  2. Redundant double parent-walk for transformGetCGAffineTransform calls GetParentTransform which calls GetParentSemanticContentAttribute again. Since the resolved UISemanticContentAttribute is already available, the transform can be derived directly:

    // Simpler: derive transform from already-resolved attribute
    CGAffineTransform transform = contentAttribute == UISemanticContentAttribute.ForceRightToLeft
        ? CGAffineTransform.MakeScale(-1, 1)
        : CGAffineTransform.MakeIdentity();
  3. No mac/ snapshot baselineTestCases.Mac.Tests/snapshots/mac/ has no baseline for Stepper_ChangeFlowDirection_RTL_VerifyVisualState. If the test runs on macOS CI, it will fail due to missing baseline image.


Recommended Changes

  1. Fix GetParentSemanticContentAttribute to walk the full ancestor chain (see code above)
  2. Simplify GetCGAffineTransform/GetParentTransform by deriving transform from resolved attribute
  3. Add mac/ snapshot baseline (or gate test on TEST_FAILS_ON_CATALYST until baseline is added)
  4. Consider collapsing the 4 helper methods (GetSemanticContentAttribute, GetParentSemanticContentAttribute, GetCGAffineTransform, GetParentTransform) into a simpler structure

Try-Fix Summary

# Model Approach Result
1 claude-opus-4.6-1m ViewHandler delegation + EffectiveUserInterfaceLayoutDirection ✅ PASS
2 claude-sonnet-4.6 Full ancestor chain walk + derive transform from resolved attribute ✅ PASS
3 gpt-5.3-codex MauiStepper subclass with MovedToWindow/LayoutSubviews override ❌ FAIL
4 gemini-3-pro-preview MauiStepper subclass + CustomTransform + LayoutSubviews ❌ FAIL

📋 Expand PR Finalization Review

PR #34005 Finalization Review

PR: #34005 - [iOS & MacCatalyst] Fixed Flowdirection in Stepper
Author: @SubhikshaSf4851 (Syncfusion partner)
Fixes: #29704 — Stepper ignores RightToLeft FlowDirection on iOS and macOS


Phase 1: Title & Description

🟡 Title: Needs Minor Update

Current: [iOS & MacCatalyst] Fixed Flowdirection in Stepper

Issues:

  • Flowdirection has incorrect casing — should be FlowDirection
  • Past tense Fixed is less conventional (prefer present tense imperative)
  • Missing description of what changed (not just "fixed")

Recommended:

[iOS & MacCatalyst] Stepper: Fix FlowDirection RTL not rendering correctly

🟡 Description: Good Structure, Needs Enhancements

Quality Assessment:

Aspect Status Notes
NOTE block ✅ Present Correct at top
Root Cause ✅ Present Clearly stated
Description of Change ✅ Present File-by-file breakdown
Issues Fixed ✅ Present Links to #29704
Platforms Tested ⚠️ Misleading Checks Windows/Android but fix is iOS/Mac-only
iOS 26 workaround ❌ Missing Key technical detail omitted
Screenshots ❌ Empty Table cells empty, no before/after images

Action: Keep existing structure, add the iOS 26 callout and clarify the platforms tested.

Recommended Description:

<!-- Please let the below note in for people that find this PR -->
> [!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!

### Root Cause

`UIStepper` did not update its `SemanticContentAttribute` or visual orientation when the `FlowDirection` property changed, because `StepperHandler` had no mapper registered for `FlowDirection` on iOS/MacCatalyst. As a result, the control always rendered left-to-right regardless of the setting.

### Description of Change

* **`StepperHandler.cs`** — Added `FlowDirection` mapper registration for `__IOS__` and `MACCATALYST` platforms.
* **`StepperHandler.iOS.cs`** — Added `internal MapFlowDirection` method (marked `// TODO: Make public for .NET 11`) that delegates to the new extension.
* **`StepperExtensions.cs`** — Added `UpdateFlowDirection` extension method on `UIStepper`. Sets `UISemanticContentAttribute` and propagates it to subviews. Includes an iOS 26+ workaround: starting with iOS 26 / MacCatalyst 26, `SemanticContentAttribute` alone is insufficient — a horizontal `CGAffineTransform` (`MakeScale(-1, 1)`) must also be applied to the stepper and counter-applied to subviews so that button order reverses while the `+`/`` symbols remain readable.
* **`StepperFeatureTests.cs`** — Removed `#if TEST_FAILS_ON_IOS && TEST_FAILS_ON_CATALYST` guard (was tracking #29704) to re-enable the RTL screenshot test on iOS and MacCatalyst.
* **Snapshots** — Added reference screenshots for `ios` and `ios-26` buckets.

### Key Technical Detail: iOS 26+ Behavior

iOS 26 changed `UIStepper`'s internal rendering pipeline so that setting `SemanticContentAttribute` alone no longer mirrors the control. This PR applies:

1. `platformStepper.Transform = CGAffineTransform.MakeScale(-1, 1)` — flips the whole stepper container horizontally (reverses button order).
2. `subview.Transform = CGAffineTransform.MakeScale(-1, 1)` — counter-flips each subview back (keeps `+`/`` labels oriented correctly).

On iOS < 26, only `SemanticContentAttribute` is set (no transform needed).

### Issues Fixed

Fixes #29704

### Platforms Tested (by fix scope)

- [x] iOS ✅ (primary fix)
- [x] MacCatalyst ✅ (primary fix)
- [ ] Android (no change — already worked)
- [ ] Windows (no change — already worked)

Phase 2: Code Review

✅ Looks Good

  • Platform-guard pattern#elif __IOS__ || MACCATALYST in StepperHandler.cs is the correct idiom (aligns with platform checks memory noting IOS || MACCATALYST for Apple TFMs).
  • Internal API with TODOMapFlowDirection is internal with a comment to promote to public in .NET 11. This is appropriate API-stability practice; no PublicAPI.Unshipped.txt entry is needed today.
  • iOS 26 detectionOperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26) is the correct cross-platform version check pattern.
  • Null-safe parent lookup(stepper as IView)?.Parent as IView with a null guard before accessing FlowDirection is safe.
  • Test guard removal — The #if TEST_FAILS_ON_IOS && TEST_FAILS_ON_CATALYST block was correctly removed (not just commented out) since the fix is now in place.
  • VerifyScreenshot parameterstolerance: 0.5, retryTimeout: TimeSpan.FromSeconds(2) is an appropriate choice for a screenshot test involving layout direction.

🟡 Suggestions (Non-Blocking)

1. Minor inefficiency: GetParentSemanticContentAttribute called twice for MatchParent

In StepperExtensions.cs, for FlowDirection.MatchParent, the code path calls:

  1. GetSemanticContentAttributeGetParentSemanticContentAttribute (to compute UISemanticContentAttribute)
  2. GetCGAffineTransformGetParentTransformGetParentSemanticContentAttribute again (to compute transform)

This results in two identical parent lookups. Not a correctness issue, and the parent walk is cheap, but it could be simplified in a follow-up by computing both values together.

Suggested refactor (optional, .NET 11 when making public):

static (UISemanticContentAttribute, CGAffineTransform) GetEffectiveAttributes(IStepper stepper)
{
    // Compute once, return both
}

2. MatchParent on iOS 26: double parent lookup

Same as above — GetParentTransform re-derives direction from the semantic attribute rather than accepting it as a parameter. Minor cleanup opportunity.

3. Platforms Tested checkboxes are misleading

The PR description checks all four platforms (including Windows and Android) as "Tested", but the code change only affects iOS/MacCatalyst. Checking untouched platforms is misleading for reviewers and could mask regressions. Recommend checking only iOS and MacCatalyst as the affected platforms.

🔴 No Critical Issues Found

The implementation is logically correct. The double-flip technique (flip container, counter-flip children) is a known pattern for RTL mirroring on iOS 26 and does not introduce bugs.


Summary

Area Status
Title 🟡 Needs casing fix (FlowDirection) and minor rewording
Description 🟡 Good structure; add iOS 26 workaround detail; fix platforms-tested checkboxes
Code correctness ✅ No critical issues
Tests ✅ Appropriate guard removal + new snapshots
Breaking changes ✅ None — internal API, no public surface change

Overall: PR is ready for merge with the title correction applied. Description improvements are recommended but not blocking.

@sheiksyedm
Copy link
Contributor

/azp run maui-pr-uitests

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@kubaflo kubaflo added the s/agent-fix-implemented PR author implemented the agent suggested fix label Mar 16, 2026
@kubaflo kubaflo changed the base branch from main to inflight/current March 16, 2026 15:35
@kubaflo kubaflo merged commit c317f1c into dotnet:inflight/current Mar 16, 2026
28 of 31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-controls-stepper Stepper community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration platform/ios platform/macos macOS / Mac Catalyst s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-implemented PR author implemented the agent suggested fix s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates 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-suggestions-implemented Maintainer applies when PR author adopts agent's recommendation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stepper Ignores RightToLeft FlowDirection on iOS and macOS

6 participants