Everybody knows importance of animation in iOS. If you are an iOS developer, I’m pretty sure you must have used animations at least once which also makes me assume that you are familiar with CAMediaTimingFunction.

CAMediaTimingFunction represents one segment of a function that defines the pacing of an animation as a timing curve. The function maps an input time normalized to the range [0,1] to an output time also in the range [0,1].

You can create a media timing function by supplying your own cubic Bézier curve control points using the init(controlPoints:_:_:_:) method or by using one of the predefined timing functions.

The interesting one is init(controlPoints: Float, Float, Float, Float) which takes four float parameters. The fact is CAMediaTimingFunction is created using cubic–bezier curve which is quite commonly used in digital drawing tools. It needs four Points to draw a curve. How the actual drawing takes place is not in the scope of this article, also there are plenty of tutorials and videos available to explain this.

Now among these 4 points one is starting point, one is end point and rest two are called control points which are responsible for the curvature of curve. In case of CAMediaTimingFunction start Point and end points are fixed i.e [(0,0), (1, 1)]. Now we need two control points to draw an easing function, which is exactly what - init(controlPoints: Float, Float, Float, Float) asking you to provide. So if we create CAMediaTimingFunction with CAMediaTimingFunction(controlPoints: x1, y1, x2, y2) we have required four points to create the cubic–bezier curve, which are [(0, 0), (x1, y1), (x2, y2), (1, 1)].

It is no surprise that default timing functions CAMediaTimingFunctionName.linear, CAMediaTimingFunctionName.easeIn, CAMediaTimingFunctionName.easeOut, CAMediaTimingFunctionName.easeInEaseOut, CAMediaTimingFunctionName.default are also created using these four points.

Lets try to find out what these control points are.

Control points of default easing functions.

Code:

import UIKit
let cords: UnsafeMutablePointer<Float> = UnsafeMutablePointer.allocate(capacity: 2)
let defaultTimingFunctionOptions = [CAMediaTimingFunctionName.linear,
                                    CAMediaTimingFunctionName.easeIn,
                                    CAMediaTimingFunctionName.easeOut,
                                    CAMediaTimingFunctionName.easeInEaseOut,
                                    CAMediaTimingFunctionName.default]

let timingFunctions = defaultTimingFunctionOptions.map({ CAMediaTimingFunction(name: $0) })
for timingFunction in timingFunctions {
    print("\n" + timingFunction.description)

    for i in 0..<4 {
        timingFunction.getControlPoint(at: i, values: cords)
        print("(x:\(cords[0]) y:\(cords[1]))")
    }
}

Result:

linear
(x:0.0 y:0.0)
(x:0.0 y:0.0)
(x:1.0 y:1.0)
(x:1.0 y:1.0)

easeIn
(x:0.0 y:0.0)
(x:0.42 y:0.0)
(x:1.0 y:1.0)
(x:1.0 y:1.0)

easeOut
(x:0.0 y:0.0)
(x:0.0 y:0.0)
(x:0.58 y:1.0)
(x:1.0 y:1.0)

easeInEaseOut
(x:0.0 y:0.0)
(x:0.42 y:0.0)
(x:0.58 y:1.0)
(x:1.0 y:1.0)

default
(x:0.0 y:0.0)
(x:0.25 y:0.1)
(x:0.25 y:1.0)
(x:1.0 y:1.0)

Please notice that start point and end point are same for every function while control points define the behavior of the function.

Plotting the curve.

Code:

import UIKit
import PlaygroundSupport

public class TimingFunctionGraph: UIView  {

    private var x1: CGFloat
    private var y1: CGFloat
    private var x2: CGFloat
    private var y2: CGFloat

    public init(x1: CGFloat, y1: CGFloat, x2: CGFloat, y2: CGFloat) {
        self.x1 = x1
        self.x2 = x2
        self.y1 = y1
        self.y2 = y2
        super.init(frame: CGRect(x: 0, y: 0, width: 320, height: 320))
        backgroundColor = UIColor.white
    }

    public required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override public func draw(_ rect: CGRect) {
        let path = UIBezierPath()
        UIColor.blue.setStroke()
        path.lineWidth = 3.0
        path.move(to: CGPoint(x: 0.0, y: 0.0))
        path.addCurve(to: CGPoint(x: 320.0, y: 320.0),
                      controlPoint1: CGPoint(x: 320.0*x1, y: 320.0*y1),
                      controlPoint2: CGPoint(x: 320.0*x2, y: 320.0*y2))
        path.stroke()
    }
}

let firstLine = TimingFunctionGraph(x1: 0.0, y1: 0.0, x2: 0.58, y2: 1.0)
firstLine.transform = CGAffineTransform(scaleX: 1, y: -1)

PlaygroundPage.current.liveView = firstLine

The above code snippet creates a class TimingFunctionGraph which plots the function curve given the control points. Please notice that in let firstLine = TimingFunctionGraph(x1: 0.0, y1: 0.0, x2: 0.58, y2: 1.0) we have passed the control points for CAMediaTimingFunctionName.easeOut which we got above. You will see following curve plotted in playground live view.

timings

This is fun. But now we can try custom values for control points and see how this curve behaves. Actually we can create a lot of custom curves like this:

curves

Lets create an extension:

extension CAMediaTimingFunction {

    // default
    static let defaultTiming = CAMediaTimingFunction(name: CAMediaTimingFunctionName.default)
    static let linear = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
    static let easeIn = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
    static let easeOut = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
    static let easeInEaseOut = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)

    // custom
    static let easeInSine = CAMediaTimingFunction(controlPoints: 0.47, 0, 0.745, 0.715)
    static let easeOutSine = CAMediaTimingFunction(controlPoints: 0.39, 0.575, 0.565, 1)
    static let easeInOutSine = CAMediaTimingFunction(controlPoints: 0.445, 0.05, 0.55, 0.95)
    static let easeInQuad = CAMediaTimingFunction(controlPoints: 0.55, 0.085, 0.68, 0.53)
    static let easeOutQuad = CAMediaTimingFunction(controlPoints: 0.25, 0.46, 0.45, 0.94)
    static let easeInOutQuad = CAMediaTimingFunction(controlPoints: 0.455, 0.03, 0.515, 0.955)
    static let easeInCubic = CAMediaTimingFunction(controlPoints: 0.55, 0.055, 0.675, 0.19)
    static let easeOutCubic = CAMediaTimingFunction(controlPoints: 0.215, 0.61, 0.355, 1)
    static let easeInOutCubic = CAMediaTimingFunction(controlPoints: 0.645, 0.045, 0.355, 1)
    static let easeInQuart = CAMediaTimingFunction(controlPoints: 0.895, 0.03, 0.685, 0.22)
    static let easeOutQuart = CAMediaTimingFunction(controlPoints: 0.165, 0.84, 0.44, 1)
    static let easeInOutQuart = CAMediaTimingFunction(controlPoints: 0.77, 0, 0.175, 1)
    static let easeInQuint = CAMediaTimingFunction(controlPoints: 0.755, 0.05, 0.855, 0.06)
    static let easeOutQuint = CAMediaTimingFunction(controlPoints: 0.23, 1, 0.32, 1)
    static let easeInOutQuint = CAMediaTimingFunction(controlPoints: 0.86, 0, 0.07, 1)
    static let easeInExpo = CAMediaTimingFunction(controlPoints: 0.95, 0.05, 0.795, 0.035)
    static let easeOutExpo = CAMediaTimingFunction(controlPoints: 0.19, 1, 0.22, 1)
    static let easeInOutExpo = CAMediaTimingFunction(controlPoints: 1, 0, 0, 1)
    static let easeInCirc = CAMediaTimingFunction(controlPoints: 0.6, 0.04, 0.98, 0.335)
    static let easeOutCirc = CAMediaTimingFunction(controlPoints: 0.075, 0.82, 0.165, 1)
    static let easeInOutCirc = CAMediaTimingFunction(controlPoints: 0.785, 0.135, 0.15, 0.86)
    static let easeInBack = CAMediaTimingFunction(controlPoints: 0.6, -0.28, 0.735, 0.045)
    static let easeOutBack = CAMediaTimingFunction(controlPoints: 0.175, 0.885, 0.32, 1.275)
    static let easeInOutBack = CAMediaTimingFunction(controlPoints: 0.68, -0.55, 0.265, 1.55)
}

I hope this article helped you