Skip to content

RolfHendriks/RHAnimator

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

RHAnimator

RHAnimator screenshot

RHAnimator screenshot

Overview

This project consists of three parts:

  1. RHAnimator, a simple low level utility for implementing any custom animation
  2. RHAnimationCurves, a companion utility that defines a variety of custom animation curves not found in UIKit
  3. A detailed demo project to showcase RHAnimator+RHAnimationCurves and to serve as a sample project for professional grade iOS app development in Swift.

RHAnimator Basics

RHAnimator is an extremely simple and flexible low level animation API that calls an update method once every animation frame. For example:

RHAnimator.animate( duration:0.5 animations:
{   
  (progress : Double) in // progress begins at 0 and ends at 1  

  // example - simple fade in:
  view.alpha = progress

  // example - play a frame by frame custom animation defined by image assets:
  let frameNumber : Int = Int( progress * Double(self.animationFrames.count - 1) ) 
  imageView.image = self.animationFrames[frameNumber]

  // example - blink 10 times, slowly at first then quickly, ending at visible:
  let acceleratedTime : Double = progress * progress
  let onOffCount : Int = Int ( acceleratedTime * 19 )
  blinker.hidden = (onOffCount % 2 == 0)

  // etc, etc
}

Although RHAnimator could replace UIKit animations in a project, this is not the intent. Instead, RHAnimator is intended for game-like rich custom animations outside of the scope of UIKit, such as animating unanimatable properties or using nonstandard animation curves. You are free to do anything you want inside the update method, so the uses of RHAnimator are limited only by your imagination.

Custom Animation Curves

One of the main use cases for RHAnimator is to define custom animation curves. For example, to eject a view from the screen using an unusually strong acceleration, you could do this:

let strongAcceleration : (Double)->Double = { x in return x*x*x }
RHAnimator.animate( duration:1 curve:strongAcceleration animations{ 
  progress in 
  view.transform = CGAffineTransform (translationX: CGFloat(progress * 100), y:0 )
})

Notice that in RHAnimator, easing curves are just functions. This gives you full freedom over defining your own curves, often in a single line of code. This is different from UIKit, where your options are to either select one of four presets or define keyframes.

If custom animation curves are your reason for using RHAnimator, RHAnimationCurves provides a wide variety of useful premade curves, which the demo app shows off in detail. Because this is a feature you may or may not need, RHAnimationCurves is a separate component from RHAnimator so that RHAnimator keeps its minimalist size.

Exponential Deceleration / Slowdown

RHAnimator screenshot

An exponential deceleration curve matches the physics of a real object slowing to a stop, and so is an excellent choice any time you want an object to come to rest from a moving state. iOS uses exponential deceleration curves when scroll views slow down, but unfortunately does not expose them in any API. So if you want to use exponential deceleration, RHAnimator combined with RHAnimationCurves.decelerate is one possible solution.

Configurable Easing

RHAnimator screenshot

iOS defines curves for easing in, out, or both, but iOS's curves are subtle and not configurable. RHAnimationCurves defines paramterized easing functions that allow the amount of easing to be configured to create more pronounced curves.

Configurable Overshoot

RHAnimator screenshot

RHAnimationCurves.overshoot defines a parameterized overshoot curve that uses harmonic oscillation physics and behaves like UIKit's spring animation API. RHAnimationCurves.overshoot is easier to tweak though, allowing you to define an exact number of overshoots. And since RHAnimator can animate anything, the overshoot curve allows you to bring spring animations to unanimatable properties.

UIKit Curve Replicas

RHAnimator screenshot

RHAnimationCurves defines exact replicas of UIKit's four animation curves - easeInOut, easeIn, easeOut, linear. These are useful if you want to apply a standard easing function to animate a custom property.

Technical Details

Landscape Layout using UIStackView

RHAnimator screenshot

The screen layout uses a two element UIStackView to lay out a 50/50 split between the bottom half of the screen, displaying a function graph, and the top half of the screen, displaying everything else. Because stack views have a property to define their layout direction, we can lay out a horizontal 50/50 split for landscape orientations by just changing a single property on a single view:

private func setWideLayout(_ wide: Bool){
    self.rootStackView.axis = wide ? .horizontal : .vertical
}

Accessibility

Since the animation demo is also a code sample, and since accessibility is an area of interest of mine, the demo includes many details that enhance accessibility. Notice that:

  • Animation curve and animation duration sections behave like one large control for voice over users
  • Voice over focus automatically switches to the curve picker as it slides in, and automatically switches back to the curve control as the picker slides out
  • While the animation curve picker is showing, the only two components available to a voice over user are the curve picker and a dismiss 'button'
  • Performing an accessibility escape gesture (two finger N shape) dismisses the animation curve picker
  • Animated boxes use custom accessibility frames so that they stay inside their voice over frame while animating
  • Animation duration has separate user visible vs voice over strings so that users hear 'two seconds', not 'two point zero sec'
  • Demo supports dynamic text sizes

Using CADisplayLink

RHAnimator uses CADisplayLink internally to manage its timing for maximum performance. CADisplayLink syncronizes with iOS's screen refreshing logic and guarantees that our animation logic gets called exactly once per screen update. This means that if iOS changes their refresh rate, as they have recently done with iOS11, RHAnimator will still work at full speed. And if the screen refresh rate is reduced to conserve battery life or because the system is overloaded, RHAnimator will slow down its updates automatically.

Robust interpolations using the Interpolatable protocol

One of the most common tasks for custom animations is interpolating values: given the state at the start of the animation, the desired end state, and the currently elapsed time, what should the new state be?

A typical C approach might look like this:

#define interpolate( from,to,at ) ((from)*(1-at) + (to)*(at))

It's a concise solution, but also unsafe, inflexible, and not Swift compatible. Instead, RHAnimator formalizes the idea of interpolation into a minimalist Swift protocol:

protocol Interpolatable{
  static func + (lhs: Self, rhs:Self) -> Self
  static func * (lhs: Self, rhs:Double) -> Self
}
static func interpolate<T : Interpolatable> (from: T, to:T, at:Double) -> T {
  return from * (1-at) + to * at
}

RHAnimator then defines Interpolatable implementations for numbers, points, and sizes. More important though, the Interpolatable protocol allows you to animate your own custom data structures - as long as they implement scaling and addition.

Animation Curve Composition

RHAnimationCurves defines not just curves, but methods for generating curves more easily. For example, look at the implementation of a parameterized easeInOut curve:

static func ease ( strength: Double ) -> (Double)->Double {
    let accelerate : (Double)->Double = self.accelerate(strength: strength)
    let decelerate : (Double)->Double = self.opposite(function: accelerate)
    return self.compose( accelerate, decelerate )
}

About

Mimimalist Swift animation utility with fully customizable animation curves

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages