[iOS & MacCatalyst] Fixed Flowdirection in Stepper#34005
[iOS & MacCatalyst] Fixed Flowdirection in Stepper#34005kubaflo merged 5 commits intodotnet:inflight/currentfrom
Conversation
|
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. |
There was a problem hiding this comment.
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
FlowDirectionmapper override forStepperHandler. - Implemented
UIStepper.UpdateFlowDirectionto updateSemanticContentAttributeand (for iOS 26+) apply a horizontal transform to the stepper and its subviews. - Re-enabled the
Stepper_ChangeFlowDirection_RTL_VerifyVisualStatescreenshot 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. |
src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/StepperFeatureTests.cs
Show resolved
Hide resolved
I’ve updated the changes based on the summary. Specifically:
|
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34005Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34005" |
🤖 AI Summary📊 Expand Full Review —
|
| # | 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:
UIStepperalways renders LTR even whenFlowDirection = RightToLeft; Android/Windows work correctly - PR adds
FlowDirectiontoStepperHandlerproperty mapper for__IOS__ || MACCATALYST UpdateFlowDirectionsetsSemanticContentAttributeon stepper + subviews; on iOS/MacCatalyst 26+ also appliesCGAffineTransform.MakeScale(-1, 1)becauseSemanticContentAttributealone is no longer sufficient- The test guard
#if TEST_FAILS_ON_IOS && TEST_FAILS_ON_CATALYSTwas removed —Stepper_ChangeFlowDirection_RTL_VerifyVisualStateis now active on iOS and Catalyst - Snapshot baselines added:
ios/andios-26/— nomac/baseline (MacCatalyst tests may fail on Mac runner without a baseline) MapFlowDirectionisinternalwith 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:
isIOS26check was missingIsMacCatalystVersionAtLeast(26)→ FIXED in current code ✅GetParentSemanticContentAttributeonly checks immediate parent (shallow walk) — if parent isMatchParentbut 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 entrysrc/Core/src/Handlers/Stepper/StepperHandler.iOS.cs—MapFlowDirectionmethodsrc/Core/src/Platform/iOS/StepperExtensions.cs—UpdateFlowDirectionextension
Note: The test file
StepperFeatureTests.csremoved a#if TEST_FAILS_ON_IOS && TEST_FAILS_ON_CATALYSTguard. 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
VerifyScreenshotcall 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
src/Core/src/Handlers/Stepper/StepperHandler.cs- Adds FlowDirection mapper entry for iOS/MacCatalystsrc/Core/src/Handlers/Stepper/StepperHandler.iOS.cs- Adds MapFlowDirection methodsrc/Core/src/Platform/iOS/StepperExtensions.cs- Adds UpdateFlowDirection extensioneng/pipelines/ci-copilot.yml- Pipeline configurationeng/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 entrysrc/Core/src/Handlers/Stepper/StepperHandler.iOS.cs—MapFlowDirectionmethodsrc/Core/src/Platform/iOS/StepperExtensions.cs—UpdateFlowDirectionextension
Note: The test file
StepperFeatureTests.csremoved a#if TEST_FAILS_ON_IOS && TEST_FAILS_ON_CATALYSTguard. 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
VerifyScreenshotcall 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:
- Fixes the shallow parent walk bug — MatchParent FlowDirection now correctly inherits from any ancestor (not just immediate parent)
- Eliminates redundant second parent-walk (
GetParentTransform) by reusing the already-resolvedUISemanticContentAttribute - Keeps PR's structure and pattern — minimal diff, easier to review
- More debuggable than Attempt 1 (explicit virtual hierarchy walk visible in code)
Reason preferred over Attempt 1:
- No dependency on
ViewHandler.MapFlowDirectionsequencing - 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:
StepperHandlerhad no mapper entry forFlowDirectionon iOS/MacCatalyst — changes were never applied.- Even when setting
SemanticContentAttribute, iOS 26+ (Liquid Glass) requires an explicitCGAffineTransform.MakeScale(-1, 1)to visually flip the control.
Fix Quality
What the PR does well:
- Correctly identifies the two-step solution:
SemanticContentAttribute+CGAffineTransformfor iOS 26+ - Properly propagates attributes to UIStepper's internal subviews
- Removes the
#if TEST_FAILS_ON_IOS && TEST_FAILS_ON_CATALYSTtest guard - Adds snapshot baselines for
ios/andios-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
-
Fix shallow parent walk — either implement recursive ancestor walk in
GetParentSemanticContentAttributeor adopt Attempt 1's delegation approach (which gets this for free viaViewHandler.MapFlowDirection) -
Add
IsMacCatalystVersionAtLeast(26)check —bool isIOS26 = OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26); -
Add Mac snapshot baseline — add
Stepper_ChangeFlowDirection_RTL_VerifyVisualState.pngtosrc/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:
- Setting
SemanticContentAttributetoForceRightToLefton the stepper and its internal subviews - On iOS/MacCatalyst 26+ (where
SemanticContentAttributealone is no longer sufficient): additionally applyingCGAffineTransform.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
FlowDirectionin the property mapper (iOS/MacCatalyst only — correct) - ✅
MapFlowDirectionisinternalwith// TODO: Make public for .NET 11.— correct phased API approach - ✅ Sets
SemanticContentAttributeon stepper AND subviews - ✅ Adds
CGAffineTransformfor iOS/MacCatalyst 26+ - ✅
isIOS26check now includesIsMacCatalystVersionAtLeast(26)— prior agent bug fixed ✅ - ✅ Test guard removed; snapshot baselines added for
ios/andios-26/ - ✅ Gate passes: test correctly detects bug / fix
What needs improvement:
-
Shallow parent walk bug (unresolved) —
GetParentSemanticContentAttributeonly checks(stepper as IView)?.Parent as IViewand then stops. If the immediate parent hasFlowDirection.MatchParent, the function returnsUnspecifiedeven 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;
-
Redundant double parent-walk for transform —
GetCGAffineTransformcallsGetParentTransformwhich callsGetParentSemanticContentAttributeagain. Since the resolvedUISemanticContentAttributeis 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();
-
No
mac/snapshot baseline —TestCases.Mac.Tests/snapshots/mac/has no baseline forStepper_ChangeFlowDirection_RTL_VerifyVisualState. If the test runs on macOS CI, it will fail due to missing baseline image.
Recommended Changes
- Fix
GetParentSemanticContentAttributeto walk the full ancestor chain (see code above) - Simplify
GetCGAffineTransform/GetParentTransformby deriving transform from resolved attribute - Add
mac/snapshot baseline (or gate test onTEST_FAILS_ON_CATALYSTuntil baseline is added) - 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:
Flowdirectionhas incorrect casing — should beFlowDirection- Past tense
Fixedis 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 | 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__ || MACCATALYSTinStepperHandler.csis the correct idiom (aligns withplatform checksmemory notingIOS || MACCATALYSTfor Apple TFMs). - Internal API with TODO —
MapFlowDirectionisinternalwith a comment to promote topublicin .NET 11. This is appropriate API-stability practice; noPublicAPI.Unshipped.txtentry is needed today. - iOS 26 detection —
OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26)is the correct cross-platform version check pattern. - Null-safe parent lookup —
(stepper as IView)?.Parent as IViewwith a null guard before accessingFlowDirectionis safe. - Test guard removal — The
#if TEST_FAILS_ON_IOS && TEST_FAILS_ON_CATALYSTblock was correctly removed (not just commented out) since the fix is now in place. - VerifyScreenshot parameters —
tolerance: 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:
GetSemanticContentAttribute→GetParentSemanticContentAttribute(to computeUISemanticContentAttribute)GetCGAffineTransform→GetParentTransform→GetParentSemanticContentAttributeagain (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.
|
/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!
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
FlowDirectioninStepperHandlerfor iOS and MacCatalyst, allowing the handler to respond to flow direction changes.MapFlowDirectionmethod inStepperHandler.iOS.csto update the platform view's flow direction.UpdateFlowDirectionextension method forUIStepperinStepperExtensions.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
BeforeFix29704.mov
AfterFix18Version29704.mov