summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadVPN/CustomNavigationController.swift
blob: 1a7e7bb49d088c166ef9497e685d55eff8b8d81d (plain)
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
//
//  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: class {
    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 {
            // Call super implementation
            return customNavigationController_navigationBar(navigationBar, shouldPop: item)
        } else {
            return shouldPop
        }
    }
}

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)
    }
}