setFocus does not check if display object has an appropriate listener

It’s possible in a single touch app to block all touch inputs by set a display object to be the focus of all touches and then removing the event listener. This is a programmer error. However in my case it was caused by the fact that setFocus behaves very differently between single touch and multitouch apps and the fact that Corona does not seem to detect this state.

My code looked something like this:

-- scene one scene:show(event) if event.phase == "will" then local button = display.newRect(...) local touchListener = function() if event.phase == "began" then display.getCurrentStage():setFocus(button, event.id) end if event.phase == "ended" then display.getCurrentStage():setFocus(nil) end end button:addEventListener("touch", touchListener) timer.performWithDelay(2000, function() button:removeEventListener("touch", touchListener) composer.gotoScene("next scene") end) end) scene:addEventListener("show)

That’s a simplification of what was actually happening.

The problem for me was that if the user is pressing the button when the timer kicks in all touches go to the display object button. However button no longer has a touch listener. In practice on a single touch app this means that all touches a silently absorbed by a display object which in my case was no longer on the screen.

It took me a while to figure out what was going on. I think Corona could have helped if:

  1. the event system printed warnings when running in the simulator if the display object set to be the focus has no appropriate event handler

  2. if the setFocus is passed an event id it should behave in the same way in single touch apps as it does in multitouch apps to avoid confusion.

I think 2. always catches me out when I move between single and multitouch apps, I don’t know if that’s just me or if it is a common problem.

What exactly do you want to happen to the touch when you the button listener is removed?

If you are trying to remove the focus from the button with the listener is removed, you could try setting focus to nil in the timer.performWithDelay inline function

[lua]

timer.performWithDelay(2000, function() 

      button:removeEventListener(“touch”, touchListener)

      display.getCurrentStage():setFocus( nil ) – remove the focus here

      composer.gotoScene(“next scene”)

end) 

[/lua]

Oh, I just noticed that your event.phase only had “ended”.  You should add “cancelled” to account for events like removed event listeners - since the event did not typically end with the finger lifting

[lua]

if event.phase == “ended” or event.phase == “cancelled” then

     display.getCurrentStage():setFocus(nil)

end

[/lua]

Thanks, that’s roughly what I went for in the end.

Internally Corona must either be checking that the focus object has a touch listener (or it would crash) or it must be caching the touchListener associated with the object/event. If it’s the former case I think removing the check or printing a warning is all that is required. In the latter case something needs to change as I guess there’s a function being persisted that should be garbage collected

Nice idea, however I just tested the following:

local button = display.newRect( display.contentCenterX, display.contentCenterY, 200, 200) local touchListener = function(event) print (event.phase) end button:addEventListener("touch", touchListener) display.getCurrentStage():setFocus(button) timer.performWithDelay(1000, function() button:removeEventListener("touch", touchListener) end)

You’ll only get the began event. The cancelled event is I think only invoked if the app loses focus due to an interruption like a phone call.