Tap detection is different between Android and iOS

I’m developing an isometric game where you can select a tile with a tap and you can scroll or maybe pinch to zoom the map using touch events. All works fine and as expected on Android, with tap and touch responding at a good time. But, testing the same project on iOS I noticed that the tap event is detected more easily. If I swipe to move the map and release it with the same distance on both Android and iOS devices at the same time (without moving so much), the tap event is fired on iOS and not on Android.

This is really annoying on iOS, especially when you do pinch to zoom, because at the same time it select the tiles behind the fingers.

Any suggestions?

This may not be helpful, but I always tell folks do not use the tap event.  I only use the touch event.  Also, I never mix the two.

Regarding the difference in sensitivity/speed.  This is actually not surprising.  Remember, the tap (and touch) events are hooked up to the native OS events for the device and I’m sure those are a little different between Android and iOS.

Ok, so how can I simulate a tap event using the touch event? Maybe a relation with pressed time and distance?

Maybe something like this?

local rect = display.newRect(100, 100, 200, 200) function rect:touch(event) if event.phase == "began" then rect.pressTime = event.time rect.isTouch = false elseif event.phase == "moved" then if math.sqrt(math.pow(event.x - event.xStart, 2) + math.pow(event.y - event.yStart, 2)) \> 20 or event.time - rect.pressTime \> 100 then rect.isTouch = true print("Is touch") end else if not rect.isTouch then print("Is tap") end end end rect:addEventListener("touch", rect)

First you need to decide what you think the difference between a tap and a touch are.  Or better yet define what you think a tap is.

To me, a tap is basically the “ended” phase of a touch.  However, that often is not good enough.  i.e. What if you want something to happen immediately upon the user touching the screen? 

--To me this is the touch equivalent of a tap (ignoring double-tap which can be easily coded) local function onTouch( self, event ) if( event.phase == "ended" ) then print("tapped button") end return true end local button = display.newRect( 100, 100, 100, 100) button.touch = onTouch button:addEventListener("touch")

I know many won’t agree with me here, but the ‘tap’ event is one of those things I wish was never built-in to Corona.  99% of the time (in my opinion) it is almost useless.

The reason I don’t like (and tell folks not to use) ‘tap’ is because I think too many users start their coding journey with tap events and then get wholly confused when it falls short and they have to switch to ‘touch’ events in the middle of coding their games.

i.e. You start off using tap events and life seems simple and good, but as soon as you need to do anything even vaguely sophisticated (detect a hold, detect a drag, detect touches that move off the target then back again, etc.) you have to switch to touch and having started from ‘tap’ this is difficult for many to grok.

So, I encourage folks to bite the bullet and learn how to use these features of touch listeners:

, then when they have solid understanding of the features, come back to their game or app and re-examine the interaction they are trying to make in the context of a touch event and its features.

Thank you for the reply. I agree with you that touch events are more flexible, more customizable and can avoid problems like mine.

So, I haven’t yet answered your original question.  Re-posted here with a little better formatting to make it legible (please note, line break and paragraphs go a long ways to making it easier for folks to read and understand your posts, so please use them):

I’m developing an isometric game where you can select a tile with a tap and you can scroll or maybe pinch to zoom the map using touch events. 

All works fine and as expected on Android, with tap and touch responding at a good time. But, testing the same project on iOS I noticed that the tap event is detected more easily. 

If I swipe to move the map and release it with the same distance on both Android and iOS devices at the same time (without moving so much), the tap event is fired on iOS and not on Android.

This is really annoying on iOS, especially when you do pinch to zoom, because at the same time it select the tiles behind the fingers.

Any suggestions?

Yes, I have a suggestion (besides everything above).  Encode your drag and tap code into a single listener.

local dragThreshold = 10 dragThreshold = dragThreshold ^ 2 local function onTapDrag( self, event ) local phase = event.phase local id = event.id if( phase == "began" ) then self.isFocus = true display.currentStage:setFocus( self, id ) self.x0 = self.x self.y0 = self.y self.isDragging = false elseif( self.isFocus ) then local dx = event.x - event.xStart local dy = event.y - event.yStart -- FOR EFFICIENCY, SHORT-CIRCUIT TO A SELF-ASSIGNMENT ONCE DRAG IS UNDERWAY -- i.e. Once you are dragging the calculation is not executed. self.isDragging = self.isDragging or ( (dx^2 + dy^2) \>= dragThreshold ) -- DO NOT MOVE UNTIL WE HAVE BREACHED THE DRAG THRESHOLD if( self.isDragging ) then self.x = self.x0 + dx self.y = self.y0 + dy end if( phase == "ended" or phase == "cancelled" ) then self.isFocus = false display.currentStage:setFocus( self, nil ) if( self.isDragging ) then -- DO DRAG RELEASED ACTION HERE (IF ANY) else -- DO TAP ACTION HERE end end end return true end

later use it like this:

local button = display.newRect( 100, 100, 100, 100) button.touch = onTapDrag button:addEventListener("touch")

I hope that gets you rolling.  The code above is fully compatible with multitouch or no multi-touch

If your isometric app is relatively (will be) complex, combining all touch into a single listener is just not practical.  

I have a complex isometric app and I have pinch zoom/pan as a runtime listener.  

I then have a separate touch handler shared with all the ground tiles and a separate shared touch handler to control interaction with objects in my iso world.

I agree with you, in fact I also use two different touch handler: one for the camera for moving the tilemap and one for selecting a tile so I can’t do how roaminggamer has suggested.

Instead I can use the function I wrote above for selecting a tile and it seems to work well.

This may not be helpful, but I always tell folks do not use the tap event.  I only use the touch event.  Also, I never mix the two.

Regarding the difference in sensitivity/speed.  This is actually not surprising.  Remember, the tap (and touch) events are hooked up to the native OS events for the device and I’m sure those are a little different between Android and iOS.

Ok, so how can I simulate a tap event using the touch event? Maybe a relation with pressed time and distance?

Maybe something like this?

local rect = display.newRect(100, 100, 200, 200) function rect:touch(event) if event.phase == "began" then rect.pressTime = event.time rect.isTouch = false elseif event.phase == "moved" then if math.sqrt(math.pow(event.x - event.xStart, 2) + math.pow(event.y - event.yStart, 2)) \> 20 or event.time - rect.pressTime \> 100 then rect.isTouch = true print("Is touch") end else if not rect.isTouch then print("Is tap") end end end rect:addEventListener("touch", rect)

First you need to decide what you think the difference between a tap and a touch are.  Or better yet define what you think a tap is.

To me, a tap is basically the “ended” phase of a touch.  However, that often is not good enough.  i.e. What if you want something to happen immediately upon the user touching the screen? 

--To me this is the touch equivalent of a tap (ignoring double-tap which can be easily coded) local function onTouch( self, event ) if( event.phase == "ended" ) then print("tapped button") end return true end local button = display.newRect( 100, 100, 100, 100) button.touch = onTouch button:addEventListener("touch")

I know many won’t agree with me here, but the ‘tap’ event is one of those things I wish was never built-in to Corona.  99% of the time (in my opinion) it is almost useless.

The reason I don’t like (and tell folks not to use) ‘tap’ is because I think too many users start their coding journey with tap events and then get wholly confused when it falls short and they have to switch to ‘touch’ events in the middle of coding their games.

i.e. You start off using tap events and life seems simple and good, but as soon as you need to do anything even vaguely sophisticated (detect a hold, detect a drag, detect touches that move off the target then back again, etc.) you have to switch to touch and having started from ‘tap’ this is difficult for many to grok.

So, I encourage folks to bite the bullet and learn how to use these features of touch listeners:

, then when they have solid understanding of the features, come back to their game or app and re-examine the interaction they are trying to make in the context of a touch event and its features.

Thank you for the reply. I agree with you that touch events are more flexible, more customizable and can avoid problems like mine.

So, I haven’t yet answered your original question.  Re-posted here with a little better formatting to make it legible (please note, line break and paragraphs go a long ways to making it easier for folks to read and understand your posts, so please use them):

I’m developing an isometric game where you can select a tile with a tap and you can scroll or maybe pinch to zoom the map using touch events. 

All works fine and as expected on Android, with tap and touch responding at a good time. But, testing the same project on iOS I noticed that the tap event is detected more easily. 

If I swipe to move the map and release it with the same distance on both Android and iOS devices at the same time (without moving so much), the tap event is fired on iOS and not on Android.

This is really annoying on iOS, especially when you do pinch to zoom, because at the same time it select the tiles behind the fingers.

Any suggestions?

Yes, I have a suggestion (besides everything above).  Encode your drag and tap code into a single listener.

local dragThreshold = 10 dragThreshold = dragThreshold ^ 2 local function onTapDrag( self, event ) local phase = event.phase local id = event.id if( phase == "began" ) then self.isFocus = true display.currentStage:setFocus( self, id ) self.x0 = self.x self.y0 = self.y self.isDragging = false elseif( self.isFocus ) then local dx = event.x - event.xStart local dy = event.y - event.yStart -- FOR EFFICIENCY, SHORT-CIRCUIT TO A SELF-ASSIGNMENT ONCE DRAG IS UNDERWAY -- i.e. Once you are dragging the calculation is not executed. self.isDragging = self.isDragging or ( (dx^2 + dy^2) \>= dragThreshold ) -- DO NOT MOVE UNTIL WE HAVE BREACHED THE DRAG THRESHOLD if( self.isDragging ) then self.x = self.x0 + dx self.y = self.y0 + dy end if( phase == "ended" or phase == "cancelled" ) then self.isFocus = false display.currentStage:setFocus( self, nil ) if( self.isDragging ) then -- DO DRAG RELEASED ACTION HERE (IF ANY) else -- DO TAP ACTION HERE end end end return true end

later use it like this:

local button = display.newRect( 100, 100, 100, 100) button.touch = onTapDrag button:addEventListener("touch")

I hope that gets you rolling.  The code above is fully compatible with multitouch or no multi-touch

If your isometric app is relatively (will be) complex, combining all touch into a single listener is just not practical.  

I have a complex isometric app and I have pinch zoom/pan as a runtime listener.  

I then have a separate touch handler shared with all the ground tiles and a separate shared touch handler to control interaction with objects in my iso world.

I agree with you, in fact I also use two different touch handler: one for the camera for moving the tilemap and one for selecting a tile so I can’t do how roaminggamer has suggested.

Instead I can use the function I wrote above for selecting a tile and it seems to work well.