Circular Scrolling + Inertia

Why Inertia?

I’ve talked a lot on this blog about the circular scroll-view/table-view thing I created for my metronome app, and while I’m very pleased with how the whole thing turned out, I was still annoyed that I hadn’t figured out how to do one thing: give it inertia like UIScrollView. A commenter on one of the posts even asked me about inertia, and I basically just flat out said I’d like to, but it would be too hard! But after that, it just kept bugging me; there had to be a way. Inertial scrolling on iOS – and now on OS X too – just feels so nice. It’s one of those things that many people probably don’t even think about or notice until it’s gone. After feeling the smooth scrolling and realistic deceleration of UIScrollViews, my little rotary control just feels like garbage. I mean, come on, you give it a little flick and it stops rotating immediately when you lift your finger!? Lame! Frankly, my control just doesn’t feel quite completely at home on iOS yet, and I want to change that.

Maybe UIScrollView Can Help!

I got really exciting after watching the WWDC 2012 video for Session 223: Enhancing User Experience with Scroll Views. In the presentation, they give an excellent demo of how to leverage the physics of UIScrollView even when your app uses OpenGL. The UIScrollView is not used for any drawing, but by passing touches through it, listening for the scrollViewDidScroll, and reading the contentOffset, you can enjoy all the benefits of UIScrollView like inertia and edge bounce without actually drawing your content in the scroll view itself. Maybe, I thought, I can use that same trick on my circular scroller. Using the same techniques from the video, I was able to get up and running with the same circular action as well as a bit of UIScrollView provided inertia, but it just did not feel right at all. The geometry of linear scrolling and deceleration just don’t line up with what I need in terms of angular rotation.

Eureka!

The video technique didn’t work out quite like I had hoped, but I still took away one key idea: implement the inertia in the touch handler, not in the circular view itself. Seeing as how no one has seen the details of my implementation, that might not make sense to anyone but me right now, but I’ll try to explain. At its most basic, my control has two parts: the transparent view which interprets the touches as circular movements (a gesture recognizer, if you will, although it’s not an actual UIGestureRecognizer subclass) and the display view that receives notifications of those movements and rotates, swapping elements in and out sort of like UITableView cells. I had already implemented a bit of animation in the view component that allowed it to center on a certain cell, and I kept trying to think how I could make this also work with a longer rotation, and I was running into a lot of problems with the cell swapping during the rotation animation. But, if I implement the inertia on the control side and use the same delegate call for rotation that comes from an actual touch OR from the inertia, the view side doesn’t even know the difference. And it actually worked pretty well without much modification at all!

Check out the Repo

So, here’s the real/final point of this post: I’d love to get this whole rotation control thing (or most of it, at least) out into the wild as an open source component, but it’s been hard finding the time. It’s a fairly complex API, and I don’t feel like I can just release it without some more clarity and focus, as well as a nice sample project. So, I’m going to try to get it out there piece by piece if possible. I’ve put up a little bare bones project on GitHub – my first! – to test the waters. Let me know what you think, and of course, feel free to fork it and make it better. Right now it’s just a simple version of the rotation control view (RDDRotationControlSurface) which is hooked up to a UIViewController delegate that spins a UIImageView based on the input. Wow, that sounds way more complicated than it is – it’s just spinning a little picture of a very nice-looking German (or Austrian; can’t remember where this picture was taken) man in a cool getup. Don’t ask me why I chose this picture; it was there, and I went with it!

PS: You may be wondering why I didn’t go with a UIGestureRecognizer subclass for this. As far as I can tell, the gesture recognizer API would not work very well with something like this which needs to continue sending updates even after all touches have stopped. So, in the end, I’d still end up with a UIView subclass of some kind. Doesn’t mean GR couldn’t help here, but I just didn’t go that route.

Advertisements

3 thoughts on “Circular Scrolling + Inertia

  1. hi Ryan! i stumbled across your blog after searching for a rotary/circular UI control with a UIScrollView-esque feel to it!

    but in the meantime i just read almost all of your blog entries so far and also purchased a copy of your Click app, just to see how i turned out and for some design inspirations! i have no affinity for music and i don’t play any muscial instrument so far, but maybe i’m going to start with it after playing around with Click! 😉 great app with great design, keep on going like that! 🙂

    i’ll try to build up on your solution for the circular control for sure. as some possible ideas flush through my brain, my approach would be to make a robust control (UIControl) out of it and to pack the logic for the inertia and bounce behavior into some kind of “circle physics” object which than could be attached to the UIControl object etc.! you didn’t go with gesture recognizers for obvious reasons. i’d like to try something out with the built-in tracking methods of UIControl maybe, to make this whole thing integrate with a robust design. let’s see how it’s gonna turn out!

    do you have any idea up your sleeve how to extend the given inertia logic to adapt for bounce behavior like UIScrollView? so when a treshold value is reached during rotation, the wheel stops spinning in a nice bounce-esque fashion!

    anyway, your blog is great so far! i’ts already on my RSS feed list! 🙂
    keep on the good work! i’ll report back with my solution if i find the time to consolidate this into a full-fledged UIControl, which would be great!

    1. Thanks so much for the kind words, and for purchasing Click as well!! And I apologize for the slow response. As for the bouncing effect, I actually did implement that in Click. The tempo selection “wheel” bounces once you reach the minimum or maximum tempo. The rotation motion continues, but slows down and then stops completely after a certain threshold. The basic idea with the bouncing is that once the min or max point is reached, the actual on screen movement is half of the touch movement. As far a I can tell, this is how UIScrollView works too. There’s a one-to-one correlation between the touch and the object onscreen until it starts bouncing. Then, it’s a 2-1 ratio. So, if the user rotates by 4 degrees, say, then the circle view itself would only rotate by 2 while bouncing. I’m still working on how to combine the bouncing effect with the inertial spin though. Good luck with whatever project it is you’re working on!

  2. Hey. I came across this project witch is exactly what I need for my “spin-the-bottle” like quizz game. I having some trouble making it work in my project though. I integrate it exactly the same as in you did but the way it spins is weird. It looks like it shrinks the picture until it’s flat, then it flips it and then it does it again after 180 kind of spin. I would like to show you more details but can’t post any pics here. Do you have any idea why this happens? I’m using storyboards and the app is targeted for ios 6.1 in XCode

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 )

Google+ photo

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

Connecting to %s