Long-Press Event Detection

Hi all, my current project was in need of a way to detect when the user long-presses an object on the screen. I didn’t see much in the forums and wanted to share the following code in the hopes it saves others the time of recreating this functionality. Please, if anyone has ANY suggestions on improving my code, please share! I’m always interested in the way others solve similar problems.

In my code I break out the various touch states (BEGAN, MOVING, ENDED) which is why the touched() method forwards calls to one of the following:

    touchesBegan(),  touchesMoved()  and   touchesEnded()

For my project I needed an onLongPress() method to be called every time the user touches and holds the screen for a given time. My code uses a global ‘longPressDuration’ which by default is set to 1/2 a second (0.5). This is the only variable that should be changed if you need longer or shorter long-press durations. All of the variables that begin with 2 underscores ‘__’ are used internally and should not be changed as they’re used to maintain the state of the long-press event detection logic.

Basically the algorithm works like this: when the user first touches the screen (touch.state == BEGAN), we store the current ElapsedTime so we can later check to see how long it’s been since the user began touching the screen. If the user moves their finger, then the long-press is cancelled. Since the only method Codea calls regualrly is draw(), I placed all the logic for detecting a long press at the END of draw(). This code first checks to see if we’ve detected a touch BEGAN event. If so, and we’ve yet to detect a long-press then we check the current time with the stored time when the touch BEGAN. If this result is greater than the long-press hold time (longPressDuration) then we trigger the onLongPress() method. You might notice the extra flags & checking, these ensure that once the system has detected a long-press, it doesn’t keep sending out the onLongPress() method on every draw() cycle.

Enough talk, here’s the code. Share & enjoy

  LongPress Event Detection

  Sample code to detect when a user long-presses/touches the screen.

  Since Codea doesn't provide timers or multiple threads, long-press
  detection is done in the only method that's called regularly, the
  'draw()' method.

  Also, Once a long-press has been detected, no more movement of the touch
  will be forward (touch.state == MOVING or ENDED).  Only after the user
  releases their finger from the screen will the long-press be cleared and
  touches will begin to be recognized again.


-- ----------------------------------------------------------------------------------
-- ----------------------------------------------------------------------------------

-- how long does it take before a long-press event is triggered.
longPressDuration = 0.5

-- Sensitivity of the touch movement in points.
-- The number of points a touch can move to still allow a long-press.
-- users tend to 'jiggle' their fingers when long-pressing the screen,
-- this helps to make a long-press more accurate.
longPressSensitivity = 10

-- touch start time
__longPressStartTime = 0

-- did we detect a touch.state == BEGAN?
__longPressStarted = false

-- has a long-press been detected?
__longPressDetected = false

-- store the touch BEGAN location
-- these are used to calculate the distance between
-- the touch start and the longPressSensitivity value
__longPressTouchX = 0
__longPressTouchY = 0

-- ----------------------------------------------------------------------------------
-- draw()
-- ----------------------------------------------------------------------------------
function draw()
    -- <>>
    -- The following code goes AFTER all drawing is completed.
    -- Basically, if we've detected a BEGAN touch event and
    -- a long-press hasn't been detected yet, check how long it's
    -- been since the touch started.  If it's longer than the long-press
    -- duration then trigger a long-press event.
    if __longPressStarted then
        if not __longPressDetected then
            if (ElapsedTime - __longPressStartTime) > longPressDuration then
                __longPressDetected = true

-- ----------------------------------------------------------------------------------
-- touched()
-- When the user touches the screen, this method processes the touch event
-- and routes the different touch types to their own method.  If a touch BEGAN
-- event is received, then setup a long-press/touch state.  If a MOVING touch
-- state is received, and the distance between the start of a long-press and
-- the current 'moving' touch is less than the long-press sensitivity then
-- ignore the MOVING touch, otherwise cancel the long-press and just trigger a
-- MOVING event. 

-- Once a long-press/touch has been detected, ALL future touch events are IGNORED
-- until the user removes their finger from the screen, which triggers an ENDED
-- touch state.  Once this happens, the long-press state is reset and touches
-- are enabled once again.
-- ----------------------------------------------------------------------------------
function touched(touch)
    if touch.state == BEGAN then
        __longPressStartTime = ElapsedTime
        __longPressStarted = true
        __longPressTouchX = touch.x
        __longPressTouchY = touch.y

    elseif touch.state == MOVING then
        if not __longPressDetected then
            -- has the touch moved outside of the long-press sensitivty range?
            if not distanceWithinRange(touch.x, touch.y, __longPressTouchX, __longPressTouchY, longPressSensitivity) then
                -- cancel the long-press state...
                __longPressStarted = false
                -- send a touchesMoved() event...
        -- This is for touch.state == ENDED 
        if not __longPressDetected then
        -- clear the long-press flags
        __longPressDetected = false
        __longPressStarted = false

-- ----------------------------------------------------------------------------------
-- distanceWithinRange()
-- Given 2 points calculate their distance for both X and Y.
-- If the distance of BOTH X and Y are less than distanceRange
-- then return 'true', otherwise return 'false'
-- Params:
--            p1x, p1y       First point to check
--            p2x, p2y       Second point to check
--            distanceRange  Minimum distance between p1 and p2 to check for
-- Return:
--            true        Distance between p1 AND p2 is less than or equal to'distanceRange'
--            false       Distance between p1 AND p2 is greater than 'distanceRange'
-- ----------------------------------------------------------------------------------
function distanceWithinRange(p1x, p1y, p2x, p2y, distanceRange)
    local result = false
    -- calculate the distance between p1 and p2
    local dx = math.abs(p2x-p1x)
    local dy = math.abs(p2y-p1y)
    -- if the distance between BOTH p1 and p2 is less than or equal todistanceRange
    -- then set result to 'true', otherwise result will be the default value
    -- of 'false'
    if dx <= distanceRange then
        if dy <= distanceRange then
            result = true

    return result

-- ----------------------------------------------------------------------------------
-- touchesBegan()
-- This method is called when a touch.state == BEGAN
-- ----------------------------------------------------------------------------------
function touchesBegan(touch)
    print("touch BEGAN")

-- ----------------------------------------------------------------------------------
-- touchesMoved()
-- This method is called when a touch.state == MOVED
-- ----------------------------------------------------------------------------------
function touchesMoved(touch)
    print("touch MOVED")

-- ----------------------------------------------------------------------------------
-- touchesEnded()
-- This method is called when a touch.state == ENDED
-- ----------------------------------------------------------------------------------
function touchesEnded(touch)
    print("touch ENDED")

-- ----------------------------------------------------------------------------------
-- onLongPress()
-- This method will be called if the user has long-pressed/touched the screen.
-- ----------------------------------------------------------------------------------
function onLongPress()
    print("** onLongPress() **")
    -- add your code here to process the long-press/touch


I ‘cleaned up’ the code above, as well as added a way to change the sensitivity of the long-press/touch. The global ‘longPressSensitivity’ is the number of points the touch can move and still be recognized as a long-press/touch. I added this because it’s a little too difficult to hold your finger on the exact same spot long enough (without moving a pixel or two) and still be recognized as a long-press. (My client had problems which resulted in this code modification). By default the system uses a 10 point radius for the long-press checking.

Hope this helps other :slight_smile:

Very cool @BrianH thanks for posting it up. I’ll save it for later use :slight_smile: