The Corona Labs Blog
Posted on . Written by

If you’re not familiar with Corona events, I recommend you read The Corona Event Model Explained first, so you can get an understanding of how events and listeners work. That article also covers the basics of touch events, so when you’re finished reading about the event model you can come back to this tutorial to get a more complete understanding.

Detecting User Touches

User touches can be detected in two different ways. They can be detected on a per-object basis, or detected on the entire screen (also known as “Runtime” or “global” touches). It’s important to know the differences so I’ll go over both of them.

Per-object touches

First thing’s first. Before any particular event can be detected, you must add an event listener. There are two ways to do this, with a table listener or a function listener. Below are examples of both.

For all intents and purposes, ‘Table Listener A’ and ‘Table Listener B’ are identical, with some minor differences. Take a moment to study the differences—you can gain some valuable Lua/Corona knowledge (that’s best explained through examples and practice).

Table Listener A


local obj = display.newImage( "image.png" )

-- touch listener
function obj:touch( event )
-- 'self' parameter exists via the ':' in function definition
end

-- begin detecting touches
obj:addEventListener( "touch", obj )

Table Listener B

-- touch listener
local function onObjectTouch( self, event )
-- 'self' parameter must be defined via function argument
end

-- begin detecting touches
obj.touch = onObjectTouch
obj:addEventListener( "touch", obj )

Function Listener


local obj = display.newImage( "image.png" )

-- touch listener
local function onObjectTouch( event )
-- no 'self' parameter exists in this scenario
end

-- begin detecting touches
obj:addEventListener( "touch", onObjectTouch )

I’ll cover what goes IN the listener functions in a moment, but for now, just pay attention to how the listener is added to the object. What all of the above examples do is simply tell Corona to call the listener function whenever a “touch” event is dispatched. Take the third code snippet for example. Here’s what’s going on:

When the user touches ‘obj’, call the onObjectTouch() function.

NOTE: It is important to know that touch events are dispatched several times for a single touch, in separate phases, which I’ll cover in a moment.

Runtime Touches

You can also add a touch listener to the global Corona “Runtime” object, which will detect touches on the entire screen, and aren’t bound to any one object. Runtime touches only support function listeners. Here’s an example:


local function onScreenTouch( event )
-- no 'self' parameter
end

-- begin listening for screen touches
Runtime:addEventListener( "touch", onScreenTouch )

How to STOP detecting touches

If you want to stop listening to touch events on a specific object (including the global Runtime object), you simply call removeEventListener() in the same exact manner that you called addEventListener().


-- example 1:
obj:removeEventListener( "touch", obj )

-- example 2:
obj:removeEventListener( "touch", onObjectTouch )

-- example3 :
Runtime:removeEventListener( "touch", onScreenTouch )

VERY IMPORTANT: Runtime event listeners are never automatically removed. If you add a touch listener (or any kind of listener) to the global Runtime object, it is your sole responsibility to remove the event listeners when you’re finished with them to prevent bugs and crashes from occurring.

Event listeners that are added to individual objects are automatically removed when the object is removed; however, it is still recommended you remove them when you’re finished as well (just to ensure you don’t accidentally “double up” event listeners on an object).

Touch Event Phases

Many Corona beginners run into problems when working with touch listeners, because naturally, you’d expect your listener function to get called once per user touch. However, that is not the case. Touch events have several “phases” which are listed below:

  • began – dispatched when the user first touches the object.
  • moved – dispatched whenever user moves their finger, as they are touching.
  • ended & cancelled – dispatched whenever user lifts their finger or touch is cancelled for whatever reason.

Whenever a touch phase is dispatched, the touch event listener function that you specified when adding the listener will be called. This usually results in a minimum of two (but usually three) calls to your function for every user touch!

You will most-likely need to do different things (or do nothing) depending on which phase of the touch is currently in, so you’ll want to test for that via the event.phase property of your touch listener. Here’s an example:


local function onObjectTouch( self, event )
if event.phase == "began" then

-- Touch has began

elseif event.phase == "moved" then

-- User has moved their finger, while touching

elseif event.phase == "ended" or event.phase == "cancelled" then

-- Touch has ended; user has lifted their finger
end

return true -- IMPORTANT
end

The above example shows you how you can test for the different event phases of a single user touch, and do something (or do nothing) during a specific phase. In most cases, the “ended” and “cancelled” phases will be handled together (as shown in the example).

To return true, or not to—THAT is the question.

At the end the the previous example, you probably noticed I put a comment that says “IMPORTANT” next to a return true statement. Whether your function returns true or false/nil will affect the outcome of your touch event.

When your touch listener returns true, then that means your touch was successful; the object that was touched is the one that was supposed to be touched during that time. The next event in the sequence will be dispatched properly. If there is a “touchable” object underneath the object that was touched, a touch will not register on the object because the object above successfully captured the touch event.

However, if your listener returns false (or nothing at all), then the touch will be assumed to be faulty and the only event phase you will get is the “began” phase. If there is a “touchable” object underneath the object you touched, then in this case, the touch will “leak through” the object you actually touched and be passed to the next object (assuming you’re touching two objects at once).

This is unfortunately a common cause of confusion for new Corona developers. If you come across the above scenario or something similar, just remember: Return true to keep the touches from going through.

Touch Focus

When you set up your touch listener, in a real-world scenario, it will most-likely resemble something like the following:


local function onObjectTouch( self, event )
if event.phase == "began" then

-- specify the global touch focus
display.getCurrentStage():setFocus( self )
self.isFocus = true

elseif self.isFocus then
if event.phase == "moved" then

-- do something here; or not

elseif event.phase == "ended" or event.phase == "cancelled" then

-- reset global touch focus
display.getCurrentStage():setFocus( nil )
self.isFocus = nil
end
end

return true
end

When detecting touch events on a specific object, it is important to set the object as the global “touch focus” to prevent unexpected behavior from occurring, and to ensure your touch event handling goes as smooth as possible. This should be done during the “began” phase (as shown in the example). Touch focus should then be reset during the ended/cancelled phase of the touch so other objects can be touched once the touch is completed, which is also demonstrated in the example.

If it helps, I recommend copying/pasting the above example into a text file to serve as a “touch event” snippet you can re-use in your own apps (it’ll save you a lot of typing).

Other Event Properties

Included with every touch event (to include each of the individual phases) is an event table that has a some properties that may be useful to you. They are described below:

  • event.id – a unique identifier for the touch event (mostly used for multi-touch).
  • event.name – this will always be “touch” for touch events.
  • event.phase – the current “phase” of the user’s touch. Possible values include: “began”, “moved”, “ended”, and “cancelled” (hint: I already covered this one)
  • event.target – the object that is being touched; same as ‘self’ (when self is available)
  • event.time – timestamp of when the event took place.
  • event.x, event.y – the x/y coordinates (global/screen) of where the touch is currently taking place.
  • event.xStart, event.yStart – the x/y coordinates of where the touch event began (it can be different if the use dragged their finger during the course of a single touch). Example usage here.

You can get a more detailed account of all of the above touch event properties on the touch event API reference page.

Further Reading

While we’re on the subject of touches, it’s worth mentioning that Corona also supports another type of touch event, called a “tap”. This is a simple type of touch that does not have phases, and is useful if you want to detect when the user quickly taps on an object or on the screen.

More information on “tap” events in Basic Interactivity. To further solidify your touch event knowledge, I recommend you also take a look at the “Touch Event” section of the Events and Listeners documentation.

20 Responses to “Tutorial: Detecting Touches in Corona”

  1. Joel

    This is a great article that clearly explains how to handle touch events. Thanks so much!

    Reply
  2. tetu

    aren’t all 3 listeners identical?

    is there something that listener A (or B) can do but a function listener cannot?

    Reply
  3. Lynne

    Jonathan, if there’s a functional difference, can you please clarify usage? When would you choose A/B (table) over function? and, subsequently, A over B? Thanks.

    Reply
  4. Simon Strange

    Excellent as always Jonathan.

    Something very important, which really tripped me up, is the fact that different Android devices fire the event.phase = “moved” very differently. Some Android devices behave like iOS – they do not fire a “moved” phase unless the finger moves a few px across the screen. But some Android devices (XS is one, Kindle Fire as well) fire “moved” phases if any change at all occurs during the touch. This makes debugging some touch events very tricky!

    I initially used “tap” touch events for my menu presses, but I didn’t like some of the timing parameters for what constitutes a “tap” on iOS, so I rolled my own tap-detector using event.phase. Unfortunately it broke horribly on about 30% of Android devices – my testers couldn’t access any menus, except maybe 1 in 40 attempts! Eventually I split my code so that Android uses “tap” events, while iOS uses my home-rolled solution. But it certainly demonstrated to me why “tap” was an important concept for clean cross-platform development!

    Reply
  5. All

    THaaaaaaaaaaaaaaaaaaaaNKssssssssssssssssssssssssss

    THaaaaaaaaaaaaaaaaaaaaNKssssssssssssssssssssssssss

    THaaaaaaaaaaaaaaaaaaaaNKssssssssssssssssssssssssss

    THaaaaaaaaaaaaaaaaaaaaNKssssssssssssssssssssssssss

    Reply
  6. Clueless

    Hi, I seem to be having a problem with self in the event function, but if I take self out and reference the object it works fine?

    Reply
  7. Kyle

    @tetu, @Lynne – I believe the table/function listeners are pretty much identical EXCEPT that you have no access to “self” in the function listener. Access to self can be useful if you don’t want to bother with the event object (e.g. finding the target via event, etc.).

    Reply
  8. tetu

    @Kyle
    you have access to ‘self’ by using event.target in the function listener

    if you set t=event.target in the function listener, then self.x=100 of the table listener is the same with t.x=100 of the function listener, self.isFocus = true the same with t.isFocus = true and so on

    Reply
  9. Emanouel

    thanks for the great post,

    i was wondering if someone could help me with detecting different touches in a storyboard. as I’m having difficulty regarding coding it. here is what i have so far

    ———————————————————————————
    – Covered Purch (100) Room 1
    ———————————————————————————

    local storyboard = require( “storyboard” )
    local scene = storyboard.newScene()

    ———————————————————————————
    – BEGINNING OF YOUR IMPLEMENTATION
    ———————————————————————————

    local image, text1, text2, text3, object1

    – Touch event listener for background image
    local function onSceneTouch( self, event )
    if event.phase == “began” then

    storyboard.gotoScene( “scene2″, “fade”, 400 )

    return true
    end
    end

    – Called when the scene’s view does not exist:
    function scene:createScene( event )
    local screenGroup = self.view

    image = display.newImage( “assets/graphics/Room1.png” )
    screenGroup:insert( image )

    image.touch = onSceneTouch

    object1 = display.newImage( “assets/graphics/object.png”, 175, 200 )
    screenGroup:insert( object1 )

    object1.touch = onSceneTouch

    text1 = display.newText( “Scene 1″, 0, 0, native.systemFontBold, 24 )
    text1:setTextColor( 255 )
    text1:setReferencePoint( display.CenterReferencePoint )
    text1.x, text1.y = display.contentWidth * 0.5, 50
    screenGroup:insert( text1 )

    text2 = display.newText( “MemUsage: “, 0, 0, native.systemFont, 16 )
    text2:setTextColor( 255 )
    text2:setReferencePoint( display.CenterReferencePoint )
    text2.x, text2.y = display.contentWidth * 0.5, display.contentHeight * 0.5
    screenGroup:insert( text2 )

    text3 = display.newText( “Touch to continue.”, 0, 0, native.systemFontBold, 18 )
    text3:setTextColor( 255 ); text3.isVisible = false
    text3:setReferencePoint( display.CenterReferencePoint )
    text3.x, text3.y = display.contentWidth * 0.5, display.contentHeight – 100
    screenGroup:insert( text3 )

    print( “n1: createScene event”)
    end

    – Called immediately after scene has moved onscreen:
    function scene:enterScene( event )

    print( “1: enterScene event” )

    – remove previous scene’s view
    storyboard.purgeScene( “scene0″ )

    – Update Lua memory text display
    local showMem = function()
    image:addEventListener( “touch”, image )
    text3.isVisible = true
    text2.text = text2.text .. collectgarbage(“count”)/1000 .. “MB”
    text2.x = display.contentWidth * 0.5
    end
    local memTimer = timer.performWithDelay( 1000, showMem, 1 )
    end

    – Called when scene is about to move offscreen:
    function scene:exitScene( event )

    print( “1: exitScene event” )

    – remove touch listener for image
    image:removeEventListener( “touch”, image )

    – reset label text
    text2.text = “MemUsage: ”
    end

    – Called prior to the removal of scene’s “view” (display group)
    function scene:destroyScene( event )

    print( “((destroying scene 1′s view))” )
    end

    ———————————————————————————
    – END OF YOUR IMPLEMENTATION
    ———————————————————————————

    – “createScene” event is dispatched if scene’s view does not exist
    scene:addEventListener( “createScene”, scene )

    – “enterScene” event is dispatched whenever scene transition has finished
    scene:addEventListener( “enterScene”, scene )

    – “exitScene” event is dispatched before next scene’s transition begins
    scene:addEventListener( “exitScene”, scene )

    – “destroyScene” event is dispatched before view is unloaded, which can be
    – automatically unloaded in low memory situations, or explicitly via a call to
    – storyboard.purgeScene() or storyboard.removeScene().
    scene:addEventListener( “destroyScene”, scene )

    ———————————————————————————

    return scene

    Reply
  10. Chris Palmer

    Jonathan,

    Thanks for posting this. Like tetu and Lynne, I am also curious to know if there are differences in functionality between the two types of table listeners and the function listener.

    I am fairly new to all of this, and now realize that I have been using the function listener (as this is generally the technique used in the Cheetomoskeeto video tutorials). I believe this is what has me so turned around trying to use the Storyboard template which, if I am correct, uses the table listener B method.

    Would the Storyboard sample would work just as well using the other methods, or is there was a compelling reason for selecting that particular method?

    Many thanks!

    Chris

    Reply
  11. Jonathan Beebe

    Hi all — because of Lua’s inherent flexibility, there ends up being several different ways to do the same thing.

    There is no benefit to using one method over the other, it’s all a matter of use preference. I personally prefer method B, but you might prefer another method. I wanted to show you the different ways to do it because if I showed you only Method B, then if you saw someone else (or another sample) use Method A or even a function listener, then you’d probably be confused as to what’s going on.

    But know that you can use whichever method you want (table listener A/B or function listeners) and accomplish the same thing :-)

    There is one caveat: If the listener does not provide an event.target, then table listeners are much more useful because you get a ‘self’ object to work with. For function listeners that do have an event.target, then there is no benefit to using table listeners in that case.

    Reply
  12. Chris Palmer

    Jonathan,

    Thanks so much for the clarification! Just one more newbie question (yeah right, huh?) – would you mind giving an example of a listener that would not have an event.target?

    Thanks so much!

    Chris

    Reply
  13. James White

    Jonathan,
    thanks for posting this tutorial on Touch events. I’m having a problem with Touch events using the newTableView widget. When I have newTableView under another one, in other words stacked on top of each other, the bottom newTableView gets all the Touch events and the one on top gets only the Press event. I only want the object I’m touching to get the events

    Reply
  14. blagino

    i know this isn’t what we were talking about but i got a error in my game and plz if you know what it’s can you help me.

    function moveballoon(event)
    local balloon = event.target
    balloon:applyLinearImpulse(event.x, event.y , 0, -0.05 )
    end

    balloon:addEventListener(“touch”, moveballoon) <—- it says
    "…\users\admin\document\corona projects\game\main.lua:42: in main chunk"

    Reply
  15. blagino

    i know this isn’t what we were talking about but i got a error in my game and plz if you know what it’s can you help me.

    function moveballoon(event)
    local balloon = event.target
    balloon:applyLinearImpulse(event.x, event.y , 0, -0.05 )
    end

    balloon:addEventListener(“touch”, moveballoon) <—- it says:
    "…\users\admin\document\corona projects\game\main.lua:42: in main chunk"

    Reply

Leave a Reply

  • (Will Not Be Published)