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
import UIKit
import Combine
import SnapKit
import Shared
final class BottomTransition: NSObject, UIViewControllerAnimatedTransitioning {
enum Direction {
case present
case dismiss
}
var direction: Direction = .present
private let onDismissal: EmptyClosure
private weak var darkOverlayView: UIControl?
private weak var topConstraint: Constraint?
private weak var bottomConstraint: Constraint?
private var cancellables = Set<AnyCancellable>()
private var presentedConstraints: [NSLayoutConstraint] = []
private var dismissedConstraints: [NSLayoutConstraint] = []
init(onDismissal: @escaping EmptyClosure) {
self.onDismissal = onDismissal
super.init()
}
func transitionDuration(using context: UIViewControllerContextTransitioning?) -> TimeInterval { 0.5 }
func animateTransition(using context: UIViewControllerContextTransitioning) {
switch direction {
case .present:
present(using: context)
case .dismiss:
dismiss(using: context)
}
}
private func present(using context: UIViewControllerContextTransitioning) {
guard let presentingController = context.viewController(forKey: .from),
let presentedView = context.view(forKey: .to) else {
context.completeTransition(false)
return
}
let darkOverlayView = UIControl()
self.darkOverlayView = darkOverlayView
darkOverlayView.alpha = 0.0
darkOverlayView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
context.containerView.addSubview(darkOverlayView)
darkOverlayView.frame = context.containerView.bounds
darkOverlayView
.publisher(for: .touchUpInside)
.sink { [weak presentingController] _ in
presentingController?.dismiss(animated: true)
}.store(in: &cancellables)
context.containerView.addSubview(presentedView)
presentedView.translatesAutoresizingMaskIntoConstraints = false
presentedConstraints = [
presentedView.leftAnchor.constraint(equalTo: context.containerView.leftAnchor),
presentedView.rightAnchor.constraint(equalTo: context.containerView.rightAnchor),
presentedView.bottomAnchor.constraint(equalTo: context.containerView.bottomAnchor),
presentedView.topAnchor.constraint(
greaterThanOrEqualTo: context.containerView.safeAreaLayoutGuide.topAnchor,
constant: 60
)
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
]
dismissedConstraints = [
presentedView.leftAnchor.constraint(equalTo: context.containerView.leftAnchor),
presentedView.rightAnchor.constraint(equalTo: context.containerView.rightAnchor),
presentedView.topAnchor.constraint(equalTo: context.containerView.bottomAnchor)
]
NSLayoutConstraint.activate(dismissedConstraints)
context.containerView.setNeedsLayout()
context.containerView.layoutIfNeeded()
NSLayoutConstraint.deactivate(dismissedConstraints)
NSLayoutConstraint.activate(presentedConstraints)
UIView.animate(
withDuration: transitionDuration(using: context),
delay: 0,
usingSpringWithDamping: 1,
initialSpringVelocity: 0,
options: .curveEaseInOut,
animations: {
darkOverlayView.alpha = 1.0
context.containerView.setNeedsLayout()
context.containerView.layoutIfNeeded()
},
completion: { _ in
context.completeTransition(true)
})
}
private func dismiss(using context: UIViewControllerContextTransitioning) {
NSLayoutConstraint.deactivate(presentedConstraints)
NSLayoutConstraint.activate(dismissedConstraints)
UIView.animate(
withDuration: transitionDuration(using: context),
delay: 0,
usingSpringWithDamping: 1,
initialSpringVelocity: 0,
options: .curveEaseInOut,
animations: { [weak darkOverlayView] in
darkOverlayView?.alpha = 0.0
context.containerView.setNeedsLayout()
context.containerView.layoutIfNeeded()
},
completion: { [weak self] _ in
context.completeTransition(true)
self?.onDismissal()
})
}
}