Recently I had a project where I had to somehow get the touch points for Android and IOS. I needed coordinates of all touch points at any time, with the correct event and movements. After a short research it turned out that Delphi XE5 has no such support. The internals of FMX framework does support that to some degree because it needs that to properly do multi-touch gestures. But that is of no help, because gestures are fired only when complete. Here I needed raw data to get the touch points or coordinates.

After seeing that there is no other way but to do it myself or to use another development tool, I decided I will do it myself. One afternoon later the support was done. I managed to support IOS and Android which is all I am interested in at the moment. Android is supported without any ugly hacks, but IOS needed a change of FMX.Platform.IOS, which is bad, but there is no other way. I just don’t understand why EMB choose to close the access to such important pieces of information. Under IOS there is no way to get that data, but to patch the FMX.Platform.IOS.

I have added the code to my SVN with the example that works on IOS and Android. But because I cannot distribute the Delphi source code I did not add FMX.Plarform.IOS. If EMB allows me to I will upload it, otherwise you are on your own there. I can only tell you how to do it yourselves.

To start using the code just add Cromis.Multitouch.Custom to uses clause and then declare the OnTouchEvent like this:

function TForm1.PointsToString(const Event: TTouchEvent): string;
var
  I: Integer;
begin
  Result := '';
 
  for I := 0 to Length(Event.Points) - 1 do
  begin
    if I = 0 then
      Result := Format('[%f:%f]', [Event.Points[I].Position.X,
                                   Event.Points[I].Position.Y])
    else
      Result := Result + ' ' + Format('[%f:%f]', [Event.Points[I].Position.X,
                                                  Event.Points[I].Position.Y]);
  end;
end;
 
procedure TForm1.OnTouchEvent(const Event: TTouchEvent);
begin
  lbMultiTouch.Items.BeginUpdate;
  try
    case Event.EventType of
      teDown: lbMultiTouch.Items.Add(Format('DOWN: %s', [PointsToString(Event)]));
      teMove: lbMultiTouch.Items.Add(Format('MOVE: %s', [PointsToString(Event)]));
      teUp: lbMultiTouch.Items.Add(Format('UP: %s', [PointsToString(Event)]));
    end;
  finally
    lbMultiTouch.Items.EndUpdate;
  end;
end;

And register it like this:

procedure TForm1.FormCreate(Sender: TObject);
begin
  InitializeTouchListener;
  TouchEventListener.AddHandler(OnTouchEvent);
end;

That is all there is to it. Be carefull because the events are global and not tied to any control on the screen. It just reports all the events in global coordinates. The event is a record:

  TTouchEventType = (teDown, teMove, teUp, teCanceled);
 
  TTouchPoint = record
    ID: Integer;
    Position: TPointF;
    History: array of TPointF;
  end;
 
  TTouchEvent = record
    Points: array of TTouchPoint;
    EventType: TTouchEventType;
  end;

So you get all the touch points and the event type. And for each point you have ID which stays the same when the same finger moves over the screen, the current position and the history of positions.

If anyone is interested how it was done, look at the source code for Android or ask here in the comments. You can download the code HERE or go to the Downloads page. Also note that I only used this code in one of my projects to solve the issues there. So there may be bugs in the code or the code may not work to you expectations. Please let me know how it works for you and if you need something that is not there.

EDIT:

For those that wish to patch the IOS code do the following:

  • Make a copy of FMX.Platform.IOS and put it somwhere where you can then use it in the uses clause. Just make sure its on the search path
  • Under implementation section add Cromis.Multitouch.Custom
  • Search for these  in the TFMXViewBase:
procedure touchesBegan(touches: NSSet; withEvent: UIEvent); cdecl;
procedure touchesCancelled(touches: NSSet; withEvent: UIEvent); cdecl;
procedure touchesEnded(touches: NSSet; withEvent: UIEvent); cdecl;
procedure touchesMoved(touches: NSSet; withEvent: UIEvent); cdecl;
  • In each of them add this as the first thing after begin and try. Use appropriate event type for each one. teMove is for touchesMoved.
DoNotifyTouchEvent(touches, withEvent, teMove);
  •  The procedure looks like this:
procedure TFMXViewBase.DoNotifyTouchEvent(touches: NSSet; withEvent: UIEvent; const EventType: TTouchEventType);
var
  I: Integer;
  Touch: UITouch;
  Event: TTouchEvent;
begin
  // do the event begin notify
  if TouchEventListener <> nil then
  begin
    SetLength(Event.Points, touches.allObjects.count);
    Event.EventType := EventType;
 
    // notify our global touch handler
    for I := 0 to touches.allObjects.count - 1 do
    begin
      Touch := TUITouch.Wrap(touches.allObjects.objectAtIndex(I));
      Event.Points[I].ID := Integer(touches.allObjects.objectAtIndex(I));
      Event.Points[I].Position.X := Touch.locationInView(UIView(Super)).x;
      Event.Points[I].Position.Y := Touch.locationInView(UIView(Super)).y;
 
      SetLength(Event.Points[I].History, 1);
      Event.Points[I].History[0].X := Touch.previousLocationInView(UIView(Super)).x;
      Event.Points[I].History[0].Y := Touch.previousLocationInView(UIView(Super)).y;
    end;
 
    TouchEventListener.Notify(Event);
  end;
end;

That is all. I am currently looking for some way to do it without changing this unit as I got some fresh ideas. But I will see if it can be done. I have no deep knowledge of objectiveC. I have almost no knowledge in fact 🙂