Last active
November 8, 2020 06:12
-
-
Save OrkhanAlikhanov/79ce67a1739e7a4a4dba013d00663bd4 to your computer and use it in GitHub Desktop.
TransitionAnimator that's based on timing and speed of layer
This file contains hidden or 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
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