Skip to content

Commit

Permalink
'New files added: 8, Files modified: 4, Files deleted: 12'
Browse files Browse the repository at this point in the history
'Added 8 files:
animator/
easing/Easing.swift
events/
movers/beta/
movers/rubberband/
movers/utils/DEPRECATEDFrameTicker.swift
physics/
proxy/

Deleted 12 files:
AnimEvent.swift
Animatable.swift
Animation.swift
Animator.swift
BaseAnimation.swift
Easing.swift
LoopingAnimator.swift
movers/SnapFriction.swift
movers/physics/Easer.swift
movers/physics/PhysicsAnimationKind.swift
movers/physics/Springer.swift
movers/utils/FrameTicker.swift

Modified 4 files:
README.md
movers/Friction.swift
movers/Mover.swift
movers/RubberBand.swift
'
  • Loading branch information
eonist committed Jul 23, 2017
1 parent 1cb6283 commit a50be8f
Show file tree
Hide file tree
Showing 27 changed files with 390 additions and 142 deletions.
10 changes: 0 additions & 10 deletions Animatable.swift

This file was deleted.

44 changes: 0 additions & 44 deletions Animator.swift

This file was deleted.

32 changes: 0 additions & 32 deletions BaseAnimation.swift

This file was deleted.

14 changes: 14 additions & 0 deletions README.md
Expand Up @@ -98,3 +98,17 @@ In iOS 10 apple gave us "UIViewPropertyAnimator" Which was a big level up in the
#### Final remarks:

There is also NumberSpringer and NumberEaser which can be used to manipulate CGFloat. Which enables you to animate color transition. Rotation, shadow, gradient, 3d perspectives or any other variable. unlike apples built in animation system this Animation lib enables you to animate any property you desire. You can also Extend the Easer or Springer class with your own Custom class so that it can have more custom logic. Say you want to do something with Point3D and need to account for the z value as well. The possibilities are endless. AnimLib also does more stock like animations similar to Apples Animation classes. I will attempt to do some examples and write another article about these features at a later date. Until then any feedback is always welcomed. Thanks for reading.


#### More examples:

**Interpolation:**

<img width="100" alt="img" src="https://raw.githubusercontent.com/stylekit/img/master/color_interpolation_take_three.gif">

The above animation can be achieved by including the simple line bellow in the progress method

```swift
let color = NSColor.green.interpolate(.blue, 0.5)
```

39 changes: 39 additions & 0 deletions animator/Animator.swift
@@ -0,0 +1,39 @@
import Cocoa
/**
* This class animates something from A to B with a easing curve attached
* NOTE: This animation class is more like stock animation, less interuptable than "physics based animation"
* TODO: ⚠️️ Take a look at other animation libs 👈
* TODO: ⚠️️ Add onComplete selector callback method on init and as a variable, do the same with method, use optional to assert if they exist or not
* TODO: ⚠️️ Seek,reverse,repeate,autoRepeat
*/
class Animator:FrameAnimator {
var frameTick:FrameTick/*The closure method that is called on every "frame-tick" and that changes the property, you can use a var closure or a regular method, probably even an inline closure*/
var currentFrameCount:CGFloat = 0/*curFrameCount, this is need in order to know when the animation is complete*/
var easing:EasingEquation/*Variable for holding the easing method*/
var initValues:InitValues/*Stores the intial config values for the animation, duration,fromValue, toValue*/
init(onFrame:@escaping FrameTick = {_ in}, initValues:InitValues = Animator.initValues, easing:@escaping EasingEquation = Easing.linear.ease){
self.frameTick = onFrame
self.initValues = initValues
self.easing = easing
super.init(AnimProxy.shared)
}
/**
* Fires on every frame tick
*/
override func onFrame(){
let val:CGFloat = easing(currentFrameCount, from, to-from, framesToEnd)
frameTick(val)/*Call the callBack onFrame method*/
if(currentFrameCount == framesToEnd){
stop()/*Stop the animation*/
super.onEvent(AnimEvent(AnimEvent.completed,self))/*Notify listeners that the animation completed*/
}
self.currentFrameCount += 1
}
/*DEPRECATED*/
init(_ animatable:AnimProxyKind, _ duration:CGFloat = 0.5, _ from:CGFloat, _ to:CGFloat, _ callBack:@escaping FrameTick, _ easing:@escaping EasingEquation = Linear.ease){
initValues = (duration:duration,from:from,to:to)
self.frameTick = callBack
self.easing = easing
super.init(animatable)
}
}
65 changes: 65 additions & 0 deletions animator/Animator2.swift
@@ -0,0 +1,65 @@
import Foundation

/**
* NOTE: remember this needs to support many different animators and also simultan animations, so it cant be too intertwined
* TODO: make FrameAnimator2 that does not extend EventSender
* TODO: LoopAnimator2
* TODO: if you need to stop the entire anim chain you need to store each successive anim in an array and stop the one that is running, you can create utilitity methods that does this for you
* TODO: Later you can maybe create a class that is called AnimSeq, which can sequence anim from a json file, akin to your legacy project
* TODO: API like: spring(view, delay: 0.5, spring: 800, friction: 10, mass: 10) {}
* TODO: API like: animate(view, duration: 1, curve: .bezier(1, 0.4, 1, 0.5)) {$0.x = finalValue}
*/
class Animator2:FrameAnimator2 {
var frameTick:FrameTick
var currentFrameCount:CGFloat = 0/*curFrameCount, this is needed in order to know when the animation is complete*/
var initValues:InitValues
var easing:EasingEquation/*Variable for holding the easing method*/
typealias Completed = () -> Void
//
var completed:Completed = {}
//(CGFloat) -> Animator2 /*Makes the return type less verbose*/
init(initValues:Animator2.InitValues = Animator2.initValues, easing:@escaping EasingEquation = Easing.linear.ease, closure: @escaping FrameTick = {_ in}) {
self.initValues = initValues
self.frameTick = closure
self.easing = easing
//return TestingClass()
super.init(AnimProxy2.shared)
}
/**
* Fires on every frame tick
*/
override func onFrame(){
let val:CGFloat = easing(currentFrameCount, from, to-from, framesToEnd)
frameTick(val)/*Call the callBack onFrame method*/
if(currentFrameCount == framesToEnd){
stop()/*Stop the animation*/
//_ = completed(Animator.initValues, {_ in})//the animation completed, call the completed closure
completed()
}
self.currentFrameCount += 1
}
/**
* NOTE: we need onComplete in addition to complete because complete can't return self, so chaining won't work
*/
func onComplete(closure: @escaping Completed) -> Self{
completed = closure//assign the closure
return self
}
}

extension Animator2 {
typealias InitValues = (dur:CGFloat,from:CGFloat,to:CGFloat)/*Signature for initValues*/
static var initValues:InitValues = (dur:0.5,from:0,to:1)/*Default init values*/
static var fps:CGFloat = 60//<--TODO: ⚠️️ this should be derived from a device variable
var duration:CGFloat {get{return initValues.dur}set{initValues.dur = newValue}}/*In seconds*/
var from:CGFloat {get{return initValues.from}set{initValues.from = newValue}}/*From this value*/
var to:CGFloat {get{return initValues.to}set{initValues.to = newValue}}/*To this value*/
var framesToEnd:CGFloat {return Animator.fps * duration}/*totFrameCount*/
}
//extension Animator2{
// struct InitValues2{
// var duration:CGFloat
// var from:CGFloat
// var to:CGFloat
// }
//}
12 changes: 12 additions & 0 deletions animator/AnimatorExtensions.swift
@@ -0,0 +1,12 @@
import Foundation

typealias FrameTick = (CGFloat)->Void/*the callBack signature for onFrame ticks*/
extension Animator {
typealias InitValues = (duration:CGFloat,from:CGFloat,to:CGFloat)/*Signature for initValues*/
static var initValues:InitValues = (duration:0.5,from:0,to:1)/*Default init values*/
static var fps:CGFloat = 60//<--TODO: ⚠️️ this should be derived from a device variable
var duration:CGFloat {get{return initValues.duration}set{initValues.duration = newValue}}/*In seconds*/
var from:CGFloat {get{return initValues.from}set{initValues.from = newValue}}/*From this value*/
var to:CGFloat {get{return initValues.to}set{initValues.to = newValue}}/*To this value*/
var framesToEnd:CGFloat {return Animator.fps * duration}/*totFrameCount*/
}
40 changes: 40 additions & 0 deletions animator/FrameAnimator.swift
@@ -0,0 +1,40 @@
import Cocoa
/**
* FrameAnimator serves as the Core Animator in this Animation library
* TODO: ⚠️️ Consider not using EventSender in the animation lib and instead setup callbacks so that it can work standalone, also callbacks works better when setting up chaining
* NOTE: We use EventSender for in-frequent events such as onComplete or onStop and we use a regular callback method as its very frequent
*/
class FrameAnimator:EventSender {//rename to FrameAnimator
var animProxy:AnimProxyKind/*Reference to where the displayLink resides*/
init(_ animatable:AnimProxyKind = AnimProxy.shared){
self.animProxy = animatable
}
/**
* This is called from the AnimProxy.onFrameOnMainThread method
*/
func onFrame(){
fatalError("Must be overwritten in subclass")
}
/**
* Start the animation
*/
func start(){
animProxy.animators.append(self)/*Add your self to the list of animators that gets the onFrame call*/
if(!CVDisplayLinkIsRunning(animProxy.displayLink)){CVDisplayLinkStart(animProxy.displayLink)}/*start the displayLink if it isn't already running*/
}
/**
* Stop the animation
*/
func stop(){
animProxy.animators.removeAt(animProxy.animators.indexOf(self))/*If none exist -1 is returned and none is removed*/
if(animProxy.animators.isEmpty && CVDisplayLinkIsRunning(animProxy.displayLink)){CVDisplayLinkStop(animProxy.displayLink)}/*stops the frame ticker if there is no active running animators*/
super.onEvent(AnimEvent(AnimEvent.stopped,self))/*Notify listners the animation has stopped*/
}
}
extension FrameAnimator {
/**
* Assert if an animator is active or not, you can also check if the Animator is nil to check if is active or not
* TODO: ⚠️️ Name this hasStopped or isActive
*/
var stopped:Bool {return animProxy.animators.indexOf(self) == -1}
}
41 changes: 41 additions & 0 deletions animator/FrameAnimator2.swift
@@ -0,0 +1,41 @@
import Cocoa

/**
* FrameAnimator serves as the Core Animator in this Animation library
* TODO: ⚠️️ Consider not using EventSender in the animation lib and instead setup callbacks so that it can work standalone, also callbacks works better when setting up chaining
* NOTE: We use EventSender for in-frequent events such as onComplete or onStop and we use a regular callback method as its very frequent
*/
class FrameAnimator2 {/*Rename to FrameAnimator*/
var animProxy:AnimProxyKind2/*Reference to where the displayLink resides*/
init(_ animatable:AnimProxyKind2 = AnimProxy2.shared){
self.animProxy = animatable
}
/**
* This is called from the AnimProxy.onFrameOnMainThread method
*/
func onFrame(){
fatalError("Must be overwritten in subclass")
}
/**
* Start the animation
*/
func start(){
animProxy.animators.append(self)/*Add your self to the list of animators that gets the onFrame call*/
if(!CVDisplayLinkIsRunning(animProxy.displayLink)){CVDisplayLinkStart(animProxy.displayLink)}/*start the displayLink if it isn't already running*/
}
/**
* Stop the animation
*/
func stop(){
animProxy.animators.removeAt(animProxy.animators.indexOf(self))/*If none exist -1 is returned and none is removed*/
if(animProxy.animators.isEmpty && CVDisplayLinkIsRunning(animProxy.displayLink)){CVDisplayLinkStop(animProxy.displayLink)}/*stops the frame ticker if there is no active running animators*/
}
}
extension FrameAnimator2 {
/**
* Assert if an animator is active or not, you can also check if the Animator is nil to check if is active or not
* TODO: ⚠️️ Name this hasStopped or isActive
*/
var stopped:Bool {return animProxy.animators.indexOf(self) == -1}
}

37 changes: 37 additions & 0 deletions animator/LoopAnimator2.swift
@@ -0,0 +1,37 @@
import Foundation
/**
* Makes it possible to create looping animations, n-loops or infinite-loops
* NOTE: use stop() to stop the animation if the animation is infinite, with n-loops the animation stops when the last repeat has run
* NOTE: we don't use structs as init values because structs look like this: SomeStruct(from:0...) bbut tupples + typealias looks simpler (from:0...) more like method args would
* PARAM: duration: in seconds
* PARAM: callBack: is the callback ref that is called on every "frame tick"
*/
class LoopAnimator2:Animator2{
var repeatCount:Int /*<--zero means infinite, not at the moment it seems*/
var curRepeatCount:Int = 0
init(initValues:LoopAnimator2.InitLoopValues = LoopAnimator2.initLoopValues, easing:@escaping EasingEquation = Easing.linear.ease, closure: @escaping FrameTick = {_ in}) {
self.repeatCount = initValues.repeatCount
super.init(initValues: (initValues.duration,initValues.from,initValues.to), easing: easing, closure: closure)
}
/**
* Fires on every frame tick
*/
override func onFrame(){
let val:CGFloat = easing(currentFrameCount, from, to-from, framesToEnd)
frameTick(val)/*call the FrameTick method*/
if(currentFrameCount >= framesToEnd){
self.currentFrameCount = 0/*reset*/
if(curRepeatCount >= repeatCount){/*The loop ended*/
curRepeatCount = 0/*reset*/
stop()/*stop animation*/
completed()
}
curRepeatCount += 1
}
self.currentFrameCount += 1
}
}
extension LoopAnimator2{
typealias InitLoopValues = (duration:CGFloat,from:CGFloat,to:CGFloat,repeatCount:Int)/*Signature for initValues*/
static var initLoopValues:InitLoopValues = (duration:0.5,from:0,to:1,repeatCount:3)/*Default init values*/
}
4 changes: 2 additions & 2 deletions LoopingAnimator.swift → animator/LoopingAnimator.swift
Expand Up @@ -8,7 +8,7 @@ import Foundation
class LoopingAnimator:Animator{
var repeatCount:Int/*<--zero means infinite, not at the moment it seems*/
var curRepeatCount:Int = 0
init(_ animatable:Animatable, _ repeatCount:Int = 0,_ duration:CGFloat = 0.5, _ from:CGFloat, _ to:CGFloat, _ callBack:@escaping FrameTick, _ easing:@escaping EasingEquation = Linear.ease){
init(_ animatable:AnimProxyKind, _ repeatCount:Int = 0,_ duration:CGFloat = 0.5, _ from:CGFloat, _ to:CGFloat, _ callBack:@escaping FrameTick, _ easing:@escaping EasingEquation = Linear.ease){
self.repeatCount = repeatCount
super.init(animatable, duration, from, to, callBack, easing)
}
Expand All @@ -17,7 +17,7 @@ class LoopingAnimator:Animator{
*/
override func onFrame(){
let val:CGFloat = easing(currentFrameCount, from, to-from, framesToEnd)
callBack(val)/*call the FrameTick method*/
frameTick(val)/*call the FrameTick method*/
if(currentFrameCount >= framesToEnd){
self.currentFrameCount = 0/*reset*/
if(curRepeatCount >= repeatCount){/*The loop ended*/
Expand Down
9 changes: 4 additions & 5 deletions Easing.swift → easing/Easing.swift
Expand Up @@ -7,6 +7,7 @@ import Cocoa
* TODO: ⚠️️ Complete the: Elastic, Circular, Back, bounce, Quibic +++
* NOTE: robertpenner.com has lots of tutorials and pdfs on how easing works
*/
typealias EasingEquation = (_ t:CGFloat,_ b:CGFloat,_ c:CGFloat,_ d:CGFloat)->CGFloat/*Easing equation signature*/
class Easing{
static var back:Back.Type {return Back.self}
static var bounce:Bounce.Type {return Bounce.self}
Expand All @@ -20,15 +21,13 @@ class Easing{
static var quint:Quint.Type {return Quint.self}
static var sine:Sine.Type {return Sine.self}
}

/**
* NOTE: If you decrease the decimal variable you increase the friction effect
*/

/*
static func easeOut(value : CGFloat, _ from:CGFloat, _ to:CGFloat) -> CGFloat {
let distToGoal:CGFloat = NumberParser.relativeDifference(value, to)
let val:CGFloat = 0.2 * distToGoal
return val
let distToGoal:CGFloat = NumberParser.relativeDifference(value, to)
let val:CGFloat = 0.2 * distToGoal
return val
}
*/
File renamed without changes.

0 comments on commit a50be8f

Please sign in to comment.