-
Notifications
You must be signed in to change notification settings - Fork 6
/
SnapshotTransition.swift
99 lines (81 loc) · 3.62 KB
/
SnapshotTransition.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
import UIKit
class SnapshotTransition {
init(from: UIView,
to: UIView,
in container: UIView,
clipToBounds: Bool = true,
childTransitions: [(from: UIView, to: UIView)] = []) {
self.fromView = from
self.toView = to
self.containerView = container
let transitionView = UIView(frame: .zero)
transitionView.clipsToBounds = clipToBounds
self.transitionView = transitionView
self.childTransitions = childTransitions.map {
SnapshotTransition(from: $0.from, to: $0.to, in: transitionView, clipToBounds: false)
}
}
func prepare() {
let layerCornerRadiuses = [fromView, toView].map { ($0.layer, $0.layer.cornerRadius) }
let layerShadows = [fromView, toView].map { $0.layer }.map { ($0, LayerShadow.from($0)) }
let childAlphas = childTransitions.flatMap { [$0.fromView, $0.toView] }.map { ($0, $0.alpha) }
layerCornerRadiuses.forEach { layer, _ in layer.cornerRadius = 0 }
layerShadows.forEach { layer, _ in layer.apply(LayerShadow.none) }
childAlphas.forEach { view, _ in view.alpha = 0 }
toSnapshot = toView.snapshotView(afterScreenUpdates: true)
fromSnapshot = fromView.snapshotView(afterScreenUpdates: true)
layerCornerRadiuses.forEach { layer, radius in layer.cornerRadius = radius }
layerShadows.forEach { layer, shadow in layer.apply(shadow) }
childAlphas.forEach { view, alpha in view.alpha = alpha }
[fromSnapshot, toSnapshot].compactMap { $0 }.forEach {
transitionView.addSubview($0)
$0.autoresizingMask = [.flexibleWidth, .flexibleHeight]
$0.frame = transitionView.bounds
}
fromViewAlpha = fromView.alpha
toViewAlpha = toView.alpha
fromSnapshot?.alpha = 1
toSnapshot?.alpha = 0
fromView.alpha = 0
toView.alpha = 0
transitionView.layer.cornerRadius = fromView.layer.cornerRadius
transitionView.layer.apply(LayerShadow.from(fromView.layer))
transitionView.frame = fromView.convert(fromView.bounds, to: containerView)
containerView.addSubview(transitionView)
childTransitions.forEach { $0.prepare() }
}
func addKeyframes() {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5) { [toSnapshot] in
toSnapshot?.alpha = 1
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) { [fromSnapshot] in
fromSnapshot?.alpha = 0
}
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1) { [transitionView, toView] in
transitionView.layer.cornerRadius = toView.layer.cornerRadius
transitionView.frame = toView.convert(toView.bounds, to: transitionView.superview)
transitionView.layer.apply(LayerShadow.from(toView.layer))
}
childTransitions.forEach { $0.addKeyframes() }
}
func cleanUp() {
toView.alpha = toViewAlpha
fromView.alpha = fromViewAlpha
transitionView.removeFromSuperview()
fromSnapshot?.removeFromSuperview()
toSnapshot?.removeFromSuperview()
fromSnapshot = nil
toSnapshot = nil
childTransitions.forEach { $0.cleanUp() }
}
// MARK: Private
private let fromView: UIView
private let toView: UIView
private let containerView: UIView
private let transitionView: UIView
private let childTransitions: [SnapshotTransition]
private var fromViewAlpha: CGFloat = 1
private var toViewAlpha: CGFloat = 1
private var fromSnapshot: UIView?
private var toSnapshot: UIView?
}