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
|
//
// CustomNavigationController.swift
// MullvadVPN
//
// Created by pronebird on 17/09/2020.
// Copyright © 2020 Mullvad VPN AB. All rights reserved.
//
import Foundation
import UIKit
enum NavigationPopTrigger {
case backButton
case interactiveGesture
}
protocol ConditionalNavigation: AnyObject {
func shouldPopNavigationItem(_ navigationItem: UINavigationItem, trigger: NavigationPopTrigger)
-> Bool
}
class CustomNavigationController: UINavigationController, UINavigationBarDelegate {
private static let classInit: Void = {
swizzleMethod(
aClass: CustomNavigationController.self,
originalSelector: #selector(UINavigationBarDelegate.navigationBar(_:shouldPop:)),
newSelector: #selector(customNavigationController_navigationBar(_:shouldPop:))
)
}()
private var popGestureRecognizerDelegate: CustomPopGestureRecognizerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
_ = Self.classInit
popGestureRecognizerDelegate = CustomPopGestureRecognizerDelegate(
navigationController: self,
systemGestureRecognizerDelegate: interactivePopGestureRecognizer?.delegate
)
// Replace the system interactive gesture recognizer
interactivePopGestureRecognizer?.delegate = popGestureRecognizerDelegate
}
@objc dynamic func customNavigationController_navigationBar(
_ navigationBar: UINavigationBar,
shouldPop item: UINavigationItem
) -> Bool {
var shouldPop = true
if let conformingViewController = topViewController as? ConditionalNavigation {
shouldPop = conformingViewController.shouldPopNavigationItem(item, trigger: .backButton)
}
// Only call super implementation when we want to pop the controller
if shouldPop {
willPop(navigationItem: item)
// Call super implementation
return customNavigationController_navigationBar(navigationBar, shouldPop: item)
} else {
return shouldPop
}
}
func willPop(navigationItem: UINavigationItem) {
// Override in subclasses
}
}
private class CustomPopGestureRecognizerDelegate: NSObject, UIGestureRecognizerDelegate {
private let systemGestureRecognizerDelegate: UIGestureRecognizerDelegate?
private weak var navigationController: UINavigationController?
init(
navigationController: UINavigationController,
systemGestureRecognizerDelegate: UIGestureRecognizerDelegate?
) {
self.navigationController = navigationController
self.systemGestureRecognizerDelegate = systemGestureRecognizerDelegate
}
override func responds(to aSelector: Selector!) -> Bool {
if Self.instancesRespond(to: aSelector) {
return true
} else {
return systemGestureRecognizerDelegate?.responds(to: aSelector) ?? false
}
}
override func forwardingTarget(for aSelector: Selector!) -> Any? {
let shouldForward = systemGestureRecognizerDelegate?.responds(to: aSelector) ?? false
if shouldForward {
return systemGestureRecognizerDelegate
} else {
return nil
}
}
// MARK: - UIGestureRecognizerDelegate
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
let shouldBegin = systemGestureRecognizerDelegate?
.gestureRecognizerShouldBegin?(gestureRecognizer) ?? true
guard let navigationController = navigationController,
let topItem = navigationController.navigationBar.topItem,
let conformingViewController = navigationController
.topViewController as? ConditionalNavigation
else {
return shouldBegin
}
return shouldBegin && conformingViewController.shouldPopNavigationItem(
topItem,
trigger: .interactiveGesture
)
}
}
|