ios_window_control_layout

Package Likes Points

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

Cupertino before and after applying window control layout

Material

Material before and after applying window control layout

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.

"Buy Me A Coffee" Alipay WechatPay

ETH BTC

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.