|

Why text labels on Flutter’s MaterialApp looks worse on iOS and macOS?

Save or share to

There’s a long-standing bug on Flutter that, when you decide to use MaterialApp on your project, the letters on the system font for iOS and macOS appears a little bit sparsed.

I’ve taken my time to investigate this issue, and found two interesting reasons behind the issue.

First, I think the changes in #41101 (the Flutter Pull Request that is blamed to cause this issue) should not be the one to blame. In fact, they have rendered the iOS’ system fonts in the correct way. The major cause behind all of this is the Material Design’s default letter spacing/tracking values which contradicts that of Apple. To give some examples:

StyleFont SizeMaterial’s letter-spacingApple HIG’s letter-spacingDifference
Body Large160.5-0.310.81
Body Medium140.25-0.150.4
Body Small120.400.4

And after adjusting from Material to Apple’s letter spacing values, I can successfully turned my Flutter app from this:

to this (with tweaking the headings to use semibold instead):

But there’s one more problem: the updated Display and Heading styles are still awkwardly spaced. This is where I learned that Flutter does not consider using SF Pro Display for text sized at 20px and larger, as recommended and implemented by Apple themselves. Overriding the font family is also necessary to fix this spacing issue, so my app can finally look more native than ever:

This is the hack that I used for it. But don’t be excited yet because soon I found another issue.

  /// Adjusts font spacing for iOS and macOS devices
  /// See https://github.com/flutter/flutter/issues/43998
  static ThemeData adjustFontSpacing(ThemeData theme) {
    if (kIsWeb || !shouldUseCupertino()) return theme;
    return theme.copyWith(
      textTheme: theme.textTheme.copyWith(
        bodyLarge: theme.textTheme.bodyLarge?.copyWith(letterSpacing: -0.31),
        bodyMedium: theme.textTheme.bodyMedium?.copyWith(letterSpacing: -0.15),
        bodySmall: theme.textTheme.bodySmall?.copyWith(letterSpacing: 0),
        displayLarge: theme.textTheme.displayLarge?.copyWith(
          fontWeight: FontWeight.w600,
          fontFamily: "SF Pro Display",
          letterSpacing: 0.29,
        ),
        displayMedium: theme.textTheme.displayMedium?.copyWith(
          fontWeight: FontWeight.w600,
          fontFamily: "SF Pro Display",
          letterSpacing: 0.35,
        ),
        displaySmall: theme.textTheme.displaySmall?.copyWith(
          fontWeight: FontWeight.w600,
          fontFamily: "SF Pro Display",
          letterSpacing: 0.37,
        ),
        headlineLarge: theme.textTheme.headlineLarge?.copyWith(
          fontWeight: FontWeight.w600,
          fontFamily: "SF Pro Display",
          letterSpacing: 0.41,
        ),
        headlineMedium: theme.textTheme.headlineMedium?.copyWith(
          fontWeight: FontWeight.w600,
          fontFamily: "SF Pro Display",
          letterSpacing: 0.38,
        ),
        headlineSmall: theme.textTheme.headlineSmall?.copyWith(
          fontWeight: FontWeight.w600,
          fontFamily: "SF Pro Display",
          letterSpacing: 0.07,
        ),
        labelLarge: theme.textTheme.labelLarge?.copyWith(letterSpacing: -0.15),
        labelMedium: theme.textTheme.labelMedium?.copyWith(letterSpacing: 0),
        labelSmall: theme.textTheme.labelSmall?.copyWith(letterSpacing: 0.06),
        titleLarge: theme.textTheme.titleLarge?.copyWith(letterSpacing: -0.26),
        titleMedium: theme.textTheme.titleMedium?.copyWith(letterSpacing: -0.31),
        titleSmall: theme.textTheme.titleSmall?.copyWith(letterSpacing: -0.15),
      ),
    );
  }

Someone then said that the fix does not work in iOS and iPadOS apps. And after validating the issue, well, Flutter is still also stuck on another old bug where the “SF Pro Display” font is still resolved as “SF Pro Text” on those platforms.

That means, if your app supports iOS and iPadOS, you should use this hack instead (the above hack can also be used together if you mind detecting whether the platform is macOS or not.

  static ThemeData adjustFontSpacing(ThemeData theme) {
    if (!shouldUseCupertino()) return theme;
    return theme.copyWith(
      textTheme: theme.textTheme.copyWith(
        bodyLarge: theme.textTheme.bodyLarge?.copyWith(letterSpacing: -0.31),
        bodyMedium: theme.textTheme.bodyMedium?.copyWith(letterSpacing: -0.15),
        bodySmall: theme.textTheme.bodySmall?.copyWith(letterSpacing: 0),
        displayLarge: theme.textTheme.displayLarge?.copyWith(
          fontWeight: FontWeight.w600,
          letterSpacing: 0.29 - 1.5,
        ),
        displayMedium: theme.textTheme.displayMedium?.copyWith(
          fontWeight: FontWeight.w600,
          letterSpacing: 0.35 - 1.5,
        ),
        displaySmall: theme.textTheme.displaySmall?.copyWith(
          fontWeight: FontWeight.w600,
          letterSpacing: 0.37 - 1.5,
        ),
        headlineLarge: theme.textTheme.headlineLarge?.copyWith(
          fontWeight: FontWeight.w600,
          letterSpacing: 0.41 - 1.5,
        ),
        headlineMedium: theme.textTheme.headlineMedium?.copyWith(
          fontWeight: FontWeight.w600,
          letterSpacing: 0.38 - 1.5,
        ),
        headlineSmall: theme.textTheme.headlineSmall?.copyWith(
          fontWeight: FontWeight.w600,
          letterSpacing: 0.07 - 1.5,
        ),
        labelLarge: theme.textTheme.labelLarge?.copyWith(letterSpacing: -0.15),
        labelMedium: theme.textTheme.labelMedium?.copyWith(letterSpacing: 0),
        labelSmall: theme.textTheme.labelSmall?.copyWith(letterSpacing: 0.06),
        titleLarge: theme.textTheme.titleLarge?.copyWith(letterSpacing: -0.26),
        titleMedium:
            theme.textTheme.titleMedium?.copyWith(letterSpacing: -0.31),
        titleSmall: theme.textTheme.titleSmall?.copyWith(letterSpacing: -0.15),
      ),
    );
  }

Thanks for reading this article! By the way, we’re also working on finishing these interesting posts. Revisit this site soon or follow us to see them once they’re published!

[display-posts post_status=”future” include_link=”false” wrapper_id=”future-list”]

Save or share to

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *