iOS — Animations

  • UIView.Animations (discouraged by Apple)
  • UIViewPropertyAnimator
  • UIKitDynamics
  • CAAnimation (Core Animation)

— UIView.Animations

UIView.Animations acts on the view level using type methods on the ‘UIView‘ itself.

class func animate(
withDuration duration: TimeInterval,
animations: @escaping () -> Void
)
UIView.animate(withDuration: 2) {
self.roundView.center.x += 200.0
}
class func animate(
withDuration duration: TimeInterval,
delay: TimeInterval,
options: UIView.AnimationOptions = [],
animations: @escaping () -> Void,
completion: ((Bool) -> Void)? = nil
)
  • withDuration: The duration of the animation.
  • delay: The amount of seconds UIKit will wait before it starts the animation.
  • options: Lets you customize a number of aspects about your animation. An empty array [] stands for ‘no options‘.
  • animations: The closure expression to provide your animations.
  • completion: A code closure to execute when the animation completes. This parameter often comes in handy when you want to perform some final cleanup tasks or chain animations one after the other.
UIView.animate(
withDuration: 2,
delay: 1,
options: [.repeat, .autoreverse], animations: {
self.roundView.center.x += self.view.bounds.width
},completion: nil)
  • .curveEaseIn: This option applies acceleration to the start of your animation.
  • .curveEaseOut: This option applies deceleration to the end of your animation.
  • .curveEaseInOut: This option applies acceleration to the start of your animation and applies deceleration to the end of your animation.
  • .curveLinear: This option applies no acceleration or deceleration to the animation.
  • bounds: Animate this property to reposition the view’s content within the view’s frame.
  • frame: Animate this property to move and/or scale the view.
  • center: Animate this property when you want to move the view to a new
    location on screen
  • backgroundColor: Change this property of a view to have UIKit gradually change the background color over time.
  • alpha: Change this property to create fade-in and fade-out effects.
  • transform: Modify this property within an animation block to animate the rotation, scale, and/or position of a view
class func animate(
withDuration duration: TimeInterval,
delay: TimeInterval,
usingSpringWithDamping dampingRatio: CGFloat,
initialSpringVelocity velocity: CGFloat,
options: UIView.AnimationOptions = [],
animations: @escaping () -> Void,
completion: ((Bool) -> Void)? = nil
)
UIView.animate(
withDuration: 2,
delay: 0,
usingSpringWithDamping: 0.5,
initialSpringVelocity: 0.0,
options: [], animations: {
self.roundView.center.x += (self.view.bounds.width/2 —
self.roundView.frame.width/2)
})
extension UIView {
func animate(
withDuration duration: Double = 2,
delay: Double = 0,
damping: CGFloat = 0.5,
velocity: CGFloat = 0.0,
options: UIView.AnimationOptions = []
) {
UIView.animate(
withDuration: duration,
delay: delay,
usingSpringWithDamping: damping,
initialSpringVelocity: velocity,
options: options
animations: {
self.center.x += 100 },
completion: nil
)
}
}
roundView.animate()
class func animateKeyframes(
withDuration duration: TimeInterval,
delay: TimeInterval,
options: UIView.KeyframeAnimationOptions = [],
animations: @escaping () -> Void,
completion: ((Bool) -> Void)? = nil
)
class func addKeyframe(
withRelativeStartTime frameStartTime: Double,
relativeDuration frameDuration: Double,
animations: @escaping () -> Void
)
UIView.animateKeyframes(
withDuration: 2.0,
delay: 0.0,
options: [],
animations: {
UIView.addKeyframe(
withRelativeStartTime: 0,
relativeDuration: 0.5
) {
self.roundView.center.x += (self.view.bounds.width/2 —
self.roundView.frame.width/2)
}
UIView.addKeyframe(
withRelativeStartTime: 0.5,
relativeDuration: 0.5
) {
self.roundView.center.y -= (self.view.bounds.height/2
self.roundView.frame.height/2)
}
}, completion: nil)
UIView.addKeyframe(
withRelativeStartTime: 0,
relativeDuration: 1
) {
self.roundView.backgroundColor = .yellow
}

— UIViewPropertyAnimator

convenience init(
duration: TimeInterval,
curve: UIView.AnimationCurve,
animations: (() -> Void)? = nil
)
convenience init(
duration: TimeInterval,
dampingRatio ratio: CGFloat,
animations: (() -> Void)? = nil
)
let animator: UIViewPropertyAnimatoranimator = UIViewPropertyAnimator(
duration: 2.0,
curve: .easeInOut,
animations: nil
)
animator.addAnimations {
self.roundView.center.x += (self.view.bounds.width/2
self.roundView.frame.width/2)
}
animator.addAnimations {
self.roundView.backgroundColor = .yellow
}
animator.addCompletion { position in
guard position == .end else {
return
}
print(“animation completed”)
}
animator.startAnimation()
animator.pauseAnimation()
animator.continueAnimation(
withTimingParameters: nil,
durationFactor: 2.0
)
UIViewPropertyAnimator.runningPropertyAnimator(
withDuration: 2.0,
delay: 0.0,
options: [],
animations: {
self.roundView.center.x += (self.view.bounds.width/2
self.roundView.frame.width/2)
}, completion: nil
)

— Core animation

A UIView is backed with a CALayer.

override class var layerClass: AnyClass {
return CALayer.self
}
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
  • CALayer
  • CAGradientLayer
  • CAShapeLayer
  • CAGradientLayer
  • CAReplicatorLayer
  • CAEmitterLayer
  • CABasicAnimation
  • CAKeyframeAnimation
  • CASpringAnimation
  • CAAnimationGroup
let translationX = CABasicAnimation(keyPath: "position.x")
translationX.fromValue = roundView.layer.position.x
translationX.toValue = roundView.center.x += (view.bounds.width/2 — roundView.frame.width/2)
translationX.duration = 0.5
roundView.layer.add(translationX, forKey: nil)
roundView.layer.presentation()
let roundedCorners = CABasicAnimation(
keyPath: #keyPath(CALayer.cornerRadius)
)
roundedCorners.fromValue = 0.0
roundedCorners.toValue = 10.0
roundedCorners.duration = 1
roundedCorners.fillMode = .forwards
roundedCorners.isRemovedOnCompletion = false
// We want the corner radius effect to remain on screen after the animation completes.roundView.layer.add(roundedCorners, forKey: nil)
let position = CABasicAnimation(
keyPath: #keyPath(CALayer.position)
)
position.duration = 1.0position.fromValue = NSValue(cgPoint: CGPoint(x: self.roundView.layer.position.x, y: self.roundView.layer.position.y))position.toValue = NSValue(cgPoint: CGPoint(x: self.roundView.layer.position.x + 100.0, y: self.roundView.layer.position.y — 100.0))self.roundView.layer.add(position, forKey: nil)
let translateX = CAKeyframeAnimation()translateX.keyPath = "position.x"
translateX.values = [
0,
(self.view.bounds.width/2 — self.roundView.frame.width/2),
0
]
translateX.keyTimes = [0, 0.5, 1]
translateX.duration = 2
translateX.isAdditive = true
roundView.layer.add(translateX, forKey: nil)
  • colors: Animate the gradient’s colors to give it a tint.
  • locations: Animate the color milestone locations to make the colors movearound inside the gradient.
  • startPoint and endPoint: Animate the extents of the layout of the gradient.
  • path: Morph the layer’s shape into a different shape.
  • fillColor: Change the fill tint of shape to a different color.
  • lineDashPhase: Create a marquee or “marching ants” effect around your shape.
  • lineWidth: Grow or shrink the size of the stroke line of your shape.
  • instanceDelay: Animate the amount of delay between instances
  • instanceTransform: Change the transform between replications on the fly
  • instanceRedOffset, instanceGreenOffset, instanceBlueOffset: Apply a delta to apply to each instance color component
  • instanceAlphaOffset: Change the opacity delta applied to each instance

— UIKit Dynamics

UIKit Dynamics enables us to « apply physics-based animations to our views. » which means UI components will behave just like if they were in the real world.

var animator: UIDynamicAnimator!
var dynamicBehavior: UIDynamicBehavior!
var snapBehavior: UISnapBehavior!
override func viewDidLoad() {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.animate()
}
}
private func animate() {
animator = UIDynamicAnimator(referenceView: view)
dynamicBehavior = UIDynamicBehavior() snapBehavior = UISnapBehavior(
item: roundView,
snapTo: .init(
x: roundView.center.x + 100,
y: roundView.center.y + 100
)
)
dynamicBehavior.addChildBehavior(snapBehavior) animator.addBehavior(dynamicBehavior)
}
animator = UIDynamicAnimator(referenceView: view)
dynamicBehavior = UIDynamicBehavior()
dynamicBehavior.addChildBehavior(snapBehavior)
snapBehavior = UISnapBehavior(
item: roundView,
snapTo: .init(
x: roundView.center.x + 100,
y: roundView.center.y + 100
)
)
animator.addBehavior(dynamicBehavior)
var gravityBehavior: UIGravityBehavior!gravityBehavior = UIGravityBehavior(items: [roundView])dynamicBehavior.addChildBehavior(gravityBehavior)
var collisionBehavior: UICollisionBehavior!collisionBehavior = UICollisionBehavior(items: [roundView])
collisionBehavior.translatesReferenceBoundsIntoBoundary = true
dynamicBehavior.addChildBehavior(collisionBehavior)
animator.addBehavior(dynamicBehavior)

Conclusion

UIKit provides different level of abstractions to implement animations in an app such as the ‘animate‘ type methods or a ‘property animator‘, recommended by Apple, and based upon the Core Animation framework.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store