KEMBAR78
Implement SafeAreaEdges property for per-edge safe area control by Copilot · Pull Request #30337 · dotnet/maui · GitHub
Skip to content

Conversation

Copilot
Copy link
Contributor

@Copilot Copilot AI commented Jun 30, 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!

SafeArea Feature Specification

Caution as you test this PR

The safe area calculations in some scenarios are off until we can merge this PR #30629. These aren't bugs with this PR they are general bugs we've been trying to fix for sometimes.

  • If you have a control that you want hugging under the notch or top of screen it will be slightly cutoff
  • On SCrollView setting different edges or "ALL" will currently layout incorrectly on the bottom

Overview

The SafeArea feature provides fine-grained, per-edge safe area control for .NET MAUI applications, enabling developers to selectively control safe area behavior on specific edges while maintaining backward compatibility.

Public APIs

1. SafeAreaRegions Enum

[Flags]
public enum SafeAreaRegions
{
    None = 0,          // Edge-to-edge content (no safe area padding)
    SoftInput = 1,     // Always pad for keyboard/soft input
    Container = 2,     // Flow under keyboard, stay out of bars/notch  
    Default = 4,       // Platform default behavior
    All = int.MaxValue // Obey all safe area insets
}

2. SafeAreaEdges Struct

[TypeConverter(typeof(Converters.SafeAreaEdgesTypeConverter))]
public struct SafeAreaEdges : IEquatable<SafeAreaEdges>
{
    public SafeAreaRegions Left { get; set; }
    public SafeAreaRegions Top { get; set; }
    public SafeAreaRegions Right { get; set; }
    public SafeAreaRegions Bottom { get; set; }
    
    // Constructors
    public SafeAreaEdges(SafeAreaRegions uniformValue)
    public SafeAreaEdges(SafeAreaRegions horizontal, SafeAreaRegions vertical)
    public SafeAreaEdges(SafeAreaRegions left, SafeAreaRegions top, SafeAreaRegions right, SafeAreaRegions bottom)
    
    // Static properties
    public static SafeAreaEdges None { get; }
    public static SafeAreaEdges All { get; }
    public static SafeAreaEdges Default { get; }
    
    // Methods
    public SafeAreaRegions GetEdge(int edge)
    public void SetEdge(int edge, SafeAreaRegions value)
}

3. SafeAreaEdges Properties

Available on these controls:

  • Layout.SafeAreaEdges
  • ContentView.SafeAreaEdges
  • ContentPage.SafeAreaEdges
  • Border.SafeAreaEdges
  • ScrollView.SafeAreaEdges

4. XAML TypeConverter

Supports multiple input formats:

  • 1 value: "None" or "All" - applies to all edges
  • 2 values: "All,None" - horizontal,vertical pattern
  • 4 values: "All,None,All,None" - Left,Top,Right,Bottom order

Usage Examples

XAML Usage

<!-- Single value - all edges -->
<Grid SafeAreaEdges="None">

<!-- Two values - horizontal, vertical -->  
<ContentView SafeAreaEdges="All,None">

<!-- Four values - Left, Top, Right, Bottom -->
<VerticalStackLayout SafeAreaEdges="All,None,All,None">

<!-- Mixed behavior -->
<ScrollView SafeAreaEdges="Container,All,Container,SoftInput">

C# Usage

// Constructor patterns
layout.SafeAreaEdges = new SafeAreaEdges(SafeAreaRegions.None);
layout.SafeAreaEdges = new SafeAreaEdges(SafeAreaRegions.All, SafeAreaRegions.None);
layout.SafeAreaEdges = new SafeAreaEdges(
    SafeAreaRegions.All,      // Left
    SafeAreaRegions.None,     // Top  
    SafeAreaRegions.All,      // Right
    SafeAreaRegions.SoftInput // Bottom
);

// Static properties
layout.SafeAreaEdges = SafeAreaEdges.None;
layout.SafeAreaEdges = SafeAreaEdges.All;

// Programmatic edge access
var edges = new SafeAreaEdges();
edges.SetEdge(0, SafeAreaRegions.All);  // Left
edges.SetEdge(1, SafeAreaRegions.None); // Top

Behavior Specification

SafeAreaRegions Behaviors

  • None: Content extends edge-to-edge with no safe area padding
  • SoftInput: Content always pads to avoid keyboard/soft input overlay
  • Container: Content flows under keyboard but avoids status bars, notches, and home indicators
  • Default: Uses platform default safe area behavior
  • All: Content respects all safe area insets (status bars, notches, home indicators, keyboard)

Control-Specific Defaults

  • ContentPage: Returns All when value is Default
  • Layout/ContentView/Border: Returns None when value is Default
  • ScrollView: Returns Default when value is Default (uses iOS ContentInsetAdjustmentBehavior)

Platform Integration

iOS Implementation

  • MauiView.AdjustForSafeAreaPerEdge(): Applies per-edge UIEdgeInsets
  • MauiScrollView: Uses ContentInsetAdjustmentBehavior mapping:
    • All Default edges → .automatic
    • All All edges → .never
    • All None edges → .always
    • Mixed edges → .never with manual ContentInset calculation

Interface Integration

  • ISafeAreaPage: Provides IgnoreSafeAreaForEdge(int edge) and SafeAreaInsets setter
  • ISafeAreaElement: Handles SafeAreaEdges property and fallback logic

Backward Compatibility

  • Existing Layout.IgnoreSafeArea property continues to work unchanged
  • New SafeAreaEdges property provides fallback to legacy behavior when not explicitly set
  • ISafeAreaPage implementations handle both new and legacy APIs seamlessly

Testing Coverage

  • 11 unit tests covering struct behavior, interface implementation, and fallback logic
  • 6 UI test pages for different controls (Layout, ContentView, ContentPage, Border, ScrollView)
  • 6 Appium tests validating interactive behavior and settings persistence

@Copilot Copilot AI changed the title [WIP] [.NET10] SafeArea Epic Implement SafeAreaGuides attached property for per-edge safe area control Jun 30, 2025
@Copilot Copilot AI requested a review from PureWeen June 30, 2025 21:52
Copilot finished work on behalf of PureWeen June 30, 2025 21:52
@Copilot Copilot AI requested a review from PureWeen July 1, 2025 02:46
Copilot finished work on behalf of PureWeen July 1, 2025 02:46
@PureWeen
Copy link
Member

PureWeen commented Jul 1, 2025

Move the ISafeAreaView2 up to the view class.

We don't need to make sure it works for every view at the platform level yet though, just move the implementation of the interface up to the view class

Also, go through every test you can find that uses the obsolete safe area APIs and create a copy of that test using this new api

@Copilot
Copy link
Contributor Author

Copilot AI commented Jul 1, 2025

Move the ISafeAreaView2 up to the view class.

We don't need to make sure it works for every view at the platform level yet though, just move the implementation of the interface up to the view class ...

Completed both tasks in commit a053e24:

  1. Moved ISafeAreaView2 to View class: The interface implementation is now at the View level, making it available to all UI elements. Layout no longer explicitly implements it since it inherits from View. Page keeps its specific implementation since it doesn't inherit from View.

  2. Added comprehensive tests based on existing obsolete API patterns: Created 6 additional tests covering:

    • View-level SafeAreaGuides usage
    • Real-world scenarios based on existing ShellTests.iOS and Issue3809 patterns
    • Per-edge control use cases (ignore top/bottom only)
    • Two-value shorthand syntax
    • Fallback behavior for views without legacy safe area support

All 17 tests pass, confirming the new API works correctly across all view types.

Copilot finished work on behalf of PureWeen July 1, 2025 13:03
@PureWeen
Copy link
Member

PureWeen commented Jul 1, 2025

I was mistaken on my last instruction

Can you make it so that

  • Page
  • Layout
  • ContentView

All implement ISafeAreaView2 but not View

Also, we want ContentView and Page both to make use of this API so the code that's inside Layout will probably need to be generalized somewhere (probably into the attached property class you added)

@Copilot
Copy link
Contributor Author

Copilot AI commented Jul 1, 2025

I was mistaken on my last instruction

Can you make it so that
...

Completed in bf4be4f. Restructured ISafeAreaView2 implementation as requested:

  • Removed ISafeAreaView2 from View class - View no longer implements the interface
  • Added ISafeAreaView2 to Page, Layout, and ContentView - All three classes now explicitly implement the interface
  • Generalized shared logic - Created SafeAreaGuides.ShouldIgnoreSafeAreaForEdge() internal method that handles the attached property checking and legacy fallback logic
  • Updated Page and ContentView - Both now use the new attached property API instead of legacy-only behavior
  • Fixed all tests - Updated test cases to use the correct classes that implement ISafeAreaView2 (Page, Layout, ContentView instead of View-derived Label)

All 19 SafeAreaGuides unit tests pass, confirming the new structure works correctly while maintaining backward compatibility.

Copilot finished work on behalf of PureWeen July 1, 2025 16:45
Copilot finished work on behalf of PureWeen July 1, 2025 18:29
@Copilot Copilot AI requested a review from PureWeen July 1, 2025 18:29
Copilot finished work on behalf of PureWeen July 1, 2025 18:40
@PureWeen
Copy link
Member

PureWeen commented Jul 1, 2025

Can you add a MAUI sample into the HostApp project that has the ability to test all permutations of disabling and enabling the safea layout guies on each edge?

Just look at any of the files inside the

maui/src/Controls/tests/TestCases.HostApp/Issues/

folder to see an example of how to setup the maui sample.

You don't need to create a new project just add a single page that I can use to test setting and unsetting

@Copilot
Copy link
Contributor Author

Copilot AI commented Jul 1, 2025

Can you add a MAUI sample into the HostApp project that has the ability to test all permutations of disabling and enabling the safea layout guies on each edge?

Just look at any of the files inside...

Created a comprehensive test page (Issue28986.xaml) in the HostApp that allows testing all permutations of SafeAreaGuides per-edge control. The page includes:

  • Visual indicators with colored backgrounds to show safe area effects
  • CheckBoxes for each edge (Left/Top/Right/Bottom) to toggle ignore/respect behavior
  • Reset buttons for easy testing scenarios
  • Real-time display of current settings and array syntax being used
  • Automatic optimization to test 1-value, 2-value, and 4-value array syntaxes

The page follows the same pattern as other issue test files in the TestCases.HostApp/Issues folder and provides an interactive way to validate the SafeAreaGuides.IgnoreSafeArea attached property functionality across all edge combinations.

Commit: dae5dc1

Copilot finished work on behalf of PureWeen July 1, 2025 20:00
@PureWeen
Copy link
Member

PureWeen commented Jul 1, 2025

currently failing with
/Users/shneuvil/Projects/maui2/src/Core/src/Platform/iOS/MauiView.cs(60,20): error CS0051: Inconsistent accessibility: parameter type 'ISafeAreaView2' is less accessible than method 'MauiView.AdjustForSafeAreaPerEdge(CGRect, ISafeAreaView2)'

@PureWeen
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 3 pipeline(s).

Copy link
Member

@PureWeen PureWeen left a comment

Choose a reason for hiding this comment

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

  • for Issue28986.cs add a scenario that validates setting the bottom edge to SoftInput and then setting the top edge to Container and the top Edge to none
  • Create a second set of tests for Issue28986_ScrollView that tests the ScrollView with SafeAreaEdges settings as well
  • revert changes made to ".gitignore"

…gitignore changes

Co-authored-by: PureWeen <5375137+PureWeen@users.noreply.github.com>
@Copilot
Copy link
Contributor Author

Copilot AI commented Jul 27, 2025

  • for Issue28986.cs add a scenario that validates setting the bottom edge to SoftInput and then setting the top edge to Container and the top Edge to none
  • Create a second set of tests for Issue2898...

Completed all requested changes:

  1. Added per-edge validation scenario to Issue28986.cs - Tests setting bottom edge to SoftInput, then top edge to Container, then top edge to None with proper assertions
  2. Created Issue28986_ScrollView test suite - New test page and test class with button-based controls testing ScrollView SafeAreaEdges behavior
  3. Reverted .gitignore changes - Removed the *.bak entry that was added

Commit: 765fffa

@Copilot Copilot AI requested a review from PureWeen July 27, 2025 13:18
Copilot finished work on behalf of PureWeen July 27, 2025 13:18
HorizontalOptions="Center"
Margin="0,10,0,0" />

<Button Text="Set Bottom Edge to SoftInput"
Copy link
Member

Choose a reason for hiding this comment

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

You're going to need to add an entry control that you can focus to validate that "SoftInput" is working for the tests

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added Entry control with AutomationId "SoftInputTestEntry" to enable validation of SoftInput behavior when the keyboard appears. The Entry is positioned in the control panel section for easy access during testing. d282b5c

x:Name="TestPage"
Title="SafeArea ScrollView Test">

<Grid x:Name="MainGrid" BackgroundColor="Blue" SafeAreaEdges="All">
Copy link
Member

Choose a reason for hiding this comment

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

In order for this test to work this will need to be set to "None" otherwise the SCrollView won't be able to go edge to edge

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed MainGrid SafeAreaEdges from "All" to "None" to allow the ScrollView to go edge-to-edge for proper testing. This ensures the ScrollView can extend to the full screen boundaries when needed. d282b5c

Co-authored-by: PureWeen <5375137+PureWeen@users.noreply.github.com>
@Copilot Copilot AI requested a review from PureWeen July 27, 2025 14:21
Copilot finished work on behalf of PureWeen July 27, 2025 14:21
@PureWeen
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 3 pipeline(s).

Copy link
Contributor

@StephaneDelcroix StephaneDelcroix left a comment

Choose a reason for hiding this comment

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

thx for making safeareaedges r-o

@github-project-automation github-project-automation bot moved this from Changes Requested to Approved in MAUI SDK Ongoing Jul 28, 2025
@StephaneDelcroix StephaneDelcroix merged commit 300192d into net10.0 Jul 28, 2025
129 checks passed
@StephaneDelcroix StephaneDelcroix deleted the copilot/fix-28986 branch July 28, 2025 14:01
@github-project-automation github-project-automation bot moved this from Approved to Done in MAUI SDK Ongoing Jul 28, 2025
@PureWeen PureWeen changed the title Implement SafeArea attached property for per-edge safe area control Implement SafeAreaEdges property for per-edge safe area control Jul 31, 2025
@PureWeen PureWeen added the area-layout StackLayout, GridLayout, ContentView, AbsoluteLayout, FlexLayout, ContentPresenter label Aug 21, 2025
@github-actions github-actions bot locked and limited conversation to collaborators Sep 21, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-layout StackLayout, GridLayout, ContentView, AbsoluteLayout, FlexLayout, ContentPresenter p/0 Work that we can't release without

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants