Swift Mac OS Animation using Core Animation- Part 1

Created By: Debasis Das (27-May-2017)
Swift Mac OS Animation
Animation in Mac OSX has a long history and there are more than one option for achieving an animated behavior in a Mac Application.

We can achieve animation in a Mac Application using
1. Simple View Animation
2. Using Animation Proxy or
3. Using Core Animation on CALayer

The above animation techniques can be used in isolation or it can be merged with each other to achieve a certain animation behavior

The decision to use one approach vs the other purely depends on the animation complexity and the degree of control that is desired. Whether user interaction is desired on the screen that has animated layers etc.

If a simple animation such as animating the size of the NSWindow is required, it would be easier to simply use an animator proxy to achieve the functionality, however if we have complex requirement of creating a firework effect, we would need to look beyond View Animation or using an animator proxy.

In this post we will see how Core Animation works in Mac OSX.
CAAnimation is the abstract superclass for all Core Animations.
CAAnimation has the following subclasses
– CABasicAnimation
– CAKeyframeAnimation
– CAAnimationGroup
– CATransition

We can animate the contents of our applications by attaching animations (Stated above) with Core Animation Layers

CABasicAnimation

  • Provides basic single-keyframe animations to the CALayers properties.
  • While initializing a CABasicAnimation we state the keypath of the property that we want to animate.
  • The property can be the backgroundColor, it can be the layer opacity or border color etc.
  • The animation has a from and a to value that need to be stated.
  • for example, we can animate the color change of a CALayer from red to green by creating a CABasicAnimation with backgroundColor keypath and then state the fromValue as red and toValue as green

CAKeyframeAnimation

  • Is similar to CABasicAnimation with a difference that it can accept multiple intermediate values and multiple intermediate keyTimes that controls how the transition happens
  • The timing and pacing of keyframe animations are complex than the basic animations.
  • There is a property of CAkeyframeAnimation called as calculationMode which defines the algorithm of the animation timing.
  • Below are the calculation modes
    • kCAAnimationLinear – provides a linear calculation between keyframe value
    • kCAAnimationDiscrete – each keyframe value is used in turn and no interpolated values are calculated.
    • kCAAnimationPaced – Linear keyframe values are interpolated to produce an even pace throughout the animation.
    • kCAAnimationCubic – Smooth spline calculation between keyframe values
    • kCAAnimationCubicPaced – Cubic keyframe values are interpolated to produce an even pace throughout the animation.
  • The decision of the calculationMode plays a key role based on what type of animation we are trying to achieve. A bouncing ball effect would require the ball to fall at a slow speed initially and gradually the speed should increase and when it hits the ground it should bounce back with initial higher speed and the speed should taper at the top before it reverses direction.

CAAnimationGroup

  • Allows multiple animations to be grouped and run concurrently.
  • We can create multiple animations using CABasicAnimation or CAKeyframeAnimation each having a different animation duration and then we can create a CAAnimationGroup using an array of individual animations.
  • The CAAnimationGroup also has a duration property which if smaller than individual animation durations will clip the individual animation durations.

Using a combination of CABasicAnimation, CAKeyframeAnimation and CAAnimationGroup we can achieve amazing animation effects.

We will progress through to create the below animation effect
SwiftGroupedAnimation

In this sample application we will write 3 functions for displaying a simple CABasicAnimation, a simple CAKeyframeAnimation and finally an example of CAAnimationGroup

import Cocoa

class ViewController: NSViewController {
    let circleLayer = CALayer()
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.wantsLayer = true
        initializeCircleLayer()
        //simpleCAAnimationDemo()
        //keyFrameAnimationDemo()
        //groupedAnimationDemo()
    }

Below is the function to initialize the circle layer that we will be animating using different function calls
Swift-LinearAnimation

    func initializeCircleLayer(){
        circleLayer.bounds = CGRect(x: 0, y: 0, width: 50, height: 50)
        circleLayer.position = CGPoint(x: 50, y: 50)
        circleLayer.backgroundColor = NSColor.red.cgColor
        circleLayer.cornerRadius = 25.0
        self.view.layer?.addSublayer(circleLayer)
    }

In the below function we will change the x position of the circle layer using CABasicAnimation

    
    func simpleCAAnimationDemo(){
        circleLayer.removeAllAnimations()
        let animation = CABasicAnimation(keyPath: "position")
        let startingPoint = NSValue(point: NSPoint(x: 50, y: 50))
        let endingPoint = NSValue(point: NSPoint(x: 400, y: 50))
        animation.fromValue = startingPoint
        animation.toValue = endingPoint
        animation.repeatCount = Float.greatestFiniteMagnitude
        animation.duration = 3.0
        circleLayer.add(animation, forKey: "linearMovement")
    }

The below demo we will move the circle layer in key frames over a path, Then we will stroke the path to show the path the circle layer is followingSwift-KeyFrameAnimation

        
    func keyFrameAnimationDemo(){
        circleLayer.removeAllAnimations()
        let path = CGMutablePath()
        path.move(to: CGPoint(x: 280, y: 100))
        path.addCurve(to: CGPoint(x: 320, y: 100), control1: CGPoint(x: 100, y: 400), control2: CGPoint(x: 500, y: 400))
        path.addCurve(to: CGPoint(x: 280, y: 100), control1: CGPoint(x: 600, y: 500), control2: CGPoint(x:0, y: 500))
        
        
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = path
        shapeLayer.lineWidth = 3.0
        shapeLayer.fillColor = NSColor.clear.cgColor
        shapeLayer.strokeColor = NSColor.black.cgColor
        self.view.layer?.addSublayer(shapeLayer)
        
        let animation = CAKeyframeAnimation(keyPath: "position")
        animation.calculationMode = kCAAnimationLinear
        animation.path = path
        animation.repeatCount = Float.greatestFiniteMagnitude
        animation.autoreverses = true
        animation.duration = 3.0
        circleLayer.add(animation, forKey: "KeyFrameMovement")

    }

In the below demo we will group three animations in a CAAnimationGroup.
The first animation will move the circle over a path and the second animation will change the background color of the circle layer and the third will change the border width of the circle layer.
SwiftGroupedAnimation

       
    func groupedAnimationDemo(){
        circleLayer.removeAllAnimations()
        let path = CGMutablePath()
        path.move(to: CGPoint(x: 280, y: 100))
        path.addCurve(to: CGPoint(x: 320, y: 100), control1: CGPoint(x: 100, y: 400), control2: CGPoint(x: 500, y: 400))
        path.addCurve(to: CGPoint(x: 280, y: 100), control1: CGPoint(x: 600, y: 500), control2: CGPoint(x:0, y: 500))
        
        
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = path
        shapeLayer.lineWidth = 3.0
        shapeLayer.fillColor = NSColor.clear.cgColor
        shapeLayer.strokeColor = NSColor.black.cgColor
        self.view.layer?.addSublayer(shapeLayer)

        let widthAnimation = CAKeyframeAnimation(keyPath: "borderWidth")
        let widthValues = [2.0,4.0,6.0,8.0,6.0,4.0,2.0,4.0,6.0,8.0,6.0,4.0,2.0]
        widthAnimation.values = widthValues
        widthAnimation.calculationMode = kCAAnimationPaced
        
        let positionAnimation = CAKeyframeAnimation(keyPath: "position")
        positionAnimation.calculationMode = kCAAnimationLinear
        positionAnimation.path = path

        let colorAnimation = CAKeyframeAnimation(keyPath: "backgroundColor")
        let colors = [NSColor.blue.cgColor, NSColor.red.cgColor, NSColor.green.cgColor]
        colorAnimation.values = colors
        colorAnimation.calculationMode = kCAAnimationPaced
        
        
        let groupAnimation = CAAnimationGroup()
        groupAnimation.animations = [widthAnimation,positionAnimation,colorAnimation]
        groupAnimation.duration = 5.0
        groupAnimation.repeatCount = Float.greatestFiniteMagnitude
        groupAnimation.autoreverses = true
        circleLayer.add(groupAnimation, forKey: "multiAnimation")
    }
}

You can download the code from SwiftCoreAnimation-SampleCode
In Part 2 we will work on Animator Proxy

Posted in Swift, Swift 3.1 Tagged with: , , , , ,
0 comments on “Swift Mac OS Animation using Core Animation- Part 1
1 Pings/Trackbacks for "Swift Mac OS Animation using Core Animation- Part 1"
  1. […] continuation to the Swift Mac OS Animation Part 1 we will work with Animator Proxy to achieve animation in Mac OS […]

Leave a Reply

Your email address will not be published. Required fields are marked *

*