This repository has been archived by the owner on Jun 25, 2023. It is now read-only.
/
ZoomPushTransition.swift
114 lines (85 loc) · 4.21 KB
/
ZoomPushTransition.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
import UIKit
/// A non-interactive, animated push transition.
class ZoomPushTransition: NSObject, ZoomTransition, UIViewControllerAnimatedTransitioning {
let type: TransitionType = .push
private(set) weak var fromDelegate: ZoomTransitionDelegate?
private(set) weak var toDelegate: ZoomTransitionDelegate?
private var transitionContext: UIViewControllerContextTransitioning?
private var animator: UIViewPropertyAnimator?
private let duration: TimeInterval = 0.45
private let damping: CGFloat = 0.75
private let crossDissolveDuration: TimeInterval = 0.3
init(from: ZoomTransitionDelegate?, to: ZoomTransitionDelegate?) {
self.fromDelegate = from
self.toDelegate = to
super.init()
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
duration
}
func animateTransition(using context: UIViewControllerContextTransitioning) {
configureAnimator(using: context).startAnimation()
}
/// Sets up the entire transition including container view, animation details and
/// completion registration. Returns an (inactive) animator.
private func configureAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewPropertyAnimator {
if let animator { return animator }
transitionContext.installViewsInContainer(for: type)
let animator = UIViewPropertyAnimator(duration: duration, dampingRatio: damping)
self.transitionContext = transitionContext
self.animator = animator
transitionWillBegin()
guard let sourceView = fromDelegate?.zoomTransitionView(self),
let targetView = toDelegate?.zoomTransitionView(self) else {
// In a race condition, it could potentially happen that between initiating
// the transition and now either delegate's required view has vanished
// (e.g. collection view reloads and the source cell we are transitioning
// from vanishes). In that case, use a rudamentary fallback transition
// instead of crashing.
dprint("Warning: Source and target views are required for the push transition.")
animator.addAnimations {} // Non-empty animations required.
animator.addCompletion { _ in
self.animator = nil
self.transitionContext = nil
self.transitionDidEnd()
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
return animator
}
let containerView = transitionContext.containerView
let transitionView = sourceView.transitionView()
let startingFrame = containerView.convert(sourceView.frame, from: sourceView.superview)
let targetFrame = containerView.convert(targetView.frame, from: targetView.superview)
transitionView.frame = startingFrame
containerView.addSubview(transitionView)
transitionContext.toView?.alpha = 0
targetView.isHidden = true
animator.addAnimations {
transitionView.frame = targetFrame
transitionContext.toView?.alpha = 1
}
animator.addCompletion { _ in
targetView.isHidden = false
// Blend into the target view separately.
UIView.animate(withDuration: self.crossDissolveDuration, animations: {
transitionView.alpha = 0
}, completion: { _ in
transitionView.removeFromSuperview()
})
self.transitionContext = nil
self.animator = nil
self.transitionDidEnd()
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
return animator
}
func animate(alongsideTransition animation: @escaping (UIViewControllerContextTransitioning) -> (), completion: ((UIViewControllerContextTransitioning) -> ())?) {
guard let transitionContext else { return }
animator?.addAnimations {
animation(transitionContext)
}
animator?.addCompletion { _ in
completion?(transitionContext)
}
}
}