Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import UIKit
import Foundation
// https://github.com/ekazaev/ChatLayout
final class ManualAnimator {
enum AnimationCurve {
case linear, parametric, easeInOut, easeIn, easeOut
func modify(_ x: CGFloat) -> CGFloat {
switch self {
case .linear:
return x
case .parametric:
return x.parametric
case .easeInOut:
return x.quadraticEaseInOut
case .easeIn:
return x.quadraticEaseIn
case .easeOut:
return x.quadraticEaseOut
}
}
}
private var displayLink: CADisplayLink?
private var start = Date()
private var total = TimeInterval(0)
private var closure: ((CGFloat) -> Void)?
private var animationCurve: AnimationCurve = .linear
func animate(duration: TimeInterval, curve: AnimationCurve = .linear, _ animations: @escaping (CGFloat) -> Void) {
guard duration > 0 else {
animations(1.0); return
}
reset()
start = Date()
closure = animations
total = duration
animationCurve = curve
let d = CADisplayLink(target: self, selector: #selector(tick))
d.add(to: .current, forMode: .common)
displayLink = d
}
@objc private func tick() {
let delta = Date().timeIntervalSince(start)
var percentage = animationCurve.modify(CGFloat(delta) / CGFloat(total))
if percentage < 0.0 {
percentage = 0.0
} else if percentage >= 1.0 {
percentage = 1.0
reset()
}
closure?(percentage)
}
private func reset() {
displayLink?.invalidate()
displayLink = nil
}
}
extension CGFloat {
fileprivate var parametric: CGFloat {
guard self > 0.0 else { return 0.0 }
guard self < 1.0 else { return 1.0 }
return ((self * self) / (2.0 * ((self * self) - self) + 1.0))
}
fileprivate var quadraticEaseInOut: CGFloat {
guard self > 0.0 else { return 0.0 }
guard self < 1.0 else { return 1.0 }
if self < 0.5 {
return 2 * self * self
}
return (-2 * self * self) + (4 * self) - 1
}
fileprivate var quadraticEaseOut: CGFloat {
guard self > 0.0 else { return 0.0 }
guard self < 1.0 else { return 1.0 }
return -self * (self - 2)
}
fileprivate var quadraticEaseIn: CGFloat {
guard self > 0.0 else { return 0.0 }
guard self < 1.0 else { return 1.0 }
return self * self
}
}