Skip to content

Instantly share code, notes, and snippets.

@OrkhanAlikhanov
Last active November 8, 2020 06:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save OrkhanAlikhanov/79ce67a1739e7a4a4dba013d00663bd4 to your computer and use it in GitHub Desktop.
Save OrkhanAlikhanov/79ce67a1739e7a4a4dba013d00663bd4 to your computer and use it in GitHub Desktop.
TransitionAnimator that's based on timing and speed of layer
private class TransitionAnimator {
let groupName = "transition.group"
weak var layer: CALayer?
init(_ view: UIView) {
self.layer = view.layer
}
private var group: CAAnimationGroup? {
return layer?.animation(forKey: groupName) as? CAAnimationGroup
}
private func addGroup(_ animations: [CAAnimation], duration: TimeInterval) {
guard let layer = layer else {
return
}
layer.speed = 1
layer.timeOffset = 0
let t = CACurrentMediaTime()
layer.beginTime = t
layer.setValue(t, forKey: "lastStartedAt")
layer.setValue(0, forKey: "currentDuration")
layer.removeAllAnimations()
let g = CAAnimationGroup()
g.animations = animations
g.duration = duration
layer.add(g, forKey: groupName)
}
func start(_ animations: [CAAnimation], duration: TimeInterval) {
addGroup(animations, duration: duration)
pause()
}
func seek(_ progress: CGFloat) {
guard let layer = layer, let duration = group?.duration else {
return
}
pause()
let s = CACurrentMediaTime()
layer.setValue(s, forKey: "lastStartedAt")
layer.beginTime = s
let currentDuration = duration * 2 * CFTimeInterval(progress)
layer.setValue(currentDuration, forKey: "currentDuration")
layer.timeOffset = currentDuration
}
func pause() {
guard let layer = layer else {
return
}
let t = CACurrentMediaTime()
layer.beginTime = t
let currentDuration = (layer.value(forKey: "currentDuration") as! TimeInterval) + Double(layer.speed) * (t - (layer.value(forKey: "lastStartedAt") as! TimeInterval))
layer.setValue(currentDuration, forKey: "currentDuration")
layer.speed = 0
layer.timeOffset = currentDuration
}
func resume() {
guard let layer = layer else {
return
}
layer.speed = 1
let s = CACurrentMediaTime()
layer.setValue(s, forKey: "lastStartedAt")
layer.beginTime = s
}
func cancel(isAnimated: Bool = true) {
guard let layer = layer, let group = self.group else {
return
}
guard isAnimated else {
layer.removeAnimation(forKey: groupName)
return
}
let newDuration = layer.timeOffset
let newAnimations: [CABasicAnimation] = group.animations!.compactMap { $0 as? CABasicAnimation }.map {
let a = CABasicAnimation()
a.keyPath = $0.keyPath
a.toValue = layer.value(forKeyPath: $0.keyPath!)
a.fromValue = layer.presentation()!.value(forKeyPath: $0.keyPath!)
return a
}
layer.removeAnimation(forKey: groupName)
addGroup(newAnimations, duration: newDuration)
}
func finish() {
guard let layer = layer, let group = self.group else {
return
}
group.animations!.compactMap { $0 as? CABasicAnimation }.forEach {
layer.setValue($0.toValue, forKeyPath: $0.keyPath!)
}
resume()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment