Last active
March 1, 2024 21:20
-
-
Save edwardsanchez/9d0a411e58d97f3f521f0bc845205f52 to your computer and use it in GitHub Desktop.
Spring Animator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//: Playground - noun: a place where people can play | |
import UIKit | |
//Changing values | |
var initialVelocity: CGFloat = 0 | |
var runningTime: CGFloat = 0 | |
var myProperty: CGFloat = 0 | |
//Resting position of spring | |
let endNumberValue: CGFloat = 300 | |
//Set relaxation time (duration in seconds) | |
let relaxationTime: CGFloat = 1 | |
//Spring constants | |
let dampingRatio: CGFloat = 0.9 | |
//Only allow damping ratio between just above 0 and 1 (critically damped) | |
let clippedDampingRatio: CGFloat = min(1, max(dampingRatio, 0.01)) | |
let mass: CGFloat = 1 | |
let fractionOfAmplitude: CGFloat = 1500 //A spring never gets to 0 amplitude, it gets infinitely smaller. This fraction represents the perceived 0 point. | |
let logOfFraction: CGFloat = log(fractionOfAmplitude) | |
let stiffness: CGFloat = (mass * pow(logOfFraction, 2)) / (pow(relaxationTime, 2) * pow(clippedDampingRatio, 2)) | |
let angularFrequency: CGFloat = sqrt(stiffness/mass) | |
let damping: CGFloat = 2 * mass * angularFrequency * clippedDampingRatio | |
internal func springFunction(_ currentValue: CGFloat, _ length: CGFloat, _ velocity: inout CGFloat) -> CGFloat { | |
let displacement = currentValue - length | |
let springForce = -stiffness * displacement // Hooke's Law (F = -kx) (where k is stiffness constant and x is displacement value) | |
let forceDamper = -damping * velocity // F = -dv (where d is damping constant and v is velocity) | |
let dampedForce = springForce + forceDamper | |
let acceleration = dampedForce / mass // a = F / m | |
let refreshRate: CGFloat = 1/60 //Hz | |
velocity += acceleration * refreshRate | |
let position = velocity * refreshRate | |
return position | |
} | |
public func animateProperty(_ property: inout CGFloat, _ runningTime: CGFloat) { | |
if runningTime < relaxationTime { | |
property += springFunction(property, endNumberValue, &initialVelocity) | |
} else { | |
//Set final value to exact value | |
property = endNumberValue | |
} | |
} | |
while runningTime <= relaxationTime { | |
//Put this inside a CADisplayLink updating the running time and your view origin will animate in a spring | |
animateProperty(&myProperty, runningTime) | |
runningTime += 1/60 | |
print(myProperty) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment