How To Create an Elastic Animation with Swift

Learn how to make a custom text field with an elastic bouncing animation in this iOS tutorial. By Daniel Tavares.

Leave a rating/review
Save for later
Share

Elastic AnimationEvery decent iOS App out there has custom elements, custom UI, custom animations, etc. Custom, custom, custom!

If you want your app to stand out from the rest, you have to invest time into adding some unique features that will give your app that WOW factor.

In this tutorial, you’ll build a custom text field that makes a sweet little elastic bounce animation when it gets tapped.

You’ll use a number of interesting API’s along the way:

  1. CAShapeLayer
  2. CADisplayLink
  3. UIView spring animations
  4. IBInspectable

Getting Started

Start by downloading the starter project.

The project is based on the Single View Application iOS\Application\Single View Application. It currently has two text fields and one button inside a container view.

Initial storyboard layout

Your aim is to give them an elastic bounce when they receive focus. How do you achieve this, you say?

ElasticHow

The technique is simple; you’re going to use four control point views and one CAShapeLayer, and then animate the control points with UIView spring animations. While they’re animating, you’ll redraw the shape around their positions.

Note: If you’re unfamiliar with the CAShapeLayer class, here is a great tutorial by Scott Gardner that should get you started in no time.

If this all sounds a little complicated, don’t worry! It’s easier than you think.

Create a Base Elastic View

First, you’re going to create your base elastic view; you’ll embed it in UITextfield as a subview, and you’ll animate this view to give your control the elastic bounce.

Right-click the ElasticUI group in the project navigator and select New File…, then select the iOS/Source/Cocoa Touch Class template. Click Next.

Call the class ElasticView, enter UIView into the Subclass of field and make sure the language is Swift. Click Next and then Create to choose the default location to store the file associated with this new class.

First of all, you need to create four control point views and one CAShapeLayer. Add the code below so you end up with the following class definition:

import UIKit

class ElasticView: UIView {

  private let topControlPointView = UIView()
  private let leftControlPointView = UIView()
  private let bottomControlPointView = UIView()
  private let rightControlPointView = UIView()
  
  private let elasticShape = CAShapeLayer()
    
  override init(frame: CGRect) {
    super.init(frame: frame)
    setupComponents()
  }
  
  required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    setupComponents()
  }

  private func setupComponents() {
    
  }
}

The views and layer can be created straight away. setUpComponents() is a setup method that’s called from all the initialization paths. You’re going to implement it now.

Add the following inside setupComponents():

elasticShape.fillColor = backgroundColor?.CGColor
elasticShape.path = UIBezierPath(rect: self.bounds).CGPath
layer.addSublayer(elasticShape)

Here you’re configuring the shape layer, setting its fill color to be the same as the ElasticView's background color and filling the path to be the same size as the view’s bounds. Finally, you add it to the layer hierarchy.

Next, add the following code at the end of setupComponents():

for controlPoint in [topControlPointView, leftControlPointView,
  bottomControlPointView, rightControlPointView] {
  addSubview(controlPoint)
  controlPoint.frame = CGRect(x: 0.0, y: 0.0, width: 5.0, height: 5.0)
  controlPoint.backgroundColor = UIColor.blueColor()
}

This adds all four control points to your view. To help with debugging, this also changes the background of the control points to blue so they’re easy to see in the simulator. You’ll remove this at the end of the tutorial.

You need to position the control points at the top center, bottom center, left center and right center. This makes it so that as you animate them away from the view, you use their positioning to draw a new path in your CAShapeLayer.

You’ll need to do this quite often, so create a new function to do it. Add the following to ElasticView.swift:

private func positionControlPoints(){
  topControlPointView.center = CGPoint(x: bounds.midX, y: 0.0)
  leftControlPointView.center = CGPoint(x: 0.0, y: bounds.midY)
  bottomControlPointView.center = CGPoint(x:bounds.midX, y: bounds.maxY)
  rightControlPointView.center = CGPoint(x: bounds.maxX, y: bounds.midY)
}

The function moves each control point to the correct position on the view’s edge.

Now call the new function from the end of setupComponents():

positionControlPoints()

Before you dive into animations, you’re going to add a view to play around with, so you can see how the ElasticView works. To do this, you’ll add a new view to your storyboard.

Open Main.storyboard, drag a new UIView to your view controller’s view, and set its Custom Class to be ElasticView. Don’t worry about the setting its position; as long as it’s on the screen, you’ll be able to see what’s going on.

Interface Builder

Build and run your project.

g

Look at that! Four little blue squares — these are the control point views you added in setupComponents

Now you’re going to use them to create a path on the CAShapeLayer in order to get the elastic look.

Drawing Shapes with UIBezierPath

Before you delve into the next series of steps, think about how you draw something in 2D — you rely on drawing lines, specifically, straight lines and curves. Before drawing anything, you need to specify a start and end location if you’re drawing a straight line, or multiple locations if you’re drawing something more complex.

These points are CGPoints where you specify x and y in the current coordinate systems.

When you want to draw vector-based shapes like squares, polygons and intricate curved shapes, it gets a little more complex.

To simulate the elastic effect, you’ll draw a quadratic Bézier curve that looks like a rectangle, but it will have control points for each side of the rectangle, whic gives it a curve to create an elastic effect.

Bézier curves are named after Pierre Bézier, who was a French engineer that worked with representing curves in CAD/CAM systems. Take a look at what a quadratic Bézier curve looks like:

Quadratic Bezier Curve

The blue circles are your control points, which are the four views you created earlier, and the red dots are the corners of the rectangle.

Note: Apple has an in-depth Class Reference documentation for UIBezierPath. It’s worth checking out if you’d like to drill down into how to create a path.

Now it’s time to put the theory into practice! Add the following method to ElasticView.swift:

 
private func bezierPathForControlPoints()->CGPathRef {
  // 1
  let path = UIBezierPath()

  // 2
  let top = topControlPointView.layer.presentationLayer().position
  let left = leftControlPointView.layer.presentationLayer().position
  let bottom = bottomControlPointView.layer.presentationLayer().position
  let right = rightControlPointView.layer.presentationLayer().position

  let width = frame.size.width
  let height = frame.size.height

  // 3
  path.moveToPoint(CGPointMake(0, 0))
  path.addQuadCurveToPoint(CGPointMake(width, 0), controlPoint: top)
  path.addQuadCurveToPoint(CGPointMake(width, height), controlPoint:right)
  path.addQuadCurveToPoint(CGPointMake(0, height), controlPoint:bottom)
  path.addQuadCurveToPoint(CGPointMake(0, 0), controlPoint: left)

  // 4
  return path.CGPath
}

There’s a lot going on in this method, so here’s an incremental breakdown:

  1. Create a UIBezierPath to hold your shape.
  2. Extract the control point positions into four constants. The reason you’re using presentationLayer is to get the “live” position of the view during its animation.
  3. Create the path by adding curves from corner to corner of the rectangle, using the control points
  4. Return the path as a CGPathRef, since that’s what a shape layer expects.

You need to call this method when you’re animating the control points because it lets you keep re-drawing a new shape. How do you do that?

CADISPLAYLINK to the rescue

A CADisplayLink object is a timer that allows your application to synchronize activity with the display’s refresh rate. You add a target and an action that are called whenever the screen’s contents update.

It’s the perfect opportunity to re-draw your path and update the shape layer.

First, add a method to call every time an update is required:

func updateLoop() {
  elasticShape.path = bezierPathForControlPoints()
}

Then, create the display link by adding the following variable to ElasticView.swift:

private lazy var displayLink : CADisplayLink = {
  let displayLink = CADisplayLink(target: self, selector: Selector("updateLoop"))
  displayLink.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)
  return displayLink
}()

This is a lazy variable meaning it won’t get created until you access it. Each time the screen updates, it will call the updateLoop() function.

You will need methods to start and stop the link, so add the following:

private func startUpdateLoop() {
  displayLink.paused = false
}
  
private func stopUpdateLoop() {
  displayLink.paused = true
}

You’ve got everything ready to draw a new path whenever your control points move. Now, you have to move them!

Daniel Tavares

Contributors

Daniel Tavares

Author

Over 300 content creators. Join our team.