Home > JavaScript, Object Pascal, OP4JS, Smart Mobile Studio > Dealing with swipe gestures

Dealing with swipe gestures

Someone asked me earlier about handling swipe gestures under Smart Pascal, so I thought I could deal with that in this post. I’m on sickleave with a bad back so I’m scribling this on my iPad (so heads up regarding typos). Cant say I’m to impressed with the Bluetooth keyboard, but it kinda works.

Describe a swipe

As luck would have it I already wrote a swipe controller way back in 2014, so I’ll be posting that here. But before we get into the code , let’s take a few seconds to think about what a swipe really is, because it’s not as simple as you may think.

Just stop and think about it: what is the difference between a normal touch-move and a swipe? Same thing right? Well yes and no (or kinda). What it all boils down to is time (!). So the difference between a normal touch-move operation and  a swipe, is that a swipe happens very quickly.

Latency and ranges

The time it takes from your finger touches the display until your finger leaves, is the latency of the swipe (see code below). Depending on the device this latency can vary. On an iPad you can have a latency as low as 10, while on iPhones you generally end up with 35 or 40. Anything lower and the swipe wont register.

The Microsoft Phone header, fully animated, swipe, mouse and keyboard controlled. Works great on iOS and Android as well

The Microsoft Phone swipe header

Next there are ranges. A swipe involves your finger moving quickly in some direction, and the controller has some default ranges for measuring these things. These ranges are simple minimum and maximum values for the distance. If you have hardly moved your finger then it’s not really a swipe; and if you move your finger slowly over a long distance, that’s not a swipe either (well, it can be. Just adjust the latency factor to suit your needs). At least not for components designed to be flicked back and forth.

The swipe controller class was actually written for the Microsoft Mobile component package (will be included with Smart Mobile Studio in the future), which implements the most popular MS controls used on Windows 10 touch devices. Fully GPU powered, animated, skinned and working brilliantly.

They also look good under iOS. Everyone is skinning their app’s these days, so mixing and matching between platforms is a great benefit.

Controller vs. inheritance

A controller class does not need to be inherited from. Instead, you create an instance and attach it to an already existing visual control. This is pretty cool because it doesn’t mess with your event handlers, it just extends your control over the existing behavior. Controllers are handy in situations where inheritance will be tricky, require to much refactoring or where the source is off-limits.

Using the controller is super simple:

  FSwiper := TSwipeController.Create(nil);
  FSwiper.Attach(self);
  FSwiper.OnSwipe := procedure (sender:TObject;const Direction:TSwipeControllerDirection)
    begin
       showmessage("Swipe detected @ " + ord(direction).toString);
    end;

To detach the controller just call the Detach() method, or free the object. It will dispose of everything automatically. Note: If you pass along the control in the constructor, it attaches automatically.

The code

Well here is the code. Enjoy! Also remember that you can check this out from GitHub.

unit swipecontroller;

interface

uses
  system.types,
  system.dateutils,
  SmartCL.System,
  SmartCL.Components;

type

  TSwipeControllerDirection = (
    sdNone=0,
    sdLeft,
    sdRight,
    sdDown,
    sdUp
    );

  TSwipeControllerEvent = Procedure (sender:TObject;
    const Direction:TSwipeControllerDirection);

  TSwipeControllerInfo = class
  public
    begins: TDateTime;
    sX: Integer;
    sY: Integer;
    eX: Integer;
    eY: Integer;
  end;

  TSwipeRange = class
  private
    FMin: Integer;
    FMax: Integer;
  protected
    procedure SetMin(Value:Integer);virtual;
    procedure SetMax(Value:Integer);virtual;
  public
    property  Minimum:Integer read FMin write SetMin;
    property  Maximum:Integer read FMax write SetMax;
    constructor Create(AMin,AMax:Integer);
  end;

  TSwipeController = class(TObject)
  private
    FAttached:  Boolean;
    FControl:   TW3TagObj;
    FHRange:    TSwipeRange;
    FVRange:    TSwipeRange;
    FInfo:      TSwipeControllerInfo;
    FDirection: TSwipeControllerDirection;
  protected
    FTouchHandleStart:  THandle;
    FTouchHandleMove:   THandle;
    FTouchHandleUp:     THandle;
    procedure SetupGestures;
    procedure RemoveGestures;
  public
    Property    Latency:Integer;
    property    HRange:TSwipeRange read FHRange;
    property    VRange:TSwipeRange read FVRange;
    Property    Owner:TW3TagObj read FControl;
    property    Attached:Boolean read FAttached;
    Property    OnSwipe:TSwipeControllerEvent;

    procedure   Attach(const AOwner:TW3TagObj);
    procedure   Detach;
    constructor Create(const AOwner:TW3TagObj);virtual;
    destructor  Destroy;Override;
  end;

implementation

//############################################################################
// TSwipeRange
//############################################################################

constructor TSwipeRange.Create(AMin,AMax:Integer);
begin
  inherited Create;
  FMin:=AMin;
  FMax:=AMax;
end;

procedure TSwipeRange.SetMin(Value:Integer);
begin
  FMin := TInteger.EnsureRange(Value,10,1000);
end;

procedure TSwipeRange.SetMax(Value:Integer);
begin
  FMax := TInteger.EnsureRange(Value,10,1000);
end;

//############################################################################
// TSwipeController
//############################################################################

constructor TSwipeController.Create(const AOwner:TW3TagObj);
begin
  inherited Create;
  FHRange:=TSwipeRange.Create(20,40); // [min]---X---[max]
  FVRange:=TSwipeRange.Create(20,40); // [min]---Y---[max]
  FInfo := TSwipeControllerInfo.Create;
  Latency := 35;

  if assigned(AOwner) then
    Attach(Aowner);
end;

destructor TSwipeController.Destroy;
begin
  if FAttached then
  Detach;
  FHRange.free;
  FVRange.free;
  FInfo.free;
  inherited;
end;

procedure TSwipeController.Attach(const AOwner:TW3TagObj);
begin
  if FAttached then
  Detach;

  if AOwner<>NIl then
  begin
    FControl := AOwner;
    FAttached := true;
    FControl.Handle.readyExecute( procedure ()
      begin
        SetupGestures;
      end);
  end;
end;

procedure TSwipeController.Detach;
begin
  if FAttached then
  begin
    try
      RemoveGestures;
    finally
      FAttached := false;
    end;
  end;
end;

procedure TSwipeController.RemoveGestures;
begin
  FControl.Handle.removeEventListener(FTouchHandleStart);
  FControl.Handle.removeEventListener(FTouchHandleMove);
  FControl.Handle.removeEventListener(FTouchHandleUp);
  FTouchHandleStart := unassigned;
  FTouchHandleMove := unassigned;
  FTouchHandleUp := unassigned;
end;

procedure TSwipeController.SetupGestures;
begin
  FTouchHandleStart:=FControl.Handle.addEventListener
    ('touchstart', procedure (e:variant)
    begin
      e.preventDefault;
      var t := e.touches[0];
      FInfo.sx:=t.screenX;
      FInfo.sy:=t.screenY;
    end);

  FTouchHandleMove:=FControl.Handle.addEventListener
    ('touchmove', procedure (e:variant)
    begin
      e.preventDefault;
      if (e.touches) then
      Begin
        if e.touches.length>0 then
        begin
          var t := e.touches[0];
          FInfo.eX:=t.screenX;
          FInfo.eY:=t.screenY;
          FInfo.begins:=now;
        end;
      end;
    end);

  FTouchHandleUp:=FControl.Handle.addEventListener
    ('touchend', procedure (e:variant)
    var
      mTicks: Integer;
    begin
      e.preventDefault;
      FDirection:=sdNone;

      (* How many Ms since touch and release? *)
      mTicks:=MillisecondsBetween(FInfo.begins,now);

      if (mTicks <= Latency) then       begin         if (FInfo.ex - FHRange.Minimum > FInfo.sx)
        or (FInfo.ex + FHRange.Minimum < FInfo.sx) then
        begin
          if (FInfo.ey < (FInfo.sy + FVRange.Maximum))           and (FInfo.sy > (FInfo.ey - FVRange.Maximum)) then
          begin
            if (FInfo.ex >  FInfo.sx) then
            FDirection:=sdRight else
            FDirection:=sdLeft;
          end;
        end;

        if ((FInfo.ey - FVRange.Minimum) > FInfo.sy)
        or ((FInfo.ey + FVRange.Minimum) < FInfo.sY) then
        begin
          if  (FInfo.ex < (FInfo.sx + FHRange.Maximum))           and (FInfo.sx > (FInfo.ex - FHRange.Maximum)) then
          begin
            if FInfo.ey > FInfo.sY then
            FDirection :=sdDown else
            FDirection :=sdUp;
          end;
        end;

        if assigned(OnSwipe) then
        begin
          if FDirection<>sdNone then
          OnSwipe(self,FDirection);
        end;
      end;
    end);
end;

end.

 

Advertisements
  1. Nico Wouterse
    March 28, 2016 at 5:26 pm

    Glad you’re back

    • Jon Lennart Aasenden
      March 28, 2016 at 6:10 pm

      Thnx buddy 🙂

  2. March 28, 2016 at 8:55 pm

    The key to this element is that when swiping,

    Hi, I’ve created a fast demo using SMS page transition,

    the page follows your finger horizontally so as to give the user immediate feedback that he/she is swiping between pages.

    ..but I would like to implement page transition only when the swipe gesture has crossed a certain threshold. Any idea.

    BTW, I have updated the system.animation.tween.pas unit add some easing functions
    (41 functions) See at https://raw.githubusercontent.com/smartpascal/smartms/master/games/Easing/system.animation.tween.pas

    • Jon Lennart Aasenden
      March 29, 2016 at 7:31 am

      If you mean like the iBooks page thing? So you get to move the page up to a “snap to” point — like a rubber band?

      Well, you would need to use a parent container which grabs the input and moves the “page” accordingly. Then use some values to emulate resistance.

      There is a JS library for this, could be worth porting (!) Dont remember the name right now, but google around and you will find it.

      Also found this, which is quite cool:
      http://www.html5rocks.com/static/demos/20things_pageflip/example/index.html

      I’ll check out the fork on github!

    • Jon Lennart Aasenden
      March 29, 2016 at 7:36 am

      Ah, saw the GIF now, sorry.

      Well, the same rules would apply as i mention above. Control has to happen from the container control (just an extra TW3CustomControl). I pretty much the same with the DBGrid component where you can move the column-headers around. Then i use CSS transformation for GPU movement.

      Its basically just:
      1. Allow user to move while finger/mouse is down
      2. if released before thresh-hold, use distance as speed factor and tween back to zero
      3. if released beyond thresh-hold, use distance to end as speed-factor

      For the background layer (new page being swiped into), you would need to play with the distance value and tween it to match. You will have to update the tween values during the whole operation (new target values). This would be very hard with CSS, but simple with TTween ..

      For the swipe capture, use the class above (new version on git, just update the fork), and trigger an event which gives you distance between sx and dx (sourceX and destinationX). That should do it.

  1. No trackbacks yet.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: