From JS to UICollectionView – Building a custom Roulette Wheel Stack Control in iOS

Update: Super glad to have this control accepted by CocoaControls! Thanks guys!


This would probably be the last post I’ll write with reference to the Yahoo TimeTraveler app, and I want to make it interesting by showing how the implementation of a custom UIControl evolved throughout time.

The control we were trying to build was a roulette wheel of city cards. I guess the idea came from spinning a globe and deciding on your next travel destination based on where your finger landed. Simple enough, but when it came to building the damn thing it was anything but.

A video of the control in action, from release v2.0.2

First Try – HTML and Javascript

The app started life as a hybrid app with most of its UI done with HTML. When the page began to load, an initialization method looped through an array of elements, calculated each element’s rotation angle (n/360), and applied it to the element’s style attribute.

skeleton of the roulette wheel
skeleton of the roulette wheel

The next step was to create a swipe gesture recognizer on the main div element, and add logic to animate the cards on both sides in and out of the viewport when a swipe occurs.

As seen in the clip below, this approach seemed to work pretty well. But there were obvious issues like jerkiness in the scrolling, missed strokes, and absence of inertia. It had a long way to go to reach the smoothness and speed of custom controls like iTunes’ coverflow.

Second Try – Going Native

Taking a cue from the javascript approach, I began with a plain UIView as the blank canvas, added a pile of UIImageViews on top, and applied CGAffineTransform rotations to each UIImageView around the center of the view. Then I finished with a UIPanGestureRecognizer which rotated the entire UIView upon swiping. The resulting construct looked like this:

roulette wheel - uiview
sketch of the UIView roulette view in Keynote

There was one big caveat. In the original design, the cards on the right had to be always stacked on the top. I tried to do so in scrollViewDidScroll:, using the UIImageView’s frame origin to work out which cards were in the viewport. However, I didn’t realize that the frame size of a UIView would be distorted after a transformation, so determining whether a transformed cell lied within a certain boundary was super difficult.

The idea of using UIView animation and GestureRecognizers also backfired. It was near impossible to cancel on-going animations and start new ones based on user input. Suppose a user does a quick flick, then taps on a card. He would expect the wheel to stop spinning and the card slowly lock to the upright position when the touch was released. Firing off UIView animations wasn’t the way to do it. On the other hand, not using UIView animations meant there was no easy way to add inertia to the scroll.

Third Try – Using a UIScrollView

With little point in pursuing that design further, I turned to my then-manager for advice. “It doesn’t necessarily have to be a wheel, it just have to look like one”. He said.

That got me thinking in a different direction.

He suggested looking into implementations of coverflow, which can be interpreted to having very similar requirements as we did, with the major difference being coverflow making use of 3D perspective transforms and us 2D rotations. There is also the added advantage of having silk smooth animations (with inertia, can’t stress that enough) backed by the almighty UIScrollview.

Restrained from coding right away, I prototyped on paper and in Keynote (unorthodox, but super effective) — laying out the cards in a horizontal table (slide 1), applying varying degrees of rotation to each (slide 2), and figuring out what levels of translations were needed to make the cards appear like a wheel (slide 3). I then needed to find the offsets so I can deduce where the cards were by covering the design with a 1:1 replica of the iPhone chrome (slide 4).

slide 1
slide 1
slide 2
slide 2
slide 3
slide 3
slide 4
slide 4

After getting comfortable with the constraints time was spent building a prototype. Unfortunately the code can’t be shown here but they were the usual UITableViewCell and UITableView subclasses. To prevent cards from “popping in” when views were queued and de-queued, the viewport had to be extended a bit, 50px on each side to be exact.

Here’s the version we shipped with. We must have gone through 5-6 builds until we all felt pleased with it.

Fourth Try – UICollectionView

The seed for re-implementing the roulette wheel control in UICollectionView had been planted in my head since I saw the demo in WWDC. When I actually got round to doing it, it was rather straightforward thanks to tutorials from Ray Wenderlich and various github projects. Subclassing UICollectionViewFlowLayout and overriding layoutAttributesForElementsInRect:rect was all I had to do.

The result:

The layout subclass where 99% of the logic resided:


Using UICollectionView was awesome. Not only was the animation super smooth (helped by the god-send shouldInvalidateLayoutForBoundsChange:), and you get features like center snapping for free. It drastically reduced the amount of custom code I had to write, and offered far greater performance with FPS going through the roof. UICollectionView was definitely the greatest developer feature of iOS6.

A little bit of LOC comparison:

HTML – 1385 lines of javascript, not including the YUI framework and related CSS.
UIView – ? (code was thrown away)
UIScrollView – 945 lines
UICollectionFlowLayout – 213 lines

To close off, here’s what I learned from the year long experience of implementing this custom iOS control with different approaches.

  • Work from native UIKit elements where possible.
  • Cheat! Yes, really. There’s a difference between cutting corners and doing something clever.

Remember, it doesn’t necessarily have to be a wheel, it just need to look like one.


A demo app of the UICollectionView version of the wheel can be found at


11 thoughts on “From JS to UICollectionView – Building a custom Roulette Wheel Stack Control in iOS

  1. I am implementing this in my iOS app, is there a way to know which image is snapped to the center without the user specifically tapping it? I am using this for a user to scroll through images and tap a share button, so I would like to automatically use the current image in the view.

    1. Hi Jo, there are several ways to go about this:

      1. In the method layoutAttributesForElementsInRect:, an array of cells in the specified viewport is given and for each cell, the amount of rotation to apply is deduced. Therefore, we simply have to compare these cells and find the one with the minimum rotation amount.

      – (NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
      NSArray * array = [super layoutAttributesForElementsInRect:rect];

      NSMutableArray * modifiedLayoutAttributesArray = [NSMutableArray array];

      __block float mininumRotatedByValue = 10.0f;
      __block NSIndexPath * minimumRotatedCellIndexPath = nil;
      CGFloat horizontalCenter = (CGRectGetWidth(self.collectionView.bounds) / 2.0f);
      [array enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes * layoutAttributes, NSUInteger idx, BOOL *stop) {

      CGPoint pointInCollectionView = layoutAttributes.frame.origin;
      CGPoint pointInMainView = [self.superView convertPoint:pointInCollectionView fromView:self.collectionView];

      CGPoint centerInCollectionView =;
      CGPoint centerInMainView = [self.superView convertPoint:centerInCollectionView fromView:self.collectionView];

      float rotateBy = 0.0f;
      CGPoint translateBy = CGPointZero;
      if (pointInMainView.x < self.collectionView.frame.size.width+80.0f){
      translateBy = [self calculateTranslateBy:horizontalCenter attribs:layoutAttributes];
      rotateBy = [self calculateRotationFromViewPortDistance:pointInMainView.x center:horizontalCenter];
      NSLog(@"rotateBy: %f", rotateBy);
      if (abs(rotateBy) < mininumRotatedByValue){
      mininumRotatedByValue = rotateBy;
      minimumRotatedCellIndexPath = layoutAttributes.indexPath;

      // … abbreviated
      NSLog(@"minimum rotated cell: %i", minimumRotatedCellIndexPath.item);
      return modifiedLayoutAttributesArray;

      2. UICollectionView is a scrollView, so you can make your class a delegate of the collection view, and implement – (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView and – (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate. Inside, you can loop through the array of visible cells and use their attributes to determine which card is the center card:

      – (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
      NSArray * visibleCards = self.collectionView.visibleCells;
      [visibleCards enumerateObjectsUsingBlock:^(RVCollectionViewCell * visibleCell, NSUInteger idx, BOOL *stop) {
      NSLog(@"visible cell: %@", visibleCell.imageName);

      Hope this helps!

  2. I have been looking for this quite a while. I intend to develope a single zero roulette wheel free app for smart phone so that addicted gamblers can play 24/7 without and financial damage. LOL

    1. Thanks! Glad you found the post interesting.

      To change the control to vertical, the easiest approach is to simply apply a CGAffineTransform rotate on the view. Otherwise you’d need to override RVCollectionViewLayout and change the rotation point and CATransform3D to work differently.

      1. Hi Kenny,

        thanks for the quick reply, if you will to show how it will be done. it is greatly appreciated :)

        i changed the collectionview to -45 but the result wasn’t that great.

        [self.collectionView setTransform:CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(-45))];


  3. Hi,
    I was working on a prototype using framer.js, ( where I trying to achieve a similar effect to this,I managed to do the rotation part, but I was stuck with the translation offset, while looking for help I found this.

    The part I’m stuck at is the transition between [slide 2] and [slide 3], I have noticed that in the code offered you call the method [calculateTranslateBy] but its result is never used in the main loop.

    I would really appreciate your help to understand that part.
    Thank you.

  4. Hi Mohammed, the translation+rotation was necessary because CoreAnimation doesn’t offer a built in way to rotate around a certain point. For CSS you might want to look at achieving a similar effect using attributes like transform-origin.

  5. Hi,

    Is there anyway to force the view to only have one row of cells? i’m confused as to which method calculates how many rows will be displayed. The code is awesome though! thanks for posting!

Leave a Reply

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

You are commenting using your 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