Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iPhone X: Additional bottom padding for BottomNavigationBar #12099

Closed
cbracken opened this issue Sep 14, 2017 · 19 comments · Fixed by #13431
Closed

iPhone X: Additional bottom padding for BottomNavigationBar #12099

cbracken opened this issue Sep 14, 2017 · 19 comments · Fixed by #13431
Assignees
Labels
framework flutter/packages/flutter repository. See also f: labels. platform-ios iOS applications specifically

Comments

@cbracken
Copy link
Member

cbracken commented Sep 14, 2017

Steps to Reproduce

On the iPhone X, BottomNavigationBar should include additional bottom padding to avoid placing BottomNavigationBarItems under the indicator for accessing the Home screen.

See Apple's Human Interface Guidelines for iPhone X.

Dependent on issue #12098

Example:
screen shot 2017-09-12 at 15 21 34

Flutter Doctor

[✓] Flutter (on Mac OS X 10.12.6 16G29, locale en-AU, channel master)
    • Flutter at /Users/cbracken/src/flutter/flutter
    • Framework revision dd7e313317 (51 minutes ago), 2017-09-14 12:28:21 -0700
    • Engine revision 57a1445a45
    • Tools Dart version 1.25.0-dev.11.0

[✓] Android toolchain - develop for Android devices (Android SDK 25.0.3)
    • Android SDK at /Users/cbracken/Library/Android/sdk
    • Platform android-25, build-tools 25.0.3
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_112-release-b06)

[✓] iOS toolchain - develop for iOS devices (Xcode 9.0)
    • Xcode at /Applications/Xcode9.0.app/Contents/Developer
    • Xcode 9.0, Build version 9A235
    • ios-deploy 1.9.2
    • CocoaPods version 1.3.1
@cbracken cbracken added framework flutter/packages/flutter repository. See also f: labels. platform-ios iOS applications specifically labels Sep 14, 2017
@Hixie
Copy link
Contributor

Hixie commented Sep 14, 2017

This should be based on the MediaQuery.of().padding, same as appbar.

@Hixie Hixie added this to the 3: Current Milestone milestone Sep 15, 2017
@passsy
Copy link
Contributor

passsy commented Oct 8, 2017

Ran into the same problem. Still haven't found a way to detect the iPhone X. I tried https://pub.dartlang.org/packages/device_info but the simulator always returns iPhone for model instead of iPhone X

@Hixie
Copy link
Contributor

Hixie commented Oct 9, 2017

You may be able to work around this by wrapping the BottomNavigationBar in a SafeArea widget, I'm not sure where we've gotten as far as implementing that for iPhone X. @cbracken may know.

@cbracken
Copy link
Member Author

I've got a patch to fix up the bottom bar and an engine patch to add support to expose the safe area insets. To land those, we need to get our build infra/bots upgraded to Xcode 9. I've filed the appropriate tickets to get it installed and will land the update work once they've completed it.

@najeira
Copy link
Contributor

najeira commented Oct 13, 2017

I am doing as follows:

final bool iphonex = MediaQuery.of(context).size.height >= 812.0;
final double bottomPadding = iphonex ? 16.0 : 0.0;
// add the padding to arround CupertinoTabBar

@Hixie
Copy link
Contributor

Hixie commented Oct 13, 2017

If you're also targeting android you may also wish to key it on the platform.

@cbracken cbracken self-assigned this Oct 31, 2017
@passsy
Copy link
Contributor

passsy commented Nov 7, 2017

Here is my current workaround:

Code

import 'dart:io';

import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';

// workaround for iPhone X which draws navigation in the bottom of the screen.
// Wait until https://github.com/flutter/flutter/issues/12099 is fixed
class IPhoneXPadding extends Container {
  final Widget child;

  IPhoneXPadding({
    @required this.child,
  });

  @override
  Widget build(BuildContext context) {
    var mediaQueryData = MediaQuery.of(context);
    if (!_isIPhoneX(mediaQueryData)) {
      // fallback for all non iPhone X
      return child;
    }

    var homeIndicatorHeight =
    // TODO verify exact values
    mediaQueryData.orientation == Orientation.portrait ? 22.0 : 20.0;

    var outer = mediaQueryData.padding;
    var bottom = outer.bottom + homeIndicatorHeight;
    return new MediaQuery(data: new MediaQueryData(
        padding: new EdgeInsets.fromLTRB(
            outer.left, outer.top, outer.right, bottom)),
        child: child
    );
  }

  bool _isIPhoneX(MediaQueryData mediaQuery) {
    if (Platform.isIOS) {
      var size = mediaQuery.size;
      if (size.height == 812.0 || size.width == 812.0) {
        return true;
      }
    }
    return false;
  }
}

Usage

  @override
  Widget build(BuildContext context) {
    return new IPhoneXPadding(
      child: new Scaffold(
        bottomNavigationBar: _bottomNavBar,
        body: /*...*/,
      ),
    );
  }

Result

screen shot 2017-11-07 at 19 11 18

Problem

The Scaffold doesn't expand the BottomNavigationbar to the bottom. It could look OKish if the BottomNavigationbar wouldn't cast this elevation shadow

@cbracken
Copy link
Member Author

cbracken commented Nov 7, 2017

Small update: I've got engine work in progress to resolve #12895, which is a pre-req for getting a universal solution for iPhone X issues. Once that's in, I'll clean up my preliminary patches for the bottom nav (as well as list and grid tiles).

In the meantime, if you need a quick workaround, you could apply an inline patch to the bottom nav in a branch. You may need to tweak the numbers (25.0, 34.0) a little. Also fair warning that this is a quick adaptation of the preliminary patches mentioned above (which depend on upcoming engine changes), in order to work with a manual heuristic-based iPhone X check, so I haven't tried these exact patches out locally ;)

BottomNavigationBar:

--- i/packages/flutter/lib/src/material/bottom_navigation_bar.dart
+++ w/packages/flutter/lib/src/material/bottom_navigation_bar.dart
@@ -435,6 +436,9 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
   @override
   Widget build(BuildContext context) {
+    bool isIphoneX = ...;
+    double bottomPadding = isIphoneX ? 25.0 : 0.0;
     Color backgroundColor;
     switch (widget.type) {
       case BottomNavigationBarType.fixed:
@@ -452,7 +456,7 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
           ),
         ),
         new ConstrainedBox(
-          constraints: const BoxConstraints(minHeight: kBottomNavigationBarHeight),
+          constraints: new BoxConstraints(minHeight: kBottomNavigationBarHeight + bottomPadding),
           child: new Stack(
             children: <Widget>[
               new Positioned.fill(
@@ -464,7 +468,10 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
               ),
               new Material( // Splashes.
                 type: MaterialType.transparency,
-                child: _createContainer(_createTiles()),
+                child: new Padding(
+                  padding: new EdgeInsets.only(bottom: bottomPadding),
+                  child: _createContainer(_createTiles()),
+                ),
               ),
             ],
           ),

CupertinoTabBar:

--- i/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart
+++ w/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart
@@ -92,6 +92,7 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {

   @override
   Widget build(BuildContext context) {
+    bool isIphoneX = ...;
+    double bottomPadding = isIphoneX ? 34.0 : 0.0;
     Widget result = new DecoratedBox(
       decoration: new BoxDecoration(
         border: const Border(
@@ -105,7 +106,7 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {
       ),
       // TODO(xster): allow icons-only versions of the tab bar too.
       child: new SizedBox(
-        height: _kTabBarHeight,
+        height: _kTabBarHeight + bottomPadding,
         child: IconTheme.merge( // Default with the inactive state.
           data: new IconThemeData(
             color: inactiveColor,
@@ -119,10 +120,13 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {
               fontWeight: FontWeight.w500,
               color: inactiveColor,
             ),
-            child: new Row(
-              // Align bottom since we want the labels to be aligned.
-              crossAxisAlignment: CrossAxisAlignment.end,
-              children: _buildTabItems(),
+            child: new Padding(
+              padding: new EdgeInsets.only(bottom: bottomPadding),
+              child: new Row(
+                // Align bottom since we want the labels to be aligned.
+                crossAxisAlignment: CrossAxisAlignment.end,
+                children: _buildTabItems(),
+              ),
             ),
           ),
         ),

@cbracken
Copy link
Member Author

cbracken commented Nov 8, 2017

I should clarify - the additional bottom padding is 34.0 for vertical orientation, 21.0 for horizontal. In both cases, minus 9.0 on the bottom nav for the moment.

@cbracken
Copy link
Member Author

cbracken commented Dec 8, 2017

As of #13442, this is fixed on master for both the Material BottomNavigationBar and the CupertinoNavigationBar.

@cbracken cbracken closed this as completed Dec 8, 2017
@cbracken
Copy link
Member Author

cbracken commented Dec 8, 2017

@najeira @passsy as of the above commit, you can get at iPhone X safe area insets via MediaQuery.of(context).padding. The bottom padding provides the additional bottom padding for the iPhone X home indicator, if present.

@deborah-ufw
Copy link

deborah-ufw commented Feb 12, 2018

@passsy your detection for iPhone X worked for me, would you share how you came up with 812 as the size?

bool _isIPhoneX(MediaQueryData mediaQuery) {
    if (Platform.isIOS) {
      var size = mediaQuery.size;
      if (size.height == 812.0 || size.width == 812.0) {
        return true;
      }
    }
    return false;
  }

@cbracken
Copy link
Member Author

cbracken commented Feb 12, 2018

@deborah-ufw manual iPhone X detection should no longer be necessary. The framework now includes support for iOS safe area insets. See the SafeArea class for details. If, rather than automatically applying padding, you need the pixel values of the safe area insets, they've available via MediaQuery.of(context).padding. .bottom for the iPhone X home indicator safe area height, .left/.right for the notch in landscape mode (and symmetric inset on the other side), .top for the status bar/notch in portrait mode.

As a bonus, these will work with all future iPhone X-like iPhone models, as well as any upcoming 'creative' displays on Android phones as well -- the only one I'm currently aware of is the Essential Phone, which has a camera notch, but I'm sure there'll be more.

@deborah-ufw
Copy link

deborah-ufw commented Feb 12, 2018 via email

@cbracken
Copy link
Member Author

cbracken commented Feb 12, 2018

812 is the iPhone X's screen height in logical pixels (375 x 812); I'm assuming the reason for checking both width/height was to deal with both screen orientations.

@deborah-ufw
Copy link

deborah-ufw commented Feb 12, 2018 via email

@cbracken
Copy link
Member Author

cbracken commented Feb 12, 2018

Flutter computes logical pixels consistently with what the device OS reports. Apple documents the iPhone X screen as 375 x 812 logical pixels (see the bit under screen size).

In terms of what computations Flutter does in order to report the dimensions in MediaQuery, we're just reporting what the underlying OS reports back to us, multiplied by the scale factor they report to us. An iPhone X reports a logical resolution of 375 x 812 and a scale factor of 3. MediaQuery.of(context).size returns the size in logical pixels (375 x 812 on an iPhone X) and MediaQuery.of(context).devicePixelRatio returns the scale factor (3 on an iPhone X).

In terms of how we get those values on iOS, the scale is via a call to [UIScreen mainScreen].scale and the size just comes from the view's self.bounds (engine code here and MediaQuery code here ) as such it's worth noting that the above check for the screen width or length being 812 is only safe in the case where your Flutter view is full-screen -- which is fortunately the case for most people writing a Flutter app. :)

Using the MediaQuery.of(context).padding should always be safe regardless of whether the flutter view is a full-screen view or just a subview of your app.

@cbracken
Copy link
Member Author

While we're talking screen sizes -- I find The Ultimate Guide to iPhone Resolutions far handier than Apple's official docs. Leaving a link here in case others find it useful.

@github-actions
Copy link

github-actions bot commented Sep 3, 2021

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 3, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
framework flutter/packages/flutter repository. See also f: labels. platform-ios iOS applications specifically
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants