A “Touchy” Subject… (Updated)

Working on a little iPhone puzzle game using cocos2d-iphone and in this game the user can touch an object and drag it around.  The user can also tap in an open space to place an object.

Doing some testing of the touch handling code and first notice a minor item that didn’t match my expectations:

When you drag an object off the physical screen you don’t get touchesCancelled message, you get touchesEnded.  Unexpected.  Seems like a “cancel” to me.  But ok, can code around that easily enough.

Then I decided to test what happens when someone calls the phone while the user is dragging an object. This one gets stranger.

When the call comes in the “Answer / Decline” overlay comes up over the game screen and if you leave your finger down on the screen and move it defies expectations (at least mine!):

– first you get a touchesCancelled call (and no touchesEnded). This is good.

– Then you get a touchesBegan call (even though you didn’t release and re-touch! Even though your views aren’t even in the front any more!).

– Then you get a touchesMoved call (or many depending on your finger movement).

– Then, if you release to go press the “Decline” button (this game is good – they can leave a message! 🙂 ha!), you get a touchesEnded call.

If you instead release and then touch and drag and release again, you’ll get the Touches- Begin/Move/End sequence again even though you can’t see the game board and there is another view in front.

This is highly unexpected to me since I’m used to Cocoa where you touch the view you touch in (or window) and not THROUGH some view onto another (especially when the view you’re touching “through” has buttons and is thus obviously wanting it’s own touches!).

Actually, turns out this isn’t even what the docs suggest should happen. In Event Handling Guide for iOS: Event Types and Delivery say:

Touch events. The window object uses hit-testing and the responder chain to find the view to receive the touch event. In hit-testing, a window calls hitTest:withEvent: on the top-most view of the view hierarchy; this method proceeds by recursively calling pointInside:withEvent: on each view in the view hierarchy that returns YES, proceeding down the hierarchy until it finds the subview within whose bounds the touch took place. That view becomes the hit-test view.

Sure seems like the “Accept/Decline” view which is visually in front should get these events and not the view behind it…

So, how to tell if the touches are for your game board or the “Accept/Decline” overlay?

Turns out there is an applicationState property of the shared UIApplication that will indicate that you are in an “inactive” state.  So we can use that to ignore the touches when inactive.

Oh. Wait.  That’s iOS 4.0 and later only…  You want to support 3.1.3 also?  Or 3.2 on the iPad? (no calls on that obviously.  What else can come up and do something like this? push notification maybe…?).   Well, I don’t yet know if a) the problem exists there, or b) how to fix it there if it does!

I’ll have to test that when my iphone running 3.1.3 returns home tonight.  Update to follow…

10.10.10 – update:

So, I realized that Timer Done notifications will also create a situation like this.  Also SMS message received notifications.  (and likely Push Notifications which I didn’t test).

I tried the both the timer (in Clock application) case and the SMS message received case, both in iOS 4.1.  In both cases we get the same change in applicationState instance variable, however the system does not send a touchesCancelled event and the touch movement keeps going without getting an intermediate touchesBegan message as the system does for incoming phone call events.

In all three cases the UIApplicationDelegate protocol method applicationWillResignActive gets called so that’s definitely a place to consider “fixing” this issue.

Cocos2d-iPhone pauses the animation (well, slows it down dramatically), but does nothing about touch handling.

Haven’t had a chance to try the older iPhone running 3.1.3 yet (busy weekend).

10.10.10 – update 2:

So to work around this I just implemented code to use the applicationState instance variable on UIApplication sharedApplication and basically if I get a touch Moved/Ended event and I’m currently tracking a touch then I cancel the tracking and clear the tracking-a-touch flag that I set on touch Begin.  If I’m not currently tracking a touch and the application is not active, I just ignore any touch events.   Still need to create a 3.1.3 compatible version of this.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: