Skip to content

Implement SafeArea attached property for per-edge safe area control #30337

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 23 commits into
base: net10.0
Choose a base branch
from

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

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)

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

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)'

@Copilot Copilot AI requested a review from PureWeen July 25, 2025 17:57
@PureWeen
Copy link
Member

alright let's give up on the diagnostic logging for now and just go back to making changes to this PR

Can you make the following changes
#30337 (comment)

And don't worry if you're not able to build the project
Just apply those changes please

Copy link
Contributor Author

Copilot AI commented Jul 25, 2025

alright let's give up on the diagnostic logging for now and just go back to making changes to this PR

Can you make the following changes
...

I'm unable to access the specific discussion thread at #30337 (comment) directly. Could you please specify what changes you'd like me to make? I can see from the PR comments that there have been requests about:

  1. Moving types to Core project
  2. Updating SafeAreaRegions implementation
  3. Interface changes for ISafeAreaView2

What specific changes from that discussion would you like me to apply?

Copilot finished work on behalf of PureWeen July 25, 2025 18:05
@Copilot Copilot AI requested a review from PureWeen July 25, 2025 18:20
Copilot finished work on behalf of PureWeen July 25, 2025 18:20
Copy link
Contributor Author

Copilot AI commented Jul 25, 2025

alright let's give up on the diagnostic logging for now and just go back to making changes to this PR

Can you make the following changes
...

I'm unable to access the specific GitHub discussion at that URL. Could you please specify what changes you'd like me to make? I can see the SafeArea implementation is in place, but I need to know what specific modifications from that discussion thread you want applied.

@PureWeen
Copy link
Member

  • So far the tests you've added have been unhelpful. I've modified this test "SafeAreaMainGridBasicFunctionality" to correctly validate the screen position of the Grid to ensure that toggling the safearea values does indeed cause the content to inset and remove insets as the values of the SafeAreaEdges changed. Please use "SafeAreaMainGridBasicFunctionality" as a model of how to write the unit tests for this PR and fix your existing tests that you've added.

Copilot finished work on behalf of StephaneDelcroix July 25, 2025 20:00
Copy link
Contributor Author

Copilot AI commented Jul 25, 2025

  • So far the tests you've added have been unhelpful. I've modified this test "SafeAreaMainGridBasicFunctionality" to correctly validate the screen position of the Grid to ensure that toggling the safe...

Fixed the SafeArea UI tests to follow the "SafeAreaMainGridBasicFunctionality" model pattern. The tests now validate actual screen positioning instead of just text content:

Updated tests:

  • SafeAreaMainGridAllButtonFunctionality - Now captures position when SafeAreaEdges is None vs All and verifies the ContentGrid moves to different Y positions
  • SafeAreaMainGridSequentialButtonTesting - Now validates positioning through the full sequence (All → None → Container → All) with assertions that None results in Y=0 (edge-to-edge) and All/Container result in Y>0 (offset by safe area)

Both tests now verify the actual UI behavior by checking that SafeAreaEdges changes cause the ContentGrid to inset/remove insets as expected. Commit: 8f433f0

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

/azp run

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@PureWeen
Copy link
Member

/azp run

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
p/0 Work that we can't release without
Projects
Status: Changes Requested
Development

Successfully merging this pull request may close these issues.

3 participants