Supporting the Text Scale setting in Delphi (iOS and Android)

In Delphi (Firemonkey), when you drop a TLabel onto a form, it will have a default font size of 12. Have you ever noticed that changing the size to 11 or 13 doesn’t give the results you expect, and both are usually smaller than when set to 12. Strange!

Is it a bug? Well no, it actually isn’t.

This is because Delphi treats font size 12 as a special value which isn’t necessary 12 pts in size. It’s best to think of FontSize: 12 as “default” instead and will apply the font scaling setting from your phone.

The problem is that the font scaling only applies when your font size is 12, so your app won’t honour the phone text scaling option for headings, captions etc which have smaller font sizes applied. Ideally you’d want all test within your UI to uniformly scale up or down as needed.

Luckily, it’s easy to ask the operating system for the scaling and applying it yourself.

{$IFDEF IOS}
uses iOSapi.UIKit;
{$ENDIF IOS}
{$IFDEF ANDROID}
uses Androidapi.JNI.GraphicsContentViewText;
{$ENDIF ANDROID}

function FontScale: Single;
{$IFDEF ANDROID}
var
  Resources: JResources;

  Configuration: JConfiguration;
{$ENDIF ANDROID}
{$IFDEF IOS}
var
  f: UIFontDescriptor;

{$ENDIF IOS}
begin

  Result := 1.0;

  {$IFDEF ANDROID}

  if TAndroidHelper.Context <> nil then

  begin

    Resources := TAndroidHelper.Context.getResources;
    if Resources <> nil then

    begin
      Configuration := Resources.getConfiguration;
      if Configuration <> nil then
        Result := Configuration.fontScale;

    end;

  end;

  {$ENDIF ANDROID}
  {$IFDEF IOS}
  f := TUIFontDescriptor.OCClass.preferredFontDescriptorWithTextStyle(
          UIFontTextStyleBody);
  Result := f.pointSize / 17.0;

  {$ENDIF IOS}
end;

Note: When designing your app and deciding on the font sizes for labels, you should do this relative to a scaling of 1.0 so they’re consistent. Applying the FontScale() function value above will turn these “logical” font sizes into “actual” font sizes which respect the phone’s font scaling setting.

The function returns a scaling value you could directly multiply your logical font size with before applying to your control to get the desired effect. E.g.:

label1.Font.Size:=14 * FontScale;

The end result will be a UI which respects the text scaling setting on the device.

Tip: For ease I recommend creating a helper function which returns the actual font size from the logical one, such as:

function LogToActualFontSize(const logicalSize: Single): Single;
begin
  Result:=logicalSize * FontScale;
end;

How does this work?

Android is fairly easy – it returns the actual scaling factor to apply. E.g. the default is 1.0, smaller may be 0.8 and larger may be 1.5. We can apply these directly.

iOS is a little trickier as we can’t get a scaling directly (for good reason). Instead, we get what we need by asking the type system for the current size of the body text style.
This will already be scaled by the text size setting, so we can simply divide it with the default body style font size at “normal” test sizing (17.0) to get the ratio – i.e. scaling factor.

Caveats and Things to be wary of…

The above will give you an easy way to achieve a scaling app but doesn’t address a few issues listed below. Note that I have no solution for these but if anyone discovers one, please add a comment.

  • Font Sizes of 12 will still apply the scaling regardless of the above, so you will want to ignore those cases to avoid double-scaling. Personally, I use a font.size of 12.1 to avoid this but you could put a “if logicalSize = 12 then return 12;” line in the helper function.
  • If the user changes the font scaling setting on their phone while the app is open in the background, the app won’t get notified of the change. There are ways to do this through observers but it’s beyond the scope of this article. See this post for more details:
    https://stackoverflow.com/questions/18951332/how-to-detect-dynamic-font-size-changes-from-ios-settings
  • Test very carefully for all text scale values! Your labels are likely to be a fixed size in your UI but your font size won’t stay the same as when it was designed so may introduce cropping or overflow issues.
  • If your font text doesn’t change then the most likely culprit will be the StyledSettings property of the label/control. By default, all labels will use the default font size set by the style unless the font size is changed at design time. If you’re setting the size at runtime instead, the new size will be ignored. Uncheck the Size option in this property to avoid Firemonkey ignoring the font size you set in code, or do the following in your code:

    label.StyledSettings:=label.StyledSettings – [TStyledSetting.Size];

I hope this helps you achieve a better behaving and more accessible user experience in your Delphi app.

For reference, the following articles were really useful in discovering how to do what’s described in this article:

https://programmersought.com/article/15291678656/

https://gist.github.com/zacwest/916d31da5d03405809c4

Welcome to FMX (Firemonkey) Blog

I’ve been developing with Delphi for 20 years since Delphi 1 and it’s been an interesting ride, with lots learned each year. I took a break during the “dark years” of Delphi 8 through XE but since then have been using every version of Delphi.

Delphi may not be as popular a language as it used to be, mainly due to fashion changes, the trendy new developers moving away from all-in-one IDEs, and preferring Open Source, web-originating frameworks and languages. However, it’s still a powerful, modern language capable of incredible things, and well worth looking into if you’re new to the coding arena and want to try something that is genuinely cross-platform.

The Firemonkey framework has been the most interesting additions to the Delphi family, adding support for iOS, Android and MacOS, both 32bit and 64bit variants – and now even Linux Desktop and server support. It’s a bold move on Embarcadero’s part but one that will hopefully pay off for them.

Our mainstream product, MyShiftPlanner, is one of the most popular shift worker time management app on the stores and is written completely in Delphi using Firemonkey. It’s taken 5 years to get it where it is and was initially written in XE5 but now runs like a dream under Delphi Rio 10.3.3.

I’ve learned a lot along the way which I wanted to share with anyone else who wants to take advantage of the power of this truly cross-platform framework.

Each article covers a specific topic but mostly helping to extend what Firemonkey does out-of-the-box so you can take advantage of even more for your apps. The blogs will cover how to add in-app subscriptions to your apps, how to include third party frameworks, use more native controls than are supplied by Firemonkey and more. I’ll also include links to other people’s blogs which offer similar benefits so you can find what you need in one place.

I hope you enjoy this blog and find it useful. Please continue to share/ promote the benefits of Delphi and support your fellow Delphi community developers.

Happy coding!