ios_window_control_layout 0.2.0
ios_window_control_layout: ^0.2.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;
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 margins suit ordinary content and window-control avoidance. Safe-area
regions suit floating controls placed near real device or window corners. Use
horizontalSafeArea or verticalSafeArea directly when the component needs
the raw adapted safe area.
Avoidance values are additional space, not replacements for ordinary padding
or safe-area insets. Margin avoidance is adapted margins - base margins;
safe-area avoidance is adapted safe area - base safe area. Negative values
are clamped to zero. Keep the baseline and add avoidance only when the baseline
is already applied, as in the complete example below.
All six regions belong to one immutable snapshot and refresh together. iOS versions before 26 and other platforms return zero values.
Complete example
import 'package:flutter/material.dart';
import 'package:ios_window_control_layout/ios_window_control_layout.dart';
const _edgePadding = 16.0;
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 floatingSafeArea = layout.horizontalSafeArea;
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: Padding(
padding: EdgeInsetsDirectional.only(
start: floatingSafeArea.start,
end: floatingSafeArea.end,
bottom: floatingSafeArea.bottom,
),
child: const Card(
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.