When Debugging Sucks (More than usual)
For my iDevBlogADay post today, I considered writing a tutorial about debugging, but ultimately decided against it. For one thing, it would most likely have just ended up being me venting about this freakin’ bug that plagued me for months on and off. For another thing, who would really trust a lesson in debugging written by a guy who took 4 months to solve one little bug So, I’ll get on to my main topic in a minute, but first, a few quick tips from my experience:
1. Just because the bug first appears when you start adding animations, doesn’t mean it’s a problem with how you calculate your animations; it could have been hiding there in the background all along and only appeared when you started trying to automate some things instead of reacting directly to user input…grrr
2. Just because the bug appears to be a math error, it may not be. Remember that tiny little “if…else” statement that you set up WAY back at the beginning of this project? Yeah, you didn’t account for the rare, but oh-so-important case when you’ll actually need to run the code in BOTH branches. “if…if” FTW!
3. When your “bug” is actually two very similar bugs that manifest in nearly identical ways, but are caused by slightly different circumstances, just face it, you better start praying and offering some sacrifices to the programming gods, ’cause they are obviously angry with you. Good luck trying to find a way to reliably reproduce THAT issue!
4. Do not, I repeat, do not stop digging until you *know* that you’ve found the root cause. “Well, the problem is happening somewhere in this general area, so maybe if I rework this part right here and see what…” STOP!! Keep. Searching. Now, it’s one thing to make adjustments as a part of the bug-finding process. It’s a whole other thing to just start changing things hoping the bug will disappear. It’s not like I didn’t know that before, it’s just that, *hangs head in shame* I got so desperate and…sniffle…sob…*takes a deep breath*…OK, I’m back. On the bright side, my shot-in-the-dark refactoring adventures DID result in a much better understanding of my code and in the end, a simpler, cleaner implementation.
Now, onto the main topic: simple gesture recognition techniques
Tap, Drag, Hold
iDevices are all about multitouch. It’s one of the breakthrough features that made the original iPhone so unique, and it’s implementation in iOS has been copied by every other similar smartphone and tablet out there. Over the years, as people got used to the technology, and especially after the introduction of the larger screen iPad, gestures have become a huge part of the iOS experience. Personally, I have yet to dive into programming with UIGestureRecognizers, but I’ve read the docs and watched the WWDC videos, and it really as amazing. If you’re looking to implement complex, multi-touch, multi-tap gestures on many overlapping and interacting views, then there is some really powerful stuff in there. But what if you don’t need all that? For me, the most important gestures for my metronome app are simple taps and drags, along with translating their locations into angles around a circle. I’ve demonstrated the basics of this second part in this post about rotating a circle along with a user touch. Rather than learn a whole new framework and go through the process of attaching multiple gesture recognizers to my view, with different methods for different gestures, I was looking for a simple way to implement single taps and drags right within the UIControl touch handling methods I was already using. Turns out, it’s not hard at all. (I’m using a UIControl subclass for this, but there are very similar touch handling methods on a basic UIView that will get you the same results).
Touch handling for UIControls consists of 4 main methods:
This particular implementation does not handle multiple touches, so the “multipleTouchEnabled” property of the view should be set to NO. In the -beginTracking method, we’ll grab the location of the touch that’s passed in with, conveniently enough, “[touch locationInView:self]” and store it in a CGPoint variable. We’ll also need a “tap” flag, which we’ll set here to YES.
For however long this individual touch stays down, we’ll get calls in the -continueTracking method each time the touch moves. Compare the new location to the initial touch location we stored above, and – using good old Pythagorean Theorem – calculate the distance between them. If the touch has moved more than some predefined distance, then set the “tap” flag to NO, and continue on with whatever tracking you need to do. In my case, I start tracking the angle of rotation around the center and send this info to a delegate.
In the -endTracking method, called when the finger is lifted, simply check the “tap” flag: if the touch never moved from the original location, then “tap” is still YES, and you execute the code you want for handling a tap. Otherwise, handle the end of the dragging gesture. Without too much more work, you could also implement a tap and hold option: start a timer in -beginTracking. If the touch moves, cancel the timer at the same time you set the “tap” flag to NO. If the timer fires, then you know the user touched and held without moving. If desired, it would also be possible with a timer to set a short delay and listen for multiple taps, but then we’re getting into territory that might be good for an actual UIGestureRecognizer, or at least some different implementation that can properly make use of UITouch’s tapCount property.
All in all, I’m really happy with how this turned out. Once I thought about it for a minute, it was far easier than I anticipated. It’s a nice, simple implementation that can be stuck directly into the existing touch handling methods of UIControl/UIView, and it even lets you easily define what kind of “wiggle room” you want to allow before a tap becomes a drag, as well as easily set the time needed to count as a “tap and hold” if you go that route. With just a few lines of code and a couple flags checked in the right places, you can implement different actions for tap, drag, and tap and hold, plus various combinations of the three, on any UIControl or UIView subclass.