Animation Guide

Advanced cross platform animation with Apple Swift

Works with BETA5 and higher

Animation effects, sparsely used, greatly enhance the attractiveness of an app. With BETA5, we reworked the Animation API. This guide gives an overview over the animation API and the inner workings.

We animate all vector elements using a powerful animation model drawing on CoreAnimations, Web Animations, SMIL and CSS Animations. We are pretty confident to enable any kind - even complicated animations - using the API.

Some other good reads on mobile application & animation

👍

Source code for example

Please find the sample project here. Edit the start.swift to run different examples.
Animation demo

Basics

Let's quickly recap how to access a vector element. The page consists a custom control that contains a rectangle with the id rect1:

To get a refences to any vector element:

  • make sure vector element has an id (here rect1)
  • use the drawing attribute and find method to get a reference
  • create a basic animation, here the rotate animation

Use an id tag to identify the vector element

BoundingBox

A rectangular bounding box which is used to describe the bounds of a vector element independant of its shape. You retrieve the BoundingBox using the getBoundingBox() method available on any vector element.

Get vector element absolute location

The absolute location of the vector element is computed by the absolute location of the bounding box. Here the code to get the absolute XY coordinates of the vector element on the mobile device in pixels.

func getAbsoluteCoordinates(of vectorElement:SCDSvgShape) -> (Float,Float) {
 let location = vectorElement.getBoundingBox().location
 return (Float(location.x),Float(location.y))
}

Rotate vector element

A few lines of code to animate the rectangle:

import ScadeKit

class BasicPageAdapter: SCDLatticePageAdapter {

  var rect : SCDSvgRect? 

  // page adapter initialization
  override func load(_ path: String) {		
    super.load(path)
  }

  override func show(_ view: SCDLatticeView?) {
    super.show(view) 

    // all animmation setup has to happen in show method
    self.rect = self.page!.drawing!.find(byId: "rect1") as? SCDSvgRect

    // create an animation
    let rotateAnimation = SCDSvgRotateAnimation()
    rotateAnimation.angle = 360
    rotateAnimation.duration = 2

    // add animation to element
    self.rect!.animations.append(rotateAnimation)
   }
}

Pretty straight forward

Center of rotation

You can set the center of the rotate animation using the anchor attributes. By default, the center of rotation is defined in relative values starting from the top left to the bottom right. You can set it to absolute values by setting rotateAnimation.isAbsolute = true. For now, let keep it the default (false) and the anchor attribute to a value from 0 .. 1.

The following code sets the anchor point to the middle of the object.

// Use of relative anchor coordinates
rotateAnimation.anchorX = 0.5
rotateAnimation.anchorY = 0.5
rotateAnimation.rotateAnimation = false // false is the default

Moving objects

The translate animation moves an object by dx and dy pixels. The movement is relative to the current location, i.e. it moves by +/- pixels in dx direction, and in +/- pixels in vertical direction. So if the object is located at (100,100), and you use (dx,dy) = (10,20), your new location is (110,120).

Here the swift code to create an animation that moves vector object by dx and dy:

func getMoveByAnimation(dx:Float, dy:Float) -> SCDSvgTranslateAnimation {
  // create translate animation
  let moveByAnimation = SCDSvgTranslateAnimation()
  moveByAnimation.dx = dx
  moveByAnimation.dy = dx
  moveByAnimation.duration = 2.5 // 2.5 seconds
  return moveByAnimation
 }

Compute coordinate for middle lower third of device's screen

Now lets say you want to move to certain coordinate, lets say we want to move to the middle of the lower third of the device, lets further assume we have a resolution of 400 x 690. The coordinate to move to would be (200, 460 + 115) = (200, 575).
Here the code to

  • get the width and height of the screen
  • compute the destination coordinate
func getXYCoordinateCenterLowerThird() -> (Float,Float) {
  // get width and height of screen
  let screensize = SCDRuntime.getSystem().getScreenSize()
  let (w,h) = (Float(screensize.width), Float(screensize.height))
  return (w/2.0, h*(2/3) + (h*(1/3) / 2) )
}

Move to computed coordinate

The code for moving to the coordinate is pretty easy:

let (tx,ty) = getXYCoordinateCenterLowerThird()
let (dx,dy) = ( tx - self.rect!.x.value, ty - self.rect!.y.value)
let moveAnimation = getMoveByAnimation(dx:dx,dy:dy)
self.rect!.animations.append(moveAnimation)

Moving along a path

Most certainly, we can also move along a path using the SCDSvgMotionAnimation. The object will be moved along a path, where the path comprises of the a number of straight or curved line. Usually, you wouldnt create the path yourself, but create the path through a tool such as Adobe AfterEffects. For demonstration purposes, let us move the object along the sides of a rectangle.

  1. Step 1 - 2 are straightforward. We compute the corners of the rectangle. We use the current device height and width for this

  2. We compute the absolute location of the vector element.

  3. Each motion animation needs a path SCDSvgPath along which the vector object is moved. Each path starts with a SCDSvgPathMove element, followed by a number of SCDSvgPathLine elements:

  • We add SCDSvgPathMove using the vector elements current location
  • We add a couple of lines
func getMoveAlongRectangle() -> SCDSvgMotionAnimation  {
  // move vector object along lines of a rectangle
  let screenSize = SCDRuntime.getSystem().getScreenSize()

  // compute points of rectangle to move along by
  let (x0,y0) = (Float(screenSize.width) * 0.2, Float(screenSize.height) * 0.2)
  let (x1,y1) = (Float(screenSize.width) - x0 - self.rect!.width.value, Float(screenSize.height) - y0 - self.rect!.height.value)

  // get absolute location of vector element
  let bbLocation = self.rect!.getBoundingBox().location
  let (currentX, currentY) = (Float(bbLocation.x),Float(bbLocation.y))

  // create path and set start position
  let path = SCDSvgPath()
  path.elements.append(SCDSvgPathMove(x: currentX, y: currentY))

  // set lines to move along by
  [(x0,y1), (x0,y0), (x1,y0), (x1,y1), (currentX, currentY)].forEach{ path.elements.append(SCDSvgPathLine(x:$0.0, y:$0.1))}

  // set to absolute movement (will be default to true in next build)
  path.elements.forEach{ $0.isAbsolute = true }	

  // create motion path animation. the x,y points need to be translated to 
  let motionAnim = SCDSvgMotionAnimation()
  motionAnim.path = path
  return motionAnim
}

Using this animation, the rectangle moves around the screen easily.

PropertyAnimation - animate anything

The property animation is the most powerful, as any attribute of the vector element can be animated. Lets animate the stroke-width attribute. When you specify the key, i.e. the property to modify, use camelCase syntax (start with lowerCase, use upperCase for next part)

func getStrokeWidthAnimation() -> SCDSvgPropertyAnimation {
  // the vector element attribute name needs to be specified in lowerCamel
  // so stroke-width in the SVG source becomes strokeWidth
  // we will look into changing this in the future, this inconsistency doesnt make sense
  let strokeWidthAnimation = SCDSvgPropertyAnimation("strokeWidth",from:5,to:10)
  strokeWidthAnimation.duration = 2
  return strokeWidthAnimation
}

Example for properties and ids

Vector attributeKey
fill-opacityfillOpacity
widthwidth
strokestroke
stroke-widthstrokeWidth

SCDSvgPropertyAnimation constructor

Constructor xDescription
SCDSvgPropertyAnimation(_ key:String,from:Any,to:Any>)Move the property from to . We interpolate linearly with the frame rate over the duration of the animation. Use this as default for most smooth transition
SCDSvgPropertyAnimation(_ key:String,values:[Float])Move property identified by key through the list of values specified in values within the specified duration.

Animate color transition

As you can see, the constructor and the values for SCDSvgRGBColor are a little off standard. We address this shortly, other than that, code is pretty straight forward.

func getFillColorAnimation() -> SCDSvgPropertyAnimation {
  let fromColor = SCDSvgRGBColor(r:1,g:0,b:0)
  let toColor = SCDSvgRGBColor(r:1,g:1,b:0)
  let fillColorAnimation =  SCDSvgPropertyAnimation("fill",from:fromColor,to:toColor)
  fillColorAnimation.duration = 3
  fillColorAnimation.delay = 3
  return fillColorAnimation
}

Grouping animations

The combination and parallel execution of multiple animations effects is done using the SCDSvgGroupAnimation. You add multiple animations in the animation attribute

func getGroupAnimationStrokeAndFill() -> SCDSvgGroupAnimation {
 let ga = SCDSvgGroupAnimation() 
 ga.animations = [getRotateAnimation(),getFillColorAnimation()]
 ga.delay = 2
 return ga
}

Value & Time function

The animation api uses two concepts for providing detailed control about the animation called value function and time function:

  • the value function v(x) --> Any receives an input between 0..1 as an input, and returns any value that is compatible with the property specified. As you as the developer write the value function, the behavior itself is totally up to you.

  • the time function f(t) -> [0..1] receives a value 0..1 as an input from the animation system, and returns a value [0..1] as an output. Again you develop the function, and therefore control its behaviour, but you can also use predefined timing function for special effects. These functions are called easing functions and allow your for nice looking effects. Please see here for learning more about the easing functions Easing functions

Vitaly likes to explain it likes this. Image you have an old fashioned movie on a film roll. The value function specifies how each picture on the roll looks like. The time function determines how fast you you reach the picture. The time function can speed up, slow down, fast forward or even go backward.

Value function example

This value function will return color RED for the first 25% of animation duration, the color ORANGE for the following 50% of the animation duration and for the remaining 25%, the color BLUE.

func getPropertyWithValueFunctionAnimation() -> SCDSvgPropertyAnimation {
 let v = {(x:Float) -> SCDSvgRGBColor in
   switch x {
     case 0..<0.25 : return SCDSvgRGBColor(r:1,g:0,b:0) // RED
     case 0.25..<0.75 : return SCDSvgRGBColor(r:1,g:0.5,b:0) // ORANGE
     case 0.75...1 : return SCDSvgRGBColor(r:0,g:0,b:0.5) // BLUE
     default: return SCDSvgRGBColor(r:1,g:1,b:1)
   }
 }
 let ani =  SCDSvgPropertyAnimation("fill", valueFunction : v) 
 ani.duration = 3
 return ani
}

OnComplete handler

As animation functions are invoked and execute asynchronously, the SCDSvgOnCompleteHandler allows you to call a function whenever the animation is finished

func getOnCompleteAnimation() -> SCDSvgGroupAnimation {
 let animation = getGroupAnimationStrokeAndFill() 
 animation.onComplete = SCDSvgOnCompleteHandler{ _ in print("Done with Animation") }
 return animation
}

Examples