maui-safe-area について
.NET MAUI 開発者向けに、.NET 10 以降の `SafeAreaEdges` API を使ったセーフエリアとエッジツーエッジレイアウトを解説します。辺ごとの制御、キーボード領域への対応、旧 API からの移行を扱います。
主な機能
- 01辺ごとのセーフエリア設定
- 02エッジツーエッジレイアウトの構築
- 03キーボード領域への対応
- 04Blazor Hybrid の CSS インセット連携
- 05旧セーフエリア API からの移行
活用シーン
- 01フォームをシステムバーやキーボードから保護
- 02写真や動画の没入型画面の構築
- 03キーボードに対応するチャットレイアウトの作成
- 04.NET 10 のセーフエリア処理への更新
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dotnet/ skills --skill maui-safe-areaインストール後、Claude Code で/maui-safe-areaを入力して読み込まれたか確認します。
同梱ファイルreferences/safe-area-api.md
詳細
Safe Area とエッジツーエッジレイアウト(.NET 10 以降)
.NET 10 では、従来の iOS 専用 UseSafeArea とレイアウトレベルの IgnoreSafeArea プロパティに代わる、まったく新しいクロスプラットフォームの Safe Area API が導入されています。新しい SafeAreaEdges プロパティと SafeAreaRegions フラグ列挙型を使うと、単一の API から Android、iOS、Mac Catalyst 上で、コントロールごとに各辺の Safe Area を管理できます。
これは .NET 10 で新たに導入された API です。 プロジェクトのターゲットが .NET 9 以前の場合、これらの API は存在しません。その場合は、従来の
ios:Page.UseSafeAreaプロパティとLayout.IgnoreSafeAreaプロパティを使用するよう開発者に案内してください。
使用する場面
- .NET 10 へのアップグレード後、コンテンツがステータスバー、ノッチ、Dynamic Island、ホームインジケーターに重なる
- エッジツーエッジ/イマーシブなレイアウト(フォトビューアー、動画プレーヤー、マップ)を実装する
- チャットやフォームの UI でキーボードを避ける
ios:Page.UseSafeArea、Layout.IgnoreSafeArea、またはWindowSoftInputModeAdjust.Resizeから移行する- CSS
env(safe-area-inset-*)との連携が必要な Blazor Hybrid アプリ - エッジツーエッジのヘッダーと Safe Area を尊重する本文を組み合わせたレイアウト
使用しない場面
- .NET 9 以前をターゲットにするプロジェクト — 従来の iOS 専用 API を使用してください
- システムバーやキーボードに関係しない一般的なページレイアウトに関する質問 — 標準のレイアウトガイダンスを使用してください
- アプリのライフサイクルやナビゲーション構造 — maui-app-lifecycle または Shell のガイダンスを使用してください
- テーマ設定やビジュアルスタイル — maui-theming スキルを使用してください
入力
- ターゲットフレームワーク:新しい API を使用するには
net10.0-*以降である必要があります - ターゲットプラットフォーム:Android、iOS、Mac Catalyst(Windows にはシステムバーのインセットはありません)
- UI の方式:XAML/C#、Blazor Hybrid、または MauiReactor
SafeAreaRegions 列挙型
[Flags]
public enum SafeAreaRegions
{
None = 0, // Edge-to-edge — no safe area padding
SoftInput = 1 << 0, // Pad to avoid the on-screen keyboard
Container = 1 << 1, // Stay inside status bar, notch, home indicator
Default = -1, // Use the platform default for the control type
All = 1 << 15 // Respect all safe area insets (most restrictive)
}
SoftInput と Container は組み合わせ可能なフラグです。
SafeAreaRegions.Container | SafeAreaRegions.SoftInput = システムバー と キーボードを尊重します。
SafeAreaEdges 構造体
public readonly struct SafeAreaEdges
{
public SafeAreaRegions Left { get; }
public SafeAreaRegions Top { get; }
public SafeAreaRegions Right { get; }
public SafeAreaRegions Bottom { get; }
// Uniform — same value for all four edges
public SafeAreaEdges(SafeAreaRegions uniformValue)
// Horizontal / Vertical
public SafeAreaEdges(SafeAreaRegions horizontal, SafeAreaRegions vertical)
// Per-edge
public SafeAreaEdges(SafeAreaRegions left, SafeAreaRegions top,
SafeAreaRegions right, SafeAreaRegions bottom)
}
静的プリセット:SafeAreaEdges.None、SafeAreaEdges.All、SafeAreaEdges.Default。
XAML 型コンバーター
Thickness と同様のカンマ区切り構文に従います。
SafeAreaEdges="Container"
SafeAreaEdges="Container, SoftInput"
SafeAreaEdges="Container, Container, Container, SoftInput"
コントロールの既定値
| コントロール | 既定値 | 備考 |
|---|---|---|
ContentPage | None | エッジツーエッジです。Android では .NET 9 からの破壊的変更です。 |
Layout(Grid、StackLayout など) | Container | バー/ノッチを尊重し、キーボードの下に表示されます |
ScrollView | Default | iOS ではコンテンツのインセットが自動設定されます。効果があるのは Container と None のみです。 |
ContentView | None | 親の動作を継承します |
Border | None | 親の動作を継承します |
.NET 9 からの破壊的変更
ContentPage の既定値が None に変更
.NET 9 では、Android の ContentPage は Container のように動作していました。.NET 10 では、すべてのプラットフォームで既定値が None です。アップグレード後に Android のコンテンツがステータスバーの背後に表示される場合:
<ContentPage>
<ContentPage SafeAreaEdges="Container">
WindowSoftInputModeAdjust.Resize の置き換え
WindowSoftInputModeAdjust.Resize は引き続き存在し、コンパイルもできます(削除も非推奨化もされていません)が、Android 専用です。クロスプラットフォームでキーボードを避けるには、ContentPage に SafeAreaEdges="All"(または SoftInput リージョン)を設定することをおすすめします。
使用パターン
エッジツーエッジのイマーシブコンテンツ
レイアウトの既定値は Container なので、ページとレイアウトの両方に None を設定します。
<ContentPage SafeAreaEdges="None">
<Grid SafeAreaEdges="None">
<Image Source="background.jpg" Aspect="AspectFill" />
<VerticalStackLayout Padding="20" VerticalOptions="End">
<Label Text="Overlay text" TextColor="White" FontSize="24" />
</VerticalStackLayout>
</Grid>
</ContentPage>
フォームと重要なコンテンツ
<ContentPage SafeAreaEdges="All">
<VerticalStackLayout Padding="20">
<Label Text="Safe content" FontSize="18" />
<Entry Placeholder="Enter text" />
<Button Text="Submit" />
</VerticalStackLayout>
</ContentPage>
キーボードに対応するチャットレイアウト
<ContentPage>
<Grid RowDefinitions="*,Auto"
SafeAreaEdges="Container, Container, Container, SoftInput">
<ScrollView Grid.Row="0">
<VerticalStackLayout Padding="20" Spacing="10">
<Label Text="Messages" FontSize="24" />
</VerticalStackLayout>
</ScrollView>
<Border Grid.Row="1" BackgroundColor="LightGray" Padding="20">
<Grid ColumnDefinitions="*,Auto" Spacing="10">
<Entry Placeholder="Type a message..." />
<Button Grid.Column="1" Text="Send" />
</Grid>
</Border>
</Grid>
</ContentPage>
混在レイアウト:エッジツーエッジのヘッダー+Safe Area 対応の本文+キーボードフッター
<ContentPage SafeAreaEdges="None">
<Grid RowDefinitions="Auto,*,Auto">
<Grid BackgroundColor="{StaticResource Primary}">
<Label Text="App Header" TextColor="White" Margin="20,40,20,20" />
</Grid>
<ScrollView Grid.Row="1" SafeAreaEdges="Container">
<VerticalStackLayout Padding="20">
<Label Text="Main content" />
</VerticalStackLayout>
</ScrollView>
<Grid Grid.Row="2" SafeAreaEdges="SoftInput"
BackgroundColor="LightGray" Padding="20">
<Entry Placeholder="Type a message..." />
</Grid>
</Grid>
</ContentPage>
プログラムによる設定(C#)
var page = new ContentPage
{
SafeAreaEdges = SafeAreaEdges.All
};
var grid = new Grid
{
SafeAreaEdges = new SafeAreaEdges(
left: SafeAreaRegions.Container,
top: SafeAreaRegions.Container,
right: SafeAreaRegions.Container,
bottom: SafeAreaRegions.SoftInput)
};
選択の指針
| シナリオ | SafeAreaEdges の値 |
|---|---|
| フォーム、重要な入力欄 | All |
| フォトビューアー、動画プレーヤー、ゲーム | None(ページとレイアウトの両方) |
| 固定ヘッダー/フッター付きのスクロール可能なコンテンツ | Container |
| 下部入力バーがあるチャット/メッセージング | 辺ごとに設定:Container, Container, Container, SoftInput |
| Blazor Hybrid アプリ | ページには None を設定し、インセットには CSS env() を使用 |
Blazor Hybrid の統合
Blazor Hybrid アプリでは、二重のパディングを避けるために、CSS でセーフエリアを処理します。
- ページを画面端まで表示します(.NET 10 のデフォルト):
<ContentPage SafeAreaEdges="None">
<BlazorWebView HostPage="wwwroot/index.html">
<BlazorWebView.RootComponents>
<RootComponent Selector="#app" ComponentType="{x:Type local:Routes}" />
</BlazorWebView.RootComponents>
</BlazorWebView>
</ContentPage>
index.htmlにviewport-fit=coverを追加します:
<meta name="viewport" content="width=device-width, initial-scale=1.0,
maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
- CSS の
env()関数を使用します:
body {
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
}
利用可能な CSS 環境変数:env(safe-area-inset-top)、env(safe-area-inset-bottom)、env(safe-area-inset-left)、env(safe-area-inset-right)。
レガシー API からの移行
| レガシー(.NET 9 以前) | 新規(.NET 10 以降) |
|---|---|
ios:Page.UseSafeArea="True" | SafeAreaEdges="Container" |
Layout.IgnoreSafeArea="True" | SafeAreaEdges="None" |
WindowSoftInputModeAdjust.Resize | ContentPage で SafeAreaEdges="All" |
レガシーの ios:Page.UseSafeArea および Layout.IgnoreSafeArea プロパティは引き続きコンパイルできますが、非推奨としてマークされています。IgnoreSafeArea="True" は内部的に SafeAreaRegions.None にマッピングされます。WindowSoftInputModeAdjust.Resize は非推奨ではありません。引き続きサポートされていますが、Android 専用です。
<ContentPage xmlns:ios="clr-namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly=Microsoft.Maui.Controls"
ios:Page.UseSafeArea="True">
<ContentPage SafeAreaEdges="Container">
プラットフォーム固有の動作
iOS と Mac Catalyst
- セーフエリアのインセットには、ステータスバー、ナビゲーションバー、タブバー、ノッチ/Dynamic Island、ホームインジケーターが含まれます
SoftInputには、表示中のキーボードが含まれます- インセットは、画面の回転や UI の表示状態の変更に応じて自動的に更新されます
Defaultを設定したScrollViewは、UIScrollViewContentInsetAdjustmentBehavior.Automaticにマッピングされます
ナビゲーションバーの背後にコンテンツを表示するための透明なナビゲーションバー:
<Shell Shell.BackgroundColor="#80000000" Shell.NavBarHasShadow="False" />
Android
- セーフエリアのインセットには、システムバー(ステータスバー/ナビゲーションバー)とディスプレイの切り欠きが含まれます
SoftInputには、ソフトキーボードが含まれます- MAUI は内部的に
WindowInsetsCompatおよびWindowInsetsAnimationCompatを使用します - 動作は Android のバージョンや OEM のエッジツーエッジ設定によって異なります
よくある落とし穴
-
レイアウトにも
Noneを設定し忘れる。ContentPage SafeAreaEdges="None"にするとページは画面端まで表示されますが、子レイアウトのデフォルトはContainerのため、内側にパディングが残ります。完全なイマーシブ表示にするには、ページとレイアウトの両方にNoneを設定します。 -
ScrollView に直接
SoftInputを使用する。 ScrollView は独自にコンテンツのインセットを管理するため、SoftInputを無視します。ScrollView を Grid または StackLayout でラップし、そちらにSoftInputを適用します。 -
DefaultとNoneを混同する。Defaultは「このコントロールの種類に対するプラットフォームのデフォルト」を意味します。iOS の ScrollView では、自動コンテンツインセットが有効になります。Noneは「セーフエリアのパディングを一切適用しない」という意味です。 -
Blazor Hybrid で二重にパディングする。 ページに
SafeAreaEdges="Container"を設定し、さらに CSS のenv(safe-area-inset-*)を使用すると、インセットが二重に適用されます。どちらか一方を選択してください。Blazor では CSS の使用を推奨します。 -
Blazor で
viewport-fit=coverを設定し忘れる。 この meta タグがないと、iOS では CSS のenv(safe-area-inset-*)の値が常にゼロになります。 -
Android で .NET 9 の動作が続くと思い込む。 .NET 10 にアップグレードすると、Android の
ContentPageのデフォルトはNoneになります(以前は実質的にContainerでした)。以前の動作に戻すには、SafeAreaEdges="Container"を追加します。 -
新しいコードでレガシーの
ios:Page.UseSafeAreaを使用する。 古い API は iOS 専用で、非推奨です。クロスプラットフォームのセーフエリア管理には、常にSafeAreaEdgesを使用してください。
チェックリスト
- Android のアップグレード:コンテンツがステータスバーの下に表示される場合は、
SafeAreaEdges="Container"を追加 - エッジツーエッジ表示:ページとレイアウトの両方に
Noneを設定 - ScrollView のキーボード回避には、ScrollView 自体の
SafeAreaEdgesではなくラッパーの Grid を使用 - Blazor Hybrid:XAML と CSS のどちらか一方でセーフエリアを設定し、両方は使用しない
- Blazor の
index.htmlの<meta viewport>タグにviewport-fit=coverを設定 - レガシーの
UseSafeArea/IgnoreSafeAreaをSafeAreaEdgesに移行