forked from Adyen/adyen-ios
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFullScreenViewControllerPresenter.swift
131 lines (107 loc) · 5.04 KB
/
FullScreenViewControllerPresenter.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
//
// Copyright (c) 2024 Adyen N.V.
//
// This file is open source and available under the MIT license. See the LICENSE file for more info.
//
import SwiftUI
#if canImport(SwiftUI) && canImport(Combine)
@available(iOS 13.0, *)
public extension View {
/// Present a `ViewController` modally.
///
/// - Parameter viewController: A `Binding<UIViewController?>` instance,
/// set the `wrappedValue` to a view controller instance to dismiss the previous view controller if it had previously a value
/// and presents the new one,
/// set it to `nil` to dismiss the previous view Controller if any.
func present(viewController: Binding<UIViewController?>) -> some View {
modifier(FullScreenViewControllerPresenter(viewController: viewController))
}
}
@available(iOS 13.0, *)
internal struct FullScreenViewControllerPresenter: ViewModifier {
@Binding internal var viewController: UIViewController?
internal func body(content: Content) -> some View {
ZStack {
FullScreenView(viewController: $viewController)
content
}
}
}
@available(iOS 13.0, *)
internal struct FullScreenView: UIViewControllerRepresentable {
@Binding internal var viewController: UIViewController?
internal init(viewController: Binding<UIViewController?>) {
self._viewController = viewController
}
internal final class Coordinator {
fileprivate var currentlyPresentedViewController: UIViewController?
}
internal func makeUIViewController(context: UIViewControllerRepresentableContext<FullScreenView>) -> UIViewController {
StickyViewController()
}
internal func makeCoordinator() -> Coordinator {
Coordinator()
}
internal func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<FullScreenView>) {
if let viewController, viewController !== context.coordinator.currentlyPresentedViewController {
dismissIfNeededThenPresent(viewController: viewController, presenter: uiViewController, context: context)
} else if context.coordinator.currentlyPresentedViewController != nil, viewController == nil {
dismiss(presenter: uiViewController, context: context, completion: nil)
}
}
private func dismissIfNeededThenPresent(
viewController: UIViewController,
presenter: UIViewController,
context: UIViewControllerRepresentableContext<FullScreenView>
) {
if context.coordinator.currentlyPresentedViewController != nil {
dismiss(presenter: presenter, context: context) {
Self.present(viewController: viewController, presenter: presenter, context: context)
}
} else {
Self.present(viewController: viewController, presenter: presenter, context: context)
}
}
private static func present(
viewController: UIViewController,
presenter: UIViewController,
context: UIViewControllerRepresentableContext<FullScreenView>
) {
guard !viewController.isBeingPresented, !viewController.isBeingDismissed else { return }
presenter.present(viewController, animated: true) {
context.coordinator.currentlyPresentedViewController = viewController
}
}
private func dismiss(
presenter: UIViewController,
context: UIViewControllerRepresentableContext<FullScreenView>,
completion: (() -> Void)?
) {
guard let viewController = context.coordinator.currentlyPresentedViewController else { return }
guard !viewController.isBeingPresented, !viewController.isBeingDismissed else { return }
presenter.dismiss(animated: true) {
context.coordinator.currentlyPresentedViewController = nil
completion?()
}
}
}
/// This view controller only dismisses the view controller that is presented by this view controller.
/// It resists being dismissed if the `dismiss` method is called on the view controller itself with no presented controllers.
/// It could still be dismissed by a parent view controller
private class StickyViewController: UIViewController, UIViewControllerTransitioningDelegate {
init() {
super.init(nibName: nil, bundle: Bundle(for: StickyViewController.self))
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
if presentedViewController != nil {
super.dismiss(animated: flag, completion: completion)
} else {
completion?()
}
}
}
#endif