taptouch-featMost Corona developers understand the concept behind tap and touch events, but the lines get a little blurry when dealing with more complex scenarios, for example overlapping objects with different types of event listeners. Today’s tutorial explains exactly how Corona handles these events and which events are broadcast to which objects.

Tap vs. Touch

Let’s quickly discuss the basics for users who may be new to Corona SDK or mobile development in general. When the user touches the screen of a mobile device, this action is regarded in one of two ways:

  1. “tap” — this event is represented by the user touching the screen and lifting off at the same approximate point. The tap event is considered successful only if the user touches and releases at that point.
  2. “touch” — these events provide for a much greater level of screen interactivity. Using touch events, you can detect when the user first touches the screen and when the touch is lifted off the screen. You can also track the motion of the touch as it moves around the screen.

This tutorial won’t go into depth about the properties returned from these events — if you need to explore this topic further, please refer to our guide.

Event Distinction

Looking beyond the basic concept of tap vs. touch, we’ll explore how Corona handles these events. At the core level, it’s important to understand that each type is regarded as distinct. You may think that a screen touch is just a screen touch, but the difference will become more apparent as we examine more complex examples.

Test Project Setup

For purposes of this tutorial, let’s quickly set up two squares on the screen that overlap in the center. We’ll use these squares to test different types of listeners, and explore how/when the events are processed. Let’s also place a text label on each rectangle so we can observe which type of listener is associated with each rectangle.

local backObject = display.newRect( 25, 25, 150, 150 )
backObject.alpha = 0.5
backObject.name = "Back Object"

local frontObject = display.newRect( 75, 75, 150, 150 )
frontObject.alpha = 0.8
frontObject.name = "Front Object"

local backLabel = display.newText( { text = "", x = 0, y = 0, fontSize = 28 } )
backLabel:setTextColor( 0 ) ; backLabel.x = 100 ; backLabel.y = 45

local frontLabel = display.newText( { text = "", x = 0, y = 0, fontSize = 28 } )
frontLabel:setTextColor( 0 ) ; frontLabel.x = 150 ; frontLabel.y = 200

local function tapListener( event )
   local object = event.target
   print( object.name.." TAPPED!" )
end

local function touchListener( event )
   local object = event.target
   print( event.target.name.." TOUCH on the '"..event.phase.."' Phase!" )
end

--add "tap" event listener to back object and update text label
backObject:addEventListener( "tap", tapListener )
backLabel.text = "tap"

--add "tap" event listener to front object and update text label
frontObject:addEventListener( "tap", tapListener )
frontLabel.text = "tap"

Tap on Tap

Working with this example, let’s explore the most simple case of overlapping objects: one tap object overlapping another tap object. When you click in the middle overlapping section and observe the output in the Terminal/console, you’ll notice that, by default, tap events transmit through — or “propagate” through — to underlying objects.

> Corona Simulator[3839:707] Front Object TAPPED!
> Corona Simulator[3839:707] Back Object TAPPED!
>

This is by design, but how do you prevent this from occurring when such behavior is not desired? The solution is to simply return true at the end of the listener function of the “active” object, in this case the front square. This will prevent the tap event from reaching the back square entirely.

local function tapListener( event )
   local object = event.target
   print( object.name.." TAPPED!" )
   return true  --prevent propagation to underlying tap objects
end

Now test this in the Terminal/console and you’ll notice that the tap only reaches the front square, while the back square remains blocked, at least in the region where the front square overlaps it.

> Corona Simulator[3839:707] Front Object TAPPED!
>

Touch on Touch

As expected, the same principle applies to touch events. Let’s return true at the end of our touch event listener function exactly as we did for the tap listener. We must also change both squares to touch objects instead of tap objects:

local function touchListener( event )
   local object = event.target
   print( event.target.name.." TOUCH on the '"..event.phase.."' Phase!" )
   return true  --prevent propagation to underlying touch objects
end

--add "touch" listener to back object and update text label
backObject:addEventListener( "touch", touchListener )
backLabel.text = "touch"

--add "touch" listener to front object and update text label
frontObject:addEventListener( "touch", touchListener )
frontLabel.text = "touch"

Test the updated project and the output in the Terminal/console should look similar to this:

> Corona Simulator[3839:707] Front Object TOUCH on the 'began' Phase!
> Corona Simulator[3839:707] Front Object TOUCH on the 'moved' Phase!
> Corona Simulator[3839:707] Front Object TOUCH on the 'moved' Phase!
> Corona Simulator[3839:707] Front Object TOUCH on the 'ended' Phase!
>

Tap on Touch / Touch on Tap

Things get a little more complicated when you have objects with different listener types overlapping each other, but you still need to control the propagation of tap and touch events. For testing purposes, let’s adjust the sample project so it becomes a tap object over a touch object:

--add listener to back object and update text label
backObject:addEventListener( "touch", touchListener )
backLabel.text = "touch"

--add listener to front object and update text label
frontObject:addEventListener( "tap", tapListener )
frontLabel.text = "tap"

Test this code in the Terminal/console, and the output should look similar to this:

> Corona Simulator[3839:707] Back Object TOUCH on the 'began' Phase!
> Corona Simulator[3839:707] Back Object TOUCH on the 'ended' Phase!
> Corona Simulator[3839:707] Front Object TAPPED!
>

Notice that the back square still receives touch events despite the fact that we’ve returned true in both the tap and touch listener functions. This is why it’s important to understand — as noted earlier — that tap and touch events are actually distinct from Corona’s standpoint, despite some similarities from the user’s standpoint.

The behavior is similar if we change the test case to a touch object over a tap object:

--add listener to back object and update text label
backObject:addEventListener( "tap", tapListener )
backLabel.text = "tap"

--add listener to front object and update text label
frontObject:addEventListener( "touch", touchListener )
frontLabel.text = "touch"
> Corona Simulator[3839:707] Front Object TOUCH on the 'began' Phase!
> Corona Simulator[3839:707] Front Object TOUCH on the 'ended' Phase!
> Corona Simulator[3839:707] Back Object TAPPED!
>

Working with Overlaps

The above examples of overlapping objects with different listener types is fairly common in app development, so you’ll need an approach to handle these situations. Some examples include:

  1. tap objects over a larger region that must detect touch-moves, i.e. stationary buttons placed over a background that can be scrolled/moved as the user touches and drags it.
  2. a draggable touch object that can be moved around over underlying tap objects that the user can tap on to collect/activate.

For both of these cases, there is a tactic to prevent the “wrong type” of event from propagating through to an underlying object with a different listener type, as follows:

1. Isolate Events on Overlaying Tap Object

In the instance where a tap object resides over a touch object, you can add a listener of both types (tap and touch) to the front object and then use a conditional check in the listener function to void events of the undesired type:

local function touchListener( event )
   local object = event.target

   if ( event.phase == "began" ) then
      display.getCurrentStage():setFocus( object )
   elseif ( event.phase == "ended" or event.phase == "cancelled" ) then
      display.getCurrentStage():setFocus( nil )
   end

   print( event.target.name.." TOUCH on the '"..event.phase.."' Phase!" )
   return true
end

local function comboListener( event )
   local object = event.target
   if not ( event.phase ) then
      print( object.name.." TAPPED!" )
   end
   return true
end

--add listener to back object and update text label
backObject:addEventListener( "touch", touchListener )
backLabel.text = "touch"

--add listener to front object and update text label
frontObject:addEventListener( "tap", comboListener )
frontObject:addEventListener( "touch", comboListener )
frontLabel.text = "tap/-touch"

In the comboListener function, conditionally check that event.phase exists (or in this case, does not exist). Since tap events do not return any phase property, detecting its absence reveals that the event is a touch. In addition, we use the setFocus() method in the touchListener function to give focus to the background object — but only if it receives the touch from user interaction outside of the front object’s bounds.

2. Isolate Events on Overlaying Touch Object

Similar to the above method, you can isolate events to an overlaying touch object by applying both listener types to the touch object and processing only events with a phase property, revealing that it’s a touch:

local function touchListener( event )
   local object = event.target
   print( event.target.name.." TOUCH on the '"..event.phase.."' Phase!" )
   return true
end

local function comboListener( event )
   local object = event.target
   if ( event.phase ) then
      print( event.target.name.." TOUCH on the '"..event.phase.."' Phase!" )
   end
   return true
end

--add listener to back object and update text label
backObject:addEventListener( "tap", tapListener )
backLabel.text = "tap"

--add listener to front object and update text label
frontObject:addEventListener( "touch", comboListener )
frontObject:addEventListener( "tap", comboListener )
frontLabel.text = "touch/-tap"

In Summary

Every project will vary slightly in the exact way that it should behave in regards to tap and touch, but understanding these core concepts is essential to the ultimate user experience. Experiment with the different types and phases, and always remember to test, test, and test again!

  1. thnx for the “return true” tip which isolates the touch event. .

    but what i really want to do is activate a series of touch events in ‘one swipe’ (while my fingers is touching the screen) like the “RUZZLE” game ..in Ruzzle game you can activate several letter tiles while your finger is pressing the screen . . any tip how i can do that??? i’ve trying to decode that for weeks haha

    thnx! :D

  2. Hi Brent

    I would like to know in the event.phase == “moved” when you have a print statement, it will print the whole time you move the object. But as soon as you stop the move, but not go to the ended phase (so you have in essence kept your finger in one place) the print statement will stop printing, what event or handler is checked to see if in the move phase that it has stopped moving causing the print statement to stop. I would like to know because if you put an boolen to the move phase it will remain true even if you you keep your finger in the move phase in one place, how does lua check this? I would like to know what I can set or change to get the same result as when you only move your finger and when you stop moving your finger to change a boolen or variable to notice the finger is being kept in one place?

    • Hi @Sadsack,
      The event system doesn’t really “monitor” the moved phase, it just reports when the touch has moved enough from the previous point (which triggers the next moved event). If you want to monitor if the touch is still down, but hasn’t moved, you’d need to write a Runtime listener that compares the touch point and, if it hasn’t moved in a certain cycle (and hasn’t ended) then you’d know that the touch is sitting in place… or something like that, you’d have to take all factors into account. :)

      Brent

  3. Hi Brent, how can I get the “cancelled” event phase? I tried to scroll my finger out of the display object with the touch listener, but it didn’t work both in device and simulator.

    • Hi Mahdi,
      The “cancelled” phase is only dispatched by the system/OS, never by the user’s touch. It might occur if the system terminates some process. In other words, it will not occur very often, but typically people detect it in the same conditional clause as “ended”, because typically the app would perform a similar action for both of those phases.

      Brent

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>