ios_window_control_layout 0.3.0
ios_window_control_layout: ^0.3.0 copied to clipboard
Expose iOS window-control-aware layout margins to Flutter.
ios_window_control_layout #
EN / 中文
Keep Flutter content clear of iPadOS window controls using UIKit's corner-adapted margins and safe areas while retaining ownership of your AppBar, floating controls, sidebar, and padding decisions.
Screenshots #
Cupertino #

Material

Platform support #
| Platform | Behavior |
|---|---|
| iOS/iPadOS 26+ | Queries UIView.LayoutRegion from the current Flutter view. |
| Earlier iOS versions | Returns IosWindowControlLayoutData.zero. |
| Other platforms | Returns IosWindowControlLayoutData.zero without invoking a channel. |
Getting started #
flutter pub add ios_window_control_layout
Usage #
import 'package:ios_window_control_layout/ios_window_control_layout.dart';
Wrap the part of the widget tree that needs live updates:
IosWindowControlLayout(
child: MaterialApp(home: MyHomePage()),
);
Read the current snapshot during build:
final layout = IosWindowControlLayout.of(context);
final toolbarInsets = layout.horizontalAvoidance;
final floatingControlInsets = layout.horizontalSafeArea;
final windowCorners = layout.effectiveCornerRadii;
of and maybeOf establish an inherited dependency. read and maybeRead
perform a non-listening lookup. The layout refreshes after its first frame, on
window metric changes, and when the application resumes. An application can
also request a refresh explicitly:
await IosWindowControlLayout.refresh(context);
For one-off access without adding the widget to the tree:
final layout = await IosWindowControlLayout.query();
Layout properties #
| Property | Purpose |
|---|---|
isAvailable |
Tells you whether the current window provides the iOS 26 layout guides. Use your normal layout when it is false. |
baseMargins |
UIKit's ordinary content margins. This is the reference for content that does not need special corner treatment. |
horizontalMargins |
A content guide adjusted for the left and right corners. It suits AppBar actions, navigation controls, and content aligned to either side of the window. |
verticalMargins |
A content guide adjusted for the top and bottom corners. It suits content that runs close to the upper or lower edge of the window. |
baseSafeArea |
UIKit's ordinary safe area. Use it as the native reference when matching Flutter content with surrounding iOS UI. |
horizontalSafeArea |
A safe placement area for floating controls near the left or right side, such as overlay buttons and compact toolbars. |
verticalSafeArea |
A safe placement area for controls near the top or bottom, such as a floating bottom bar or dock. |
effectiveCornerRadii |
The visible radius of each physical window corner. Use it for custom cards, floating controls, or shapes that should follow only their nearby corner. |
horizontalAvoidance |
Only the extra side spacing introduced by the window corners. Add it to an AppBar, sidebar, or existing horizontal padding. |
verticalAvoidance |
Only the extra top or bottom spacing introduced by the window corners. Add it when the ordinary content margin is already present. |
horizontalSafeAreaAvoidance |
Only the extra left or right safe spacing. It is useful when Flutter's normal safe area is already applied to a floating control. |
verticalSafeAreaAvoidance |
Only the extra top or bottom safe spacing. It is useful when an existing SafeArea already protects a bottom or top control. |
hasAvoidance |
A quick check for whether the corner-adapted content margins add any space in the current window. |
The margin values are intended for ordinary content, while safe-area values
are better suited to floating or interactive controls near a real device or
window corner. Raw adapted regions can be used as complete layout guides;
avoidance values are additions to a baseline that is already applied. The
insets still describe a rectangular root view, so use effectiveCornerRadii
when a control should respond only to its nearby physical corners.
All values belong to one immutable snapshot and refresh together. iOS versions before 26 and other platforms return zero values.
Complete example
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:ios_window_control_layout/ios_window_control_layout.dart';
const _edgePadding = 16.0;
const _bottomControlMargin = 12.0;
double _horizontalInsetAt(Radius radius, double distanceFromBottom) {
if (radius.x <= 0 || radius.y <= 0 || distanceFromBottom >= radius.y) {
return 0;
}
final normalizedY = (radius.y - distanceFromBottom) / radius.y;
return radius.x *
(1 - math.sqrt(math.max(0, 1 - normalizedY * normalizedY)));
}
void main() => runApp(const ExampleApp());
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return const IosWindowControlLayout(
child: MaterialApp(home: ExamplePage()),
);
}
}
class ExamplePage extends StatelessWidget {
const ExamplePage({super.key});
@override
Widget build(BuildContext context) {
final layout = IosWindowControlLayout.of(context);
final avoidance = layout.horizontalAvoidance;
final flutterSafeArea = MediaQuery.viewPaddingOf(context);
final adaptedBottom = math.max(
flutterSafeArea.bottom,
layout.verticalSafeArea.bottom,
);
final cornerDistance = adaptedBottom + _bottomControlMargin;
final bottomPadding = EdgeInsets.only(
left: math.max(
0,
_horizontalInsetAt(layout.effectiveCornerRadii.bottomLeft, cornerDistance) -
_bottomControlMargin,
),
right: math.max(
0,
_horizontalInsetAt(layout.effectiveCornerRadii.bottomRight, cornerDistance) -
_bottomControlMargin,
),
bottom: math.max(0, adaptedBottom - flutterSafeArea.bottom),
);
return Scaffold(
appBar: AppBar(
centerTitle: true,
leadingWidth: kToolbarHeight + _edgePadding + avoidance.start,
leading: Padding(
padding: EdgeInsetsDirectional.only(
start: _edgePadding + avoidance.start,
),
child: IconButton(
onPressed: () {},
icon: const Icon(Icons.menu),
),
),
title: const Text('Window control layout'),
actions: [
Padding(
padding: EdgeInsetsDirectional.only(
end: _edgePadding + avoidance.end,
),
child: IconButton(
onPressed: () => IosWindowControlLayout.refresh(context),
icon: const Icon(Icons.refresh),
),
),
],
),
body: Padding(
padding: EdgeInsetsDirectional.only(
start: _edgePadding + avoidance.start,
end: _edgePadding + avoidance.end,
),
child: Center(
child: Text('Available: ${layout.isAvailable}'),
),
),
bottomNavigationBar: SafeArea(
top: false,
left: false,
right: false,
child: Padding(
padding: bottomPadding,
child: const Card(
margin: EdgeInsets.all(_bottomControlMargin),
child: Padding(
padding: EdgeInsets.all(16),
child: Text('Floating controls'),
),
),
),
),
);
}
}
See the example app for manual Material and Cupertino AppBar integration. The example deliberately places controls at both toolbar edges. Its window-control layout switch is on by default; turn it off to compare the untreated layout, and switch renderers while resizing an iPad window.
Development #
Run the complete non-device check suite:
make check
Run make help to list the available development commands.
Contributing #
Issues and pull requests are welcome. Run make check before submitting a
change.
Donate #
License #
This project is licensed under the MIT License. See LICENSE for the full license text.
MIT License
Copyright (c) 2026 Fries_I23
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.