An enquiry I had from a Hans Jakobsen of EarMaster (brilliant music teaching software for PC, Mac, iPad and iPhone) recently was regarding an apparent “lag” for the OnMouseDown event for controls near the edge of the screen on devices with later versions of iOS. This article poses a workaround for the problem.

A description of the problem

Hans had already done some research about the issue, and came across this link, and he asked me to look into how to solve it in Delphi.

Implementing preferredScreenEdgesDeferringSystemGestures

Firstly, I needed to find exactly where to implement the changes. The article says that in order to indicate which edges should take “precedence” (over system gestures) in your app, the preferredScreenEdgesDeferringSystemGestures method needs to be “overridden” in the class that implements UIViewController. In the case of Delphi, this is TFMXViewController in the FMX.Platform.iOS unit. Fortunately, this class is declared in the implementation section, so the unit can be “patched” without having to recompile any other units. It was then a simple matter of adding the method to the FMXViewController interface and implementing it in TFMXViewController (see changes below)

Telling iOS which edges to prefer

Next up was how to determine what edges are “preferred”, so I used an old trick of defining a property that could be set outside of FMX.Platform.iOS, which that unit could then use. The DW.ScreenEdgeManager unit declares a record called TScreenEdgeManager, which has a private field FPreferredScreenEdges which is a set of the desired “preferred” edges. The private field value is set inside the SetPreferredScreenEdges method, and that method ensures that the changes are flagged in the system by calling setNeedsUpdateOfScreenEdgesDeferringSystemGestures. This indicates to iOS that the next time a system gesture happens, it will need to call preferredScreenEdgesDeferringSystemGestures on the view controller to discover which edges apply.

The setNeedsUpdateOfScreenEdgesDeferringSystemGestures (as per the article I linked to) method was added in iOS 11, and since Delphi has a bit of catching up to do with iOS API changes, a bit of “hackery” was needed to call this method as can be seen by the declaration of UIViewControllerEx and TUIViewControllerEx, which essentially “extends” UIViewController.

The demo

I’ve created a demo that uses the patch for FMX.Platform.iOS and the DW.ScreenEdgeManager unit to demonstrate how this works, and the difference between when there’s no “preferred” edges and where they are. Instead of creating a patch file this time, I’m detailing the changes you need to make. Copy FMX.Platform.iOS from the Delphi source\fmx folder into the demo project folder, and:

Add DW.ScreenEdgeManager to the implementation uses clause

  System.Classes, System.SysUtils, System.Types, System.UITypes, System.TypInfo, System.Messaging, System.RTLConsts,
  System.Math, Macapi.ObjCRuntime, Macapi.CoreFoundation, Macapi.Helpers, iOSapi.CocoaTypes, iOSapi.Foundation,
  iOSapi.CoreGraphics, iOSapi.Helpers, FMX.Graphics, FMX.Consts, FMX.Controls, FMX.Canvas.GPU, FMX.TextLayout,
  FMX.Text, FMX.Styles, FMX.Gestures, FMX.Context.GLES, FMX.Forms3D, FMX.Utils, FMX.Graphics.iOS, FMX.Context.GLES.iOS,
  FMX.Controls.iOS, FMX.Gestures.iOS, FMX.Helpers.iOS, FMX.Dialogs.iOS, FMX.Platform, FMX.Platform.Timer.iOS,
  FMX.Platform.SaveState.iOS, FMX.MultiTouch.iOS, FMX.Platform.Metrics.iOS, FMX.Platform.Device.iOS,
  FMX.Platform.Screen.iOS, FMX.Platform.Logger.iOS,
  DW.ScreenEdgeManager; // <-------

Add the preferredScreenEdgesDeferringSystemGestures method to FMXViewController and TFMXViewController

  FMXViewController = interface(UIViewController)
    // Some code snipped here
    function preferredScreenEdgesDeferringSystemGestures: UIRectEdge; cdecl; // <------

  TFMXViewController = class(TOCLocal)
    // Some code snipped here
    function preferredScreenEdgesDeferringSystemGestures: UIRectEdge; cdecl; // <------

Add the implementation of TFMXViewController.preferredScreenEdgesDeferringSystemGestures

function TFMXViewController.preferredScreenEdgesDeferringSystemGestures: UIRectEdge;
  UIRectEdges: array[TScreenEdge] of UIRectEdge = (UIRectEdgeTop, UIRectEdgeLeft, UIRectEdgeBottom, UIRectEdgeRight);
  LScreenEdge: TScreenEdge;
  Result := UIRectEdgeNone;
  for LScreenEdge := Low(TScreenEdge) to High(TScreenEdge) do
    if LScreenEdge in TScreenEdgeManager.PreferredScreenEdges then
      Result := Result or UIRectEdges[LScreenEdge];

To see the effect of the changes, run the app on a device which (for example) has a “swipe” bar at the bottom, and tap and hold the button at the point where the swipe bar is. It might take a couple of tries, however you will notice that most of the time, there is a noticeable delay between when the button is tapped and the rectangle changes color. Now check the “Fix lag” checkbox and repeat the earlier steps. The rectangle changes color immediately, every time.