I think for this post I’d like to continue implementing the rotary control I started in my previous post. In that example, all we really did was figure out how to arrange a series of numbers around a circle. I briefly mentioned how you could implement a rotation on the entire circle, but didn’t get into the details of how that works. So, for this post, we’ll add a bit of touch handling code to take care of rotating the entire circle based on the movement of a user’s finger on the screen.
Although the iOS multitouch system is very advanced, for this example, all we really need to is track a single finger as it rotates around the center of our circle. To do this we’ll implement the touchesBegan:withEvent and touchesMoved:withEvent methods of UIView, which are inherited from UIResponder. Since we’re now building this example out into a more full-fledged control, we’ll go ahead and create a subclass of UIImageView that will take care of displaying the same view as before, as well as handling touches to implement rotation. I’ve added the basic number layout to a “setup method” that’s called from both initWithFrame: and initWithCoder: (which is used when the view is loaded as part of a .xib file). One more method we’ll need is the one that actually calculates the angle of the rotation. What we need to know is the angle between the user’s finger and the center of our view. Now, I’ve learned some of this stuff before, but my trigonometry skills are, let’s just say a bit rusty, so when it came time to do these calculations, I was lost at first. However, Matthijs Hollemans has a nice open source rotary knob control that saved me a lot of time and headache of figuring this out. It turns out that math.h has a very helpful function called atan2(x, y). The introduction on the wikipedia article sums it up nicely: it returns the angle in radians between the positive x axis and the point given by the coordinates. What’s that mean for us? Calculate the x and y distance of a touch from the center of our view and pass that to atan2, which will return us the angle of the touch relative to our center. Once this calculation is figured out, basically all we need to do is store the angle of the initial touch in touchesBegan: and then each time touchesMoved: is called, calculate how much the angle has changed since the last call and pass this in as the rotation value on the CGAffineTransformRotate function to rotate the current transform by that angle. Here’s the source on paste bin. It’s the header and implementation in one paste. It’s just a UIImageViewSubclass, so drop it in IB or add programmatically like you would any other UIImageView to see what it looks like. Or skip the background image and just subclass UIView instead. Everything else will work exactly the same.
NB: In general, any custom touch handling object really should also implement touchesEnded: and touchesCanceled:, but in this particular case, it doesn’t really matter much to us when or how the touches stop. When our touches end (or get canceled), our touchesMoved: method stops getting called, and the UI is simply left at whatever rotation it last received. Once new touches start, we store that initial angle value and continue on from there.
There’s lots more I could show on this whole concept, especially about how to connect the rotation with a certain selection or value, and about how to display more content than what can fit around one circle, but I don’t want to go too much further right now for a few reasons: 1) My implementation works pretty well and is quite flexible, but the animations aren’t all quite right, and it’s still got a few nasty edge case bugs I’m trying to work out, so it’s not yet ready for mass consumption! 2) I’m not sure how much of this will be interesting to everybody. So, what I thought I’d do is share a little video showing at least the visual aspect of what my this thing can do, and if anyone is interested in seeing more code when it’s fully baked, then let me know what you’d like to see, and I’ll share what I’ve learned! Sorry for the bad audio; just using my earbuds microphone and trying to be quiet and not wake up my wife. Oh yeah, and the weird video aspect ratio. I wish the iPhone simulator wouldn’t act so jumpy when you rotate it. Anyway please let me know what you think and what/if you’d be interested in hearing more about with this control.