-
Notifications
You must be signed in to change notification settings - Fork 40
/
Copy pathCALayer+animations.swift
147 lines (122 loc) · 5.22 KB
/
CALayer+animations.swift
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//
// CALayer+animations.swift
// UIKit
//
// Created by Michael Knoch on 31.07.17.
// Copyright © 2017 flowkey. All rights reserved.
//
extension CALayer {
public func add(_ animation: CABasicAnimation, forKey keyPath: String) {
let copy = CABasicAnimation(from: animation)
copy.creationTime = Timer()
// animation.fromValue is optional, set it to currently visible state if nil
if copy.fromValue == nil, let keyPath = copy.keyPath {
copy.fromValue = (_presentation ?? self).value(forKeyPath: keyPath)
}
copy.animationGroup?.queuedAnimations += 1
animations[keyPath]?.animationGroup?.animationDidStop(finished: false)
animations[keyPath] = copy
}
public func removeAnimation(forKey key: String) {
animations.removeValue(forKey: key)
}
public func removeAllAnimations() {
animations.removeAll()
}
func onWillSet(keyPath: AnimationKeyPath) {
CALayer.layerTreeIsDirty = true
let animationKey = keyPath.rawValue
if let animation = action(forKey: animationKey) as? CABasicAnimation,
self.hasBeenRenderedInThisPartOfOverallLayerHierarchy
|| animation.wasCreatedInUIAnimateBlock,
!self.isPresentationForAnotherLayer,
!CATransaction.disableActions()
{
add(animation, forKey: animationKey)
}
}
func onDidSetAnimations(wasEmpty: Bool) {
if !animations.isEmpty && wasEmpty {
UIView.layersWithAnimations.insert(self)
_presentation = createPresentation()
} else if animations.isEmpty && !wasEmpty {
_presentation = nil
UIView.layersWithAnimations.remove(self)
}
}
}
extension CALayer {
func animate(at currentTime: Timer) {
let presentation = createPresentation()
animations.forEach { (key, animation) in
let animationProgress = animation.progress(for: currentTime)
update(presentation, for: animation, with: animationProgress)
if animationProgress == 1 && animation.isRemovedOnCompletion {
animation.animationGroup?.animationDidStop(finished: true)
removeAnimation(forKey: key)
}
}
self._presentation = animations.isEmpty ? nil : presentation
}
private func update(_ presentation: CALayer, for animation: CABasicAnimation, with progress: CGFloat) {
guard let keyPath = animation.keyPath else { return }
switch keyPath {
case .backgroundColor:
guard let start = animation.fromValue as? UIColor else { return }
let end = animation.toValue as? UIColor ?? self.backgroundColor ?? UIColor.clear
presentation.backgroundColor = start.interpolation(to: end, progress: progress)
case .position:
guard let start = animation.fromValue as? CGPoint else { return }
let end = animation.toValue as? CGPoint ?? self.position
presentation.position = start + (end - start) * progress
case .anchorPoint:
guard let start = animation.fromValue as? CGPoint else { return }
let end = animation.toValue as? CGPoint ?? self.anchorPoint
presentation.anchorPoint = start + (end - start) * progress
case .bounds:
guard let startBounds = animation.fromValue as? CGRect else { return }
let endBounds = animation.toValue as? CGRect ?? self.bounds
presentation.bounds = (startBounds + (endBounds - startBounds) * progress)
case .opacity:
guard let startOpacity = animation.fromValue as? Float else { return }
let endOpacity = animation.toValue as? Float ?? self.opacity
presentation.opacity = startOpacity + ((endOpacity - startOpacity)) * Float(progress)
case .transform:
guard let startTransform = animation.fromValue as? CATransform3D else { return }
let endTransform = animation.toValue as? CATransform3D ?? self.transform
let interpolatedTransform = interpolate(
startTransform: startTransform,
endTransform: endTransform,
progress: Float(progress)
)
presentation.transform = interpolatedTransform
case .unknown: break
}
}
}
extension CALayer {
nonisolated static let defaultAnimationDuration: CGFloat = 0.25
static func defaultAction(forKey event: String) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: AnimationKeyPath(stringLiteral: event))
animation.duration = CATransaction.animationDuration()
return animation
}
}
extension CALayer {
func value(forKeyPath: AnimationKeyPath) -> AnimatableProperty? {
switch forKeyPath as AnimationKeyPath {
case .backgroundColor: return backgroundColor
case .opacity: return opacity
case .bounds: return bounds
case .transform: return transform
case .position: return position
case .anchorPoint: return anchorPoint
case .unknown: return nil
}
}
}
private extension CABasicAnimation {
var wasCreatedInUIAnimateBlock: Bool {
return animationGroup != nil
}
}