Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all articles
Browse latest Browse all 4370

How To Create an Elastic Animation with Swift

$
0
0

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!

UIView Spring Animations

Apple is really good at adding new features in every iOS release, and spring animations are one of its recent inclusions that makes it easy to up your app’s WOW factor.

It allows you to animate elements with custom damping and velocity, making it more special and bouncy!

Note: If you would like to master animations, check out iOS Animations by Tutorials.

Add the following method to ElasticView.swift to get those control points moving:

func animateControlPoints() {  
  //1
  let overshootAmount : CGFloat = 10.0
  // 2
  UIView.animateWithDuration(0.25, delay: 0.0, usingSpringWithDamping: 0.9, initialSpringVelocity: 1.5,
    options: nil, animations: {
    // 3
    self.topControlPointView.center.y -= overshootAmount
    self.leftControlPointView.center.x -= overshootAmount
    self.bottomControlPointView.center.y += overshootAmount
    self.rightControlPointView.center.x += overshootAmount
  },
  completion: { _ in
    // 4
    UIView.animateWithDuration(0.45, delay: 0.0, usingSpringWithDamping: 0.15, initialSpringVelocity: 5.5,
      options: nil, animations: {
        // 5
        self.positionControlPoints()
      }, 
      completion: { _ in
        // 6
        self.stopUpdateLoop()
      })
  })
}

Here’s the step-by-step breakdown:

  1. overshootAmount is the amount the control points will move by.
  2. Wraps the upcoming UI changes in a spring animation that lasts a quarter of a second. If you’re new to spring animations but good at physics, check out the UIView class reference for a detailed explanation of the damping and velocity variables. For the rest of us non-rocket scientists, just know that these variables control how the animation bounces. It’s normal to play with the numbers to find a configuration that feels right.
  3. Move the control points up, left, down or right – this will be animated.
  4. Create another spring animation to bounce everything back.
  5. Reset the control point positions – this will also be animated.
  6. Stop the display link once things stop moving.

So far, you haven’t called animateControlPoints. The main purpose of your custom control is to be animated once you tap on it, so the best place to call the above method is inside touchedBegan.

Add the following to it:

override func touchesBegan(touches: Set, withEvent event: UIEvent) {
  startUpdateLoop()
  animateControlPoints()
}

Build and run, and then tap your view. Voila! :]

Elastic Magic

Refactoring and Polishing

Now you’ve gotten a glimpse of the cool animation, but you have a little more work to do to make your ElasticView more abstract.

The first obstacle to clear out is overshootAmount. At the moment, it’s hardcoded with a value of 10, but it would be great to change its value both programmatically and via Interface Builder.

IBDesignable

One of the new features of Xcode 6.0 is @IBInspectable, which is a nice way of setting custom properties via interface builder.

Note: If you want to learn more about @IBInspectable, then please read Modern Core Graphics with Swift by Caroline Begbie.

You’re going to take advantage of this awesome new feature by adding overshootAmount as an @IBInspectable property, so that each ElasticView you create can have a different value.

Add the following variable to ElasticView:

@IBInspectable var overshootAmount : CGFloat = 10

Reference the property in animateControlPoints() by replacing this line:

let overshootAmount : CGFloat = 10.0

With this line:

let overshootAmount self.overshootAmount

Head over to Main.storyboard, click on ElasticView and select the Attributes Inspector tab.

lBInspectable

You’ll notice a new tab that shows the name of your view and an input field named Overshoot A…

For every variable you declare with @IBInspectable, you’ll see a new input field in Interface Builder where you can edit its value.

To see this in action, duplicate ElasticView so you end up with two views and place the new one above your current view, like so.

m

Change the value of Overshoot Amount in the original view to 20 and 40 in your new view.

Build and run. Tap on both views to see the difference. As you can see, the animations vary slightly, and are dependant on the amount you’ve entered in interface builder.

n

Try changing the value to -40 instead of 40, and see what happens. You can see the control points animating inwards but the background doesn’t seem to be changing.

o

Are you ready to fix that on your own? I bet you are!

I’ll give you one clue: You’ll need to change something inside the setupComponents method. Try it on your own, but if you get stuck, take a peek at the solution below.

Solution Inside: Solution SelectShow>

Well done, you’ve finally completed your ElasticView.

Now that you have an ElasticView, you can embed it in different controls, such as text fields or buttons.

Making an Elastic UITextfield

Now that you have built the core functionality of your elastic view, the next task is to embed it into a custom text field.

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

Call the class ElasticTextField, enter UITextfield into the Subclass of field and make sure the language is Swift. Click Next and then Create.

Open up ElasticTextField.swift and replace its contents with the following:

import UIKit
 
class ElasticTextField: UITextField {
 
  // 1
  var elasticView : ElasticView!
 
  // 2
  @IBInspectable var overshootAmount: CGFloat = 10 {
    didSet {
      elasticView.overshootAmount = overshootAmount
    }
  }
 
  // 3
  override init(frame: CGRect) {
    super.init(frame: frame)
    setupView()
  }
 
  required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    setupView()
  }
 
  // 4
  func setupView() {
    // A
    clipsToBounds = false
    borderStyle = .None
 
    // B
    elasticView = ElasticView(frame: bounds)
    elasticView.backgroundColor = backgroundColor
    addSubview(elasticView)
 
    // C
    backgroundColor = UIColor.clearColor()
 
    // D
    elasticView.userInteractionEnabled = false
  }
 
  // 5
  override func touchesBegan(touches: Set, withEvent event: UIEvent) {
    elasticView.touchesBegan(touches, withEvent: event)
  }
}

There’s a lot going on in there! Here’s a step-by-step breakdown:

  1. This is A property to hold your ElasticView.
  2. An IBInspectable variable called overshootAmount, so you can change your control’s elasticity via Interface Builder. It overrides didSet and just sets the overshootAmount of your elastic view.
  3. The standard initializers for the class, both so which call a set up method.
  4. This is where you set up the text field. Let’s break it down even further:
    1. Changes clipsToBounds to false. This lets the elastic view go beyond its parent’s bounds and changes the border style of the UITextField to .None to flatten the control.
    2. Creates and adds ElasticView as a subview of your control.
    3. Changes the backgroundColor of your control to be clear; you do this because you want the ElasticView to decide the color.
    4. Finally, this sets the ElasticView’s userInteractionEnabled to false. Otherwise, it steals touches from your control.
  5. Overrides touchesBegan and forwards it to your ElasticView so it can animate. :]

Head over to Main.storyboard, select both instances of UITextfield and change their classes from UITextField to ElasticTextField in the Identity Inspector.

Also, make sure you delete both instances of ElasticView that you added for testing purposes.

textfields

Build and run. Tap on your textfield and notice how it doesn’t actually work:

Elastic UI

The reason is that when you create an ElasticView in code, it gets a clear background color, which is passed on to the shape layer.

To correct this, you need a way to forward the color to the shape layer whenever you set a new background color on your view.

Forwarding the Background Color

Because you want to use elasticShape as the primary background of your view, you have to override backgroundColor inside ElasticView.

Add the following code to ElasticView.swift:

override var backgroundColor: UIColor? {
  willSet {
    if let newValue = newValue {
      elasticShape.fillColor = newValue.CGColor
      super.backgroundColor = UIColor.clearColor()
    }
  }
}

Before the value is set, willSet is called. You check that a value has been passed, then you set fillColor for elasticShape to the user’s chosen color. Then, you call super and set its background color to clear.

Build and run, and you should have a lovely elastic control. Yippee! :]

TextTap3

Final Tweaks

Notice how close the placeholder text from UITextField is to the left edge. It’s a bit snug, don’t you think? Would you like a go at fixing that on your own?

No hints this time. If you get stuck, feel free to open up the solution below.

Solution Inside: Solution SelectShow>

Removing Debug Information

Open up ElasticView.swift and remove the following from setupComponents.

controlPoint.backgroundColor = UIColor.blueColor()

You should be proud of all the work you’ve done so far! You’ve turned a standard UITextfield into some funky elastic thing and created a custom UIView that can be embedded in all sorts of controls.

Where To Go From Here?

Here’s a link to the completed project.

You have a fully working elastic text field. There are many more controls where you could apply these techniques.

You’ve learned how to use view positions to redraw a custom shape and add bounce to it. With this skill, it could be said that the world is your oyster!

To take it a step or few further, you could play around with some different animations, add more control points for some crazy shapes, etc.

Check out easings.net; it’s great playground for working with different animations that utilize easings.

After you get comfortable with this technique, you could have a go at integrating BCMeshTransformView into your project. It’s a neat library created by Bartosz Ciechanowski that lets you manipulate individual pixels from your view.

Imagine how cool it would be if you could morph pixels into different shapes. :]

It’s been fun to walk you through how to create an elastic UI control with Swift, and I hope that you’ve learned a few things along the way. If you have questions, comments or great ideas about how to animate in Swift, please chime in below. I look forward to hearing from you!

The post How To Create an Elastic Animation with Swift appeared first on Ray Wenderlich.


Viewing all articles
Browse latest Browse all 4370

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>