Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes an iOS infinite layout cycle that occurs when nested views both have SafeAreaEdges = Container by implementing a generation counter mechanism to track SafeArea change events.
Changes:
- Implemented a generation counter system using a static counter incremented on genuine SafeArea changes and per-view tracking to prevent re-invalidation within the same generation
- Added test cases for nested SafeArea views with animations (Issue32586) and runtime SafeAreaEdges toggling (Issue33595)
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/Core/src/Platform/iOS/MauiView.cs | Added generation counter fields and logic to break infinite layout cycles in LayoutSubviews |
| src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue33595.cs | UI test verifying navigation with padding and ScrollView doesn't crash |
| src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue32586.cs | UI tests for TranslateToAsync animation and runtime SafeAreaEdges toggling |
| src/Controls/tests/TestCases.HostApp/Issues/Issue33595.cs | Test page with Grid containing ScrollView that previously caused freezing |
| src/Controls/tests/TestCases.HostApp/Issues/Issue32586.cs | Test page with nested SafeArea views and animation triggers |
Test Results ✅Successfully fixed the infinite layout cycle! The fix now passes all tests: Issue32586 Test (the freeze repro)
All SafeAreaEdges Tests
How the Fix WorksThe root cause was that The fix uses a global generation counter that increments each time any view invalidates ancestors from This preserves correct SafeArea behavior while breaking the infinite loop. Bonus: Test Infrastructure ImprovementsAlso added resilience fixes to prevent tests from hanging indefinitely when apps freeze:
These ensure tests fail gracefully instead of hanging, making it much easier to debug layout issues like this one. |
|
/rebase |
42adf00 to
ba9c901
Compare
|
/azp run maui-pr-uitests, maui-pr-devicetests |
|
Azure Pipelines successfully started running 2 pipeline(s). |
…n oscillation fix Cherry-picked from PR #34024 (ios-safearea-infinite-layout-fix): - MauiView.cs: generation counter to break parent↔child safe area cycles - MauiView.cs: global rate limiter for safe area invalidation cascades - Tests: Issue32586, Issue33595, Issue33934 (safe area layout cycle tests) Additional fix for Issue33934 animation-driven layout oscillation: - InvalidateMeasure(isPropagating: true) no longer sets _safeAreaInvalidated - A descendant changing size/transform doesn't affect system safe area insets - This prevented spurious safe area revalidation during TranslateToAsync animations, which caused measurement oscillation (±2px) and infinite SizeChanged → cancel animation → restart cycles Validated locally: - Issue33934: was infinite loop, now completes in ≤2 iterations ✅ - Issue33595: simple layout cycle fix ✅ - Issue32586: safe area layout ✅ - Issue18896: existing safe area test ✅ - Issue33458: another safe area test ✅ - SafeAreaEdges category: all 28 tests pass ✅ Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove MauiView.cs safe area changes, Issue33934/32586/33595 test files, and ViewModelBase changes from this PR. These belong in PR #34024 (ios-safearea-infinite-layout-fix) which is the dedicated safe area fix PR. This PR now focuses solely on UITest resilience infrastructure. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…n oscillation fix Cherry-picked from PR #34024 (ios-safearea-infinite-layout-fix): - MauiView.cs: generation counter to break parent↔child safe area cycles - MauiView.cs: global rate limiter for safe area invalidation cascades - Tests: Issue32586, Issue33595, Issue33934 (safe area layout cycle tests) Additional fix for Issue33934 animation-driven layout oscillation: - InvalidateMeasure(isPropagating: true) no longer sets _safeAreaInvalidated - A descendant changing size/transform doesn't affect system safe area insets - This prevented spurious safe area revalidation during TranslateToAsync animations, which caused measurement oscillation (±2px) and infinite SizeChanged → cancel animation → restart cycles Validated locally: - Issue33934: was infinite loop, now completes in ≤2 iterations ✅ - Issue33595: simple layout cycle fix ✅ - Issue32586: safe area layout ✅ - Issue18896: existing safe area test ✅ - Issue33458: another safe area test ✅ - SafeAreaEdges category: all 28 tests pass ✅ Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove MauiView.cs safe area changes, Issue33934/32586/33595 test files, and ViewModelBase changes from this PR. These belong in PR #34024 (ios-safearea-infinite-layout-fix) which is the dedicated safe area fix PR. This PR now focuses solely on UITest resilience infrastructure. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…n oscillation fix Cherry-picked from PR #34024 (ios-safearea-infinite-layout-fix): - MauiView.cs: generation counter to break parent↔child safe area cycles - MauiView.cs: global rate limiter for safe area invalidation cascades - Tests: Issue32586, Issue33595, Issue33934 (safe area layout cycle tests) Additional fix for Issue33934 animation-driven layout oscillation: - InvalidateMeasure(isPropagating: true) no longer sets _safeAreaInvalidated - A descendant changing size/transform doesn't affect system safe area insets - This prevented spurious safe area revalidation during TranslateToAsync animations, which caused measurement oscillation (±2px) and infinite SizeChanged → cancel animation → restart cycles Validated locally: - Issue33934: was infinite loop, now completes in ≤2 iterations ✅ - Issue33595: simple layout cycle fix ✅ - Issue32586: safe area layout ✅ - Issue18896: existing safe area test ✅ - Issue33458: another safe area test ✅ - SafeAreaEdges category: all 28 tests pass ✅ Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove MauiView.cs safe area changes, Issue33934/32586/33595 test files, and ViewModelBase changes from this PR. These belong in PR #34024 (ios-safearea-infinite-layout-fix) which is the dedicated safe area fix PR. This PR now focuses solely on UITest resilience infrastructure. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
|
Alright @Tamilarasan-Paranthaman @sheiksyedm Let me know what you think of this fix |
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
🔬 Fix Approach Comparison — 4 Independent AttemptsWe ran 4 independent try-fix attempts, each proposing a fundamentally different approach to fix the iOS safe area infinite layout cycle. After retesting with the updated UITests (with Retest Results (Updated UITests)
Common failure in approaches 1, 2, 4: ✅ Selected: Approach 3 — Window-Level Safe Area Comparison (applied in
|
04b2096 to
4f0de3a
Compare
|
Azure Pipelines successfully started running 2 pipeline(s). |
…e RTL mirroring The CrossPlatformArrange call with negative X offset placed content outside the scrollable range, making it unreachable. iOS UIScrollView with SemanticContentAttribute ForceRightToLeft handles RTL mirroring natively. Only ContentOffset needs to be set to position the initial scroll at the RTL start. The removed if/else branches were identical dead code from a merge conflict resolution. Fixes ScrollViewShouldWorkInRTL / Issue29458 test failure on iOS vlatest. Co-authored-by: Tamilarasan-Paranthaman <Tamilarasan-Paranthaman@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
/azp run maui-pr-uitests |
|
/azp run maui-pr-devicetests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
1 similar comment
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
/azp run maui-pr-devicetests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
/azp run maui-pr-uitests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
- Replace non-existent PR numbers (#34000, #33500, #33000, #34100) with real merged PRs (#34024, #34727, #31202, #28713, #34723) - Add "in dotnet/maui" to all prompts to prevent agent asking for repo - All PRs verified as real merged PRs with actual code changes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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
SafeAreaInsetsDidChangefires repeatedly during iOS animations (e.g.,TranslateToAsync, bottom sheet transitions) as views move relative to the window. This caused two distinct infinite loop patterns:Sub-pixel oscillation (Layout issue using TranslateToAsync causes infinite property changed cycle on iOS #32586, [iOS] TranslateToAsync causes spurious SizeChanged events after animation completion, triggering infinite layout loops #33934): Animations produce sub-pixel differences in
SafeAreaInsets(e.g.,0.0000001pt). Exact equality fails, triggeringInvalidateAncestorsMeasures→ layout pass → position change → newSafeAreaInsetsDidChange→ infinite loop.Parent-child double application ([net10] iOS 18.6 crashing on navigating to a ContentPage with Padding set and Content set to a <Grid RowDefinitions="*,Auto"> with ScrollView on row 0 #33595): A
ContentPage(implementingISafeAreaView) and its childGridboth independently apply safe area adjustments. When theContentPageadjusts its layout for the notch/status bar, it repositions theGrid. TheGrid's new position firesSafeAreaInsetsDidChange, causing it to re-apply its own adjustment — creating a ping-pong loop.Description of Change
Primary fix —
IsParentHandlingSafeArea(parent hierarchy walk):In both
MauiView.ValidateSafeAreaandMauiScrollView.ValidateSafeArea, before applying safe area adjustments, we now check whether an ancestorMauiViewis already applying safe area for the same edges. If so, the child skips its own adjustment to avoid double-padding.The check is edge-aware: a parent handling
Topdoes not block a child from independently handlingBottom. Only overlapping edges cause deferral. The_parentHandlesSafeArearesult is cached per layout cycle and cleared onSafeAreaInsetsDidChange,InvalidateSafeArea, andMovedToWindow.Secondary fix —
EqualsAtPixelLevel:Safe area values are compared at device-pixel resolution (rounding to
1 / ContentScaleFactor) before deciding whether to trigger a layout invalidation. This absorbs sub-pixel animation noise and prevents the oscillation loops in #32586 and #33934.MauiScrollView bug fixes:
!UpdateContentInsetAdjustmentBehavior()was incorrectly gating behavior; corrected toUpdateContentInsetAdjustmentBehavior()._appliesSafeAreaAdjustmentsflag now correctly incorporates!IsParentHandlingSafeArea().What was removed:
Window.SafeAreaInsetsto filter noise) was tried and removed. It was fragile: on macCatalyst with a custom TitleBar,WindowViewControllerrepositions content by pushing it down, which changes the view's ownSafeAreaInsetswithout changingWindow.SafeAreaInsets. The guard blocked this legitimate change, causing a 28px content shift regression in CI.Issues Fixed
Fixes #32586
Fixes #33934
Fixes #33595
Fixes #34042