Tuesday, April 28, 2009

Detecting a Circle Gesture

In Beginning iPhone Development, we have a chapter on gestures. While I think we covered the topic fairly well, I would have liked to have included a few more custom gestures in that chapter. By that point in writing, however, we were already way over on page count. Back then, we also just didn't have a good idea of what other gestures would become common.

I did experiment some with detecting circles back then, but didn't include the code in the book for two reasons. One reason is because the method is really quite long and would have taken a lot of explanation, which we didn't really have spare pages for. The second reasons is that I have a feeling there's a much easier and more efficient way to go about detecting a circle gesture. Because you need to build a certain amount of tolerance into gesture detection, I'm not sure that there is, but there's a least a decent chance there are better ways to detect a circle.

But, since a google search didn't turn up any sample code out there to do this, I decided I'd update the code and post it.

Please, if you see an easier way to do this and want to point it out, by all means, go for it.

You can download the sample project right here.

The sample app is very basic, it just traces the shape you draw and tells you either that it detected a circle, or tells you why it doesn't think the shape counts as a circle. I have not encapsulated this to be re-usable (yet, at least), but the technique is pretty self contained, requiring just three instance variables and overriding three of the touch handling methods. In the sample, the touch-handling code is in the view subclass, which made it easier to do the drawing, but the touch-handling should work in a view controller class as well.

circle.jpg


Here's a basic description of the algorithm I've used. You can download the code to see the exact implementation.


  1. In touchesBegan:withEvent: store the point where the user first tapped the screen

  2. In touchesMoved:withEvent: store off each additional touch point that comes in, storing them in order

  3. in touchesEnded:withEvent:, store the final point, then do a number of checks, doing the computationally cheap ones first so as to avoid having to do any of the more computationally expensive ones by ruling out obvious non-circles. Most of these checks are based on a variance or threshold defined in a constant:

    1. If the end point is too far away from the start point, it's not a circle.

    2. If the drawing operation took more than two seconds, it's not a circle. Okay, it might be a circle, but if it took too long, it's probably not an intentional gesture. You can remove this check if you don't want it.

    3. If there are not a certain number of stored points, it can't be a circle. This is to avoid false positives from lingering taps.

    4. Loop through the stored touches and determine the top-most, bottom-most, left-most, and right-most points, and use that to determine an approximate center and an approximate average radius.

    5. Loop through the stored points in order, making sure:

      1. Each point's distance from the center is within a certain variance of the approximate average radius.

      2. That the angle formed from the start point, to the radius, to the current point flows in a natural order. The angle should continuously increase or decrease. If it doesn't go in sequence, then it's probably a more complex shape than a circle.





As I stated earlier, there's probably an easier, more accurate way to detect a circle, but until one comes to light, this is at least functional.

No comments:

Post a Comment