[Flutter] showModalBottomSheetを使ったら特定機種(iPhone SE 3rd)でレンダリングオーバーフローエラーが出たので対処する
こんにちは、CX事業本部 Delivery部の若槻です。
今回は、FlutterでshowModalBottomSheetを使ったら特定機種(iPhone SE 3rd)でレンダリングオーバーフローエラーが出たので対処してみました。
showModalBottomSheetとは
Flutterのモバイルアプリ実装で大きめのモーダル領域を使用したい場合はshowModalBottomSheet
を使うと便利です。
サンプルコードを試してみます。(height
は500
に修正しています。)
import 'package:flutter/material.dart'; void main() => runApp(const BottomSheetApp()); class BottomSheetApp extends StatelessWidget { const BottomSheetApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('Bottom Sheet Sample')), body: const BottomSheetExample(), ), ); } } class BottomSheetExample extends StatelessWidget { const BottomSheetExample({super.key}); @override Widget build(BuildContext context) { return Center( child: ElevatedButton( child: const Text('showModalBottomSheet'), onPressed: () { showModalBottomSheet<void>( context: context, builder: (BuildContext context) { return Container( height: 500, color: Colors.amber, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: <Widget>[ const Text('Modal BottomSheet'), ElevatedButton( child: const Text('Close BottomSheet'), onPressed: () => Navigator.pop(context), ), ], ), ), ); }, ); }, ), ); } }
下記のように画面下方からモーダルシートを出すことができます。左がiPhone SE 3rd、右がiPhone 14 Pro Maxです。
画面下部からオレンジ背景のモーダルを表示した様子。このモーダルシート上にWidgetを設置することができます。
事象
ここでshowModalBottomSheet
内に高さを絶対値指定したWidgetを配置したとします。下記では代替としてSizedBox(height: 400)
を追加しています。
class BottomSheetExample extends StatelessWidget { const BottomSheetExample({super.key}); @override Widget build(BuildContext context) { return Center( child: ElevatedButton( child: const Text('showModalBottomSheet'), onPressed: () { showModalBottomSheet<void>( context: context, builder: (BuildContext context) { return Container( height: 500, color: Colors.amber, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: <Widget>[ const Text('Modal BottomSheet'), ElevatedButton( child: const Text('Close BottomSheet'), onPressed: () => Navigator.pop(context), ), SizedBox(height: 400), ], ), ), ); }, ); }, ), ); } }
するとモーダルを開いた時に左のiPhone SE 3rdでレンダリングオーバーフローエラーが起きるようになりました。iPhone SE 3rdではボタンの高さ分だけモーダルが隠れてしまい、絶対高さ500px以下となりオーバーフローが発生してしまったようです。
══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════ The following assertion was thrown during layout: A RenderFlex overflowed by 101 pixels on the bottom. The relevant error-causing widget was: Column Column:file:///Users/wakatsuki.ryuta/projects/cm-rwakatsuki/flutter_sample_app/lib/main.dart:36:26 To inspect this widget in Flutter DevTools, visit: http://127.0.0.1:9104/#/inspector?uri=http%3A%2F%2F127.0.0.1%3A52473%2FnO44gmovx8w%3D%2F&inspectorRef=inspector-0 The overflowing RenderFlex has an orientation of Axis.vertical. The edge of the RenderFlex that is overflowing has been marked in the rendering with a yellow and black striped pattern. This is usually caused by the contents being too big for the RenderFlex. Consider applying a flex factor (e.g. using an Expanded widget) to force the children of the RenderFlex to fit within the available space instead of being sized to their natural size. This is considered an error condition because it indicates that there is content that cannot be seen. If the content is legitimately bigger than the available space, consider clipping it with a ClipRect widget before putting it in the flex, or using a scrollable container rather than a Flex, like a ListView. The specific RenderFlex in question is: RenderFlex#429d5 relayoutBoundary=up1 OVERFLOWING: creator: Column ← Center ← ColoredBox ← ConstrainedBox ← Container ← NotificationListener<DraggableScrollableNotification> ← DefaultTextStyle ← AnimatedDefaultTextStyle ← _InkFeatures-[GlobalKey#9c376 ink renderer] ← NotificationListener<LayoutChangedNotification> ← PhysicalModel ← AnimatedPhysicalModel ← ⋯ parentData: offset=Offset(106.0, 0.0) (can use size) constraints: BoxConstraints(0.0<=w<=375.0, 0.0<=h<=363.9) size: Size(163.0, 363.9) direction: vertical mainAxisAlignment: center mainAxisSize: min crossAxisAlignment: center verticalDirection: down ◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤ ════════════════════════════════════════════════════════════════════════════════════════════════════
アプリケーションの仕様の都合でモーダルの高さの絶対値を500
から変えたくない場合に、解決する方法はあるのでしょうか。
試行:SafeArea対応では解消しなかった
iOS端末では端末毎にセーフエリア(SafeArea)を取得することができ、アプリケーションから取得してレンダリング領域の調整に使用することができます。
FlutterでもSafeArea Widgetが用意されています。
またshowModalBottomSheetではuseSafeArea
プロパティがあり、こちらを合わせて有効にすれば良さそうです。
The [useSafeArea] parameter specifies whether a [SafeArea] is inserted. Defaults to false. If false, no SafeArea is added and the top padding is consumed using [MediaQuery.removePadding].
(日本語訳)
[useSafeArea] パラメータは、[SafeArea] を挿入するかどうかを指定します。デフォルトは false です。 false の場合、SafeArea は追加されず、上部のパディングは [MediaQuery.removePadding] を使用して消費されます。
このSafeAreaを使って解消できるか試してみます。
先程のコードで、showModalBottomSheet内の実装をSafeArea Widgetでラップします。
class BottomSheetExample extends StatelessWidget { const BottomSheetExample({super.key}); @override Widget build(BuildContext context) { return Center( child: ElevatedButton( child: const Text('showModalBottomSheet'), onPressed: () { showModalBottomSheet<void>( useSafeArea: true, context: context, builder: (BuildContext context) { return SafeArea( child: Container( height: 500, color: Colors.amber, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: <Widget>[ const Text('Modal BottomSheet'), ElevatedButton( child: const Text('Close BottomSheet'), onPressed: () => Navigator.pop(context), ), SizedBox(height: 400), ], ), ), )); }, ); }, ), ); } }
しかし解消はしませんでした。
解決:SingleChildScrollViewで抑制できた
SingleChildScrollViewを導入することによりエラーを抑制することができました。これによりモーダルシートの内部をスクロール可能にしています。
class BottomSheetExample extends StatelessWidget { const BottomSheetExample({super.key}); @override Widget build(BuildContext context) { return Center( child: ElevatedButton( child: const Text('showModalBottomSheet'), onPressed: () { showModalBottomSheet<void>( context: context, builder: (BuildContext context) { return SingleChildScrollView( child: Container( height: 500, color: Colors.amber, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: <Widget>[ const Text('Modal BottomSheet'), ElevatedButton( child: const Text('Close BottomSheet'), onPressed: () => Navigator.pop(context), ), SizedBox(height: 400), ], ), ), )); }, ); }, ), ); } }
iPhone SE 3rdでは画面下部を表示するためにスクロール操作が必要となります。iPhone 14 Pro Maxではスクロールをせずにモーダル全体が表示されてします。
参考
以上