How to Develop a Tinder-Like Koloda Animation in Swift: Source Code and Step-by-Step Explanation

Tinder’s swipe right to like and left to skip has become the killer feature of the app and quickly migrated to other applications. Shopping apps like Fancy are following suit with similar swipe-to-like app formats. Viber uses Tinder-like swipes to help users discover new channels, and even Chrome for iOS uses cards to manage bookmarks.

koloda animation

This shows that this animation is on high gear, offering a unique user experience. Items on a card grab users’ attention and urge them to take action, thus increasing user engagement.

Our designer Dmitry Goncharov decided to create an animation that follows Tinder’s trend. We called our Tinder-style card-based animation Koloda which is a Ukrainian word for deck (of cards), and it sounds like fun to us.

The component can be used in different local event apps, and even in Tinder if it adds a possibility to choose dating places. The concept created by Dmitriy was implemented by Eugene Andreyev, our iOS developer. Check out our Swift animation on GitHub.

Read also: How to Create an App Design That Works

How we prototyped Koloda in Pixate

by Dmitry Goncharov

I got inspired by Tinder-like concepts and decided to elaborate on the initial idea to turn Koloda into something unusual. Surprisingly, a new concept came to me in a few hours. My idea was to get rid of the deck of cards and collect each next card from the background.

I designed the mock up in Photoshop and used Pixate for prototyping it. Pixate is a design tool similar to InVision, Marvel, Origami, Form, and others. Even though prototyping in Pixate takes much more time than in InVision, the result looks almost like a real app. The prototype I created reproduced the behavior of cards exactly how I wanted it.

Tinder-like Koloda animation

Now let’s talk a little bit about the process.

The main Pixate’s toolset includes layers, action kit, and animations. After the assets are loaded and located on the artboard, you can start working on layers, and then, proceed to reproducing interactions.

At first, I had to make the cards move horizontally and fly away from the screen once they cross a certain vertical line. I did this with the help of a simple animation which could be implemented under certain if-conditions. I also made the cards change their transparency and spin a bit during interactions.

Then, I needed to make a new card appear in a way as if it collects itself from the background, so had to stretch and scale it. I set a scale for the prototype from 3.5x (the size, when a card is still on the background) to 1x.

Koloda prototyping in Pixate

For a better effect I added a few bounce animations and that was it! The prototype was ready for development. I’d like to conclude with my overall impressions of Pixate.


  • Preview on mobile devices
  • Simple process of prototyping
  • No need to have specific knowledge of the animation basics
  • The prototype looks much like a real iOS or Android app
  • Convenient project sharing (export to computer, external link, or QR-code)


  • A prototype doesn’t cover all apps’ functionality and is rather intended for demonstrating separate features and interactions
  • A single artboard can’t accommodate all screens of a prototype
  • No possibility to export a prototype as code
  • The web app is a bit buggy
  • The basic asset kit is quite limited
  • No timelines for animations (in case you’re used to After Effects)

Despite the disadvantages, Pixate is a great tool that lets designers create native clickable prototypes, reproduce navigation patterns and interactions between screens, but most importantly, it helps the whole team understand the general vector of the project development. You can watch a tutorial by Jared Lodwick to learn more about Pixate.

Now as you know a bit about prototyping Koloda, it’s time we spoke about how we developed the second version of the animation!

Read also: How much does it cost to start an app?

How we built Koloda animation

by Eugene Andreyev

Tinder’s swipe-to-like interface has been borrowed by various apps, so there are a few ready-made mobile libraries and iOS animation examples out there that an app developer can use. At first, I decided to look at MDCSwipeToChoose and TinderSimpleSwipeCards but as it turned out, these solutions weren’t perfect for my particular case.

I wanted the animation to be as simple and convenient as the data source driven views like UITableView. Therefore, I created a custom component for the animation. It consists of the three main parts:

  1. DraggableCardView – a card which displays content.
  2. OverlayView – a dynamic view which changes depending on where a user drags a card (to the left or to the right).
  3. KolodaView – a view that controls loading and interactions between cards.

Koloda Swipe to like tinder interface

DraggableCardView implementation

As I already mentioned, DraggableCardView is a card that displays content. There are many tutorials on the internet that explain how to animate cards in the Tinder style. I chose one of these solutions, looked at it, changed a few things, and here I am with my DraggableCardView implemented with the help of UIPanGestureRecognizer and CGAffineTransform. See the coding part below:

func panGestureRecognized(gestureRecognizer: UIPanGestureRecognizer) {
       xDistanceFromCenter = gestureRecognizer.translationInView(self).x
       yDistanceFromCenter = gestureRecognizer.translationInView(self).y

       let touchLocation = gestureRecognizer.locationInView(self)
       switch gestureRecognizer.state {
       case .Began:
           originalLocation = center

           animationDirection = touchLocation.y >= frame.size.height / 2 ? -1.0 : 1.0      
           layer.shouldRasterize = true

       case .Changed:

           let rotationStrength = min(xDistanceFromCenter! / self.frame.size.width, rotationMax)
           let rotationAngle = animationDirection! * defaultRotationAngle * rotationStrength
           let scaleStrength = 1 - ((1 - scaleMin) * fabs(rotationStrength))
           let scale = max(scaleStrength, scaleMin)
           layer.rasterizationScale = scale * UIScreen.mainScreen().scale
           let transform = CGAffineTransformMakeRotation(rotationAngle)
           let scaleTransform = CGAffineTransformScale(transform, scale, scale)

           self.transform = scaleTransform
           center = CGPoint(x: originalLocation!.x + xDistanceFromCenter!, y: originalLocation!.y + yDistanceFromCenter!)
           updateOverlayWithFinishPercent(xDistanceFromCenter! / frame.size.width)
           //100% - for proportion
           delegate?.cardDraggedWithFinishPercent(self, percent: min(fabs(xDistanceFromCenter! * 100 / frame.size.width), 100))
       case .Ended:
           layer.shouldRasterize = false
       default :

When a user starts dragging a top card, it’s turning and becoming shorter all the way until it reaches an action margin (go or pass an event), and after that it moves away from the screen. The distance to the action margin is represented in percent (100%). While the top card is being dragged, the card below is reacting too – it’s either expanding or contracting. In other words, the animation of an upper and a lower card stops simultaneously.

The overlay gets updated with every move. It changes transparency in the process of animation ( 5% –  hardly seen, 100% – clearly seen).

In order to avoid a card’s edges becoming sharp during movement I used shouldRasterize layer option.

What’s more, I had to consider reset situation which happens once a card fails to reach the action margin (ending point) and comes back to the initial state. I used Facebook Pop framework for this situation, and also for the “undo” action. If you remember, this framework drives animations and transitions in Paper app. It supports dynamic bounce animations and allows to build realistic interactions based on physics with just a few lines of code.

OverlayView implementation

OverlayView is a view that is added on top of a card during animation. It has only one variable called overlayState with two options: when a user drags a card to the left, the overlayState adds a red hue to the card, and when a card is moved to the right, the variable uses the other option to make the UI become green.

To implement custom actions for the overlay, we should inherit from OverlayView, and reload the operation didSet in the overlayState:

public enum OverlayMode{
   case None
   case Left
   case Right

public class OverlayView: UIView {
      public var overlayState:OverlayMode = OverlayMode.None

class ExampleOverlayView: OverlayView {
override var overlayState:OverlayMode  {
       didSet {
           switch overlayState {
           case .Left :
               overlayImageView.image = UIImage(named: overlayLeftImageName)
           case .Right :
               overlayImageView.image = UIImage(named: overlayRightImageName)
               overlayImageView.image = nil




KolodaView implementation

The KolodaView class does card loading and card management job. You can either implement it in the code or in the Interface Builder. Then, you should specify a datasource and add a delegate (optional). After that, you should implement the following methods of the KolodaViewDataSource protocol in the datasource-class:

    func kolodaNumberOfCards(koloda: KolodaView) -> UInt
    func kolodaViewForCardAtIndex(koloda: KolodaView, index: UInt) -> UIView
    func kolodaViewForCardOverlayAtIndex(koloda: KolodaView, index: UInt) -> OverlayView?

Regarding the callbacks, we get them through the delegate’s methods.


Remember our story about developing the Guillotine menu animation for Android where Dmytro Denysenko, our Android developer, had to resort to high school Math course to build a custom interpolator? Geometry also helped me in my iOS development endeavours!

The most interesting thing in the Tinder-like animation is movement of lower cards while a user is dragging an upper card. I wanted to make the Koloda animation flexible, so that I could easily specify the number of cards I want to display on the screen. So I took a piece of paper and started my calculations.

KolodaViewhad to display a correct number of cards below the top card, and make them occupy the right positions when the animation starts. To make it possible, I had to calculate frames for all the cards by adding the corresponding indexes to each element. For example, the first card has an [i] index, the second one would have a [i+1] index, the third – [i+2], and so on.

You can see the calculations of the original frame and the size of the first card below:

developing Tinder cards

And in the code:

private func frameForCardAtIndex(index: UInt) -> CGRect {
       let bottomOffset:CGFloat = 0
       let topOffset = backgroundCardsTopMargin * CGFloat(self.countOfVisibleCards - 1)
       let xOffset = backgroundCardsLeftMargin * CGFloat(index)
       let scalePercent = backgroundCardsScalePercent
       let width = CGRectGetWidth(self.frame) * pow(scalePercent, CGFloat(index))
       let height = (CGRectGetHeight(self.frame) - bottomOffset - topOffset) * pow(scalePercent, CGFloat(index))
       let multiplier: CGFloat = index > 0 ? 1.0 : 0.0
       let previousCardFrame = index > 0 ? frameForCardAtIndex(max(index - 1, 0)) : CGRectZero
       let yOffset = (CGRectGetHeight(previousCardFrame) - height + previousCardFrame.origin.y + backgroundCardsTopMargin) * multiplier
       let frame = CGRect(x: xOffset, y: yOffset, width: width, height: height)     

       return frame

Now, since we know the indexes, card frames, and also a percent at which the animation ends (from the DraggableCardView), we can easily find out where the cards below will go once an upper card is swiped. After than we can implement PercentDrivenAnimation.

As a result, I achieved an easy to use UIView animation for iOS with an interesting name Koloda. Any developer can customize it by setting their content and overlay. In the future, I’d like to make it possible to customize frames’ calculations and animations so that any developer can make their own unique component.

How we developed Koloda v.2

by Eugene Andreyev

The main difference between the first and second versions of Koloda animation is in cards layout. The front card in the new version is placed in the middle of the screen and the back card is stretched on the background. In addition, the back card does not respond to the movement of the front card, and arrives with a bounce effect after the front card is swiped.

Also, the second version of Koloda was easier to build because Dima made a prototype of it in Pixate. Firstly, Pixate allowed me to observe all interactions on a prototype. Secondly, I could acess Pixate studio to see all transformations applied and their order, and then, simply pass them into code without having to manually adjust anything.

Lastly, the second version of Koloda is part of a travel app, unlike the first one which was all about rock'n'roll.

Koloda swiping animation

[Koloda Animation Version 1]

Implementation of KolodaView v.2

To implement Dima’s animation, I had to place the cards differently, so I put the magic method frameForCardAtIndex in the public interface.

In KolodaView inheritor I overrode the method and put the cards in the following order:

override func frameForCardAtIndex(index: UInt) -> CGRect {

       if index == 0 {

           let bottomOffset:CGFloat = defaultBottomOffset

           let topOffset:CGFloat = defaultTopOffset

           let xOffset:CGFloat = defaultHorizontalOffset

           let width = CGRectGetWidth(self.frame ) - 2 * defaultHorizontalOffset

           let height = width * defaultHeightRatio

           let yOffset:CGFloat = topOffset

           let frame = CGRect(x: xOffset, y: yOffset, width: width, height: height)

           return frame

       } else if index == 1 {

           let horizontalMargin = -self.bounds.width * backgroundCardHorizontalMarginMultiplier

           let width = self.bounds.width * backgroundCardScalePercent

           let height = width * defaultHeightRatio

           return CGRect(x: horizontalMargin, y: 0, width: width, height: height)


       return CGRectZero


What’s going on here? We placefrontCard in the middle of KolodaView, and stretch the background card with a scalePercent that equals 1.5.

Koloda Tinder-like animation version 2

Bounce animation for the background card

Since the background card arrives with a bounce effect and changes its transparency while moving, I created a new delegate method:

KolodaView - func kolodaBackgroundCardAnimation(koloda: KolodaView) -> POPPropertyAnimation?

In this method, POPAnimation is created and passed to Koloda. Then, Koloda uses it for animating frame changes after a user swipes a card. If the delegate returns nil, it means that Koloda uses default animation.

Below you can see the implementation of this method in the delegate:

 func kolodaBackgroundCardAnimation(koloda: KolodaView) -> POPPropertyAnimation? {

       let animation = POPSpringAnimation(propertyNamed: kPOPViewFrame)

       animation.springBounciness = frameAnimationSpringBounciness

       animation.springSpeed = frameAnimationSpringSpeed

       return animation


How to prevent background cards from moving?

I also added a delegate method in the new version of Koloda:

func kolodaShouldMoveBackgroundCard(koloda: KolodaView) -> Bool

If a false value is returned, it means that the interactive animation is turned off and cards that are on the background won’t move simultaneously with movements of the front card.

Here is what the animation looks like if the value is false:

koloda animation

And here is what it looks like if the value is true:

koloda animation for iOS

Check out KolodaView animaton on GitHub

4.3/ 5.0
Article rating
Remember those Facebook reactions? Well, we aren't Facebook but we love reactions too. They can give us valuable insights on how to improve what we're doing. Would you tell us how you feel about this article?
Excited to create something outstanding?

We share the same interests.

Contact us

We use cookies to personalize our services and improve your experience on this website and its subdomains. We may use certain personal data for analytics and marketing purposes. Please read our Privacy Policy before using this website.