summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj4
-rw-r--r--ios/MullvadVPN/CustomNavigationController.swift122
-rw-r--r--ios/MullvadVPN/ProblemReportViewController.swift37
-rw-r--r--ios/MullvadVPN/SettingsNavigationController.swift45
4 files changed, 54 insertions, 154 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index d999398e2b..1994428b55 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -86,7 +86,6 @@
58293FAE2510CA58005D0BB5 /* ProblemReportViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58293FAC2510CA58005D0BB5 /* ProblemReportViewController.swift */; };
58293FB125124117005D0BB5 /* CustomTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58293FB025124117005D0BB5 /* CustomTextField.swift */; };
58293FB3251241B4005D0BB5 /* CustomTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58293FB2251241B3005D0BB5 /* CustomTextView.swift */; };
- 58293FB725138B88005D0BB5 /* CustomNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58293FB625138B88005D0BB5 /* CustomNavigationController.swift */; };
582A8A3A28BCE19B00D0F9FB /* FixedWidthIntegerArithmeticsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582A8A3928BCE19B00D0F9FB /* FixedWidthIntegerArithmeticsTests.swift */; };
582AE3102440A6CA00E6733A /* AccountTokenInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582AE30F2440A6CA00E6733A /* AccountTokenInput.swift */; };
582AE3122440CA0D00E6733A /* AccountTokenInputTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582AE3112440CA0D00E6733A /* AccountTokenInputTests.swift */; };
@@ -680,7 +679,6 @@
58293FAC2510CA58005D0BB5 /* ProblemReportViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProblemReportViewController.swift; sourceTree = "<group>"; };
58293FB025124117005D0BB5 /* CustomTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextField.swift; sourceTree = "<group>"; };
58293FB2251241B3005D0BB5 /* CustomTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextView.swift; sourceTree = "<group>"; };
- 58293FB625138B88005D0BB5 /* CustomNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomNavigationController.swift; sourceTree = "<group>"; };
582A8A3928BCE19B00D0F9FB /* FixedWidthIntegerArithmeticsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FixedWidthIntegerArithmeticsTests.swift; sourceTree = "<group>"; };
582AE30F2440A6CA00E6733A /* AccountTokenInput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountTokenInput.swift; sourceTree = "<group>"; };
582AE3112440CA0D00E6733A /* AccountTokenInputTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountTokenInputTests.swift; sourceTree = "<group>"; };
@@ -1400,7 +1398,6 @@
5871FB95254ADE4E0051A0A4 /* ConsolidatedApplicationLog.swift */,
5896AE83246D5889005B36CB /* CustomDateComponentsFormatting.swift */,
582BB1B0229569620055B6EF /* CustomNavigationBar.swift */,
- 58293FB625138B88005D0BB5 /* CustomNavigationController.swift */,
5868BD32261DCD2600E6027F /* CustomSplitViewController.swift */,
58ACF64C26567A4F00ACE4B7 /* CustomSwitch.swift */,
58ACF64E26567A7100ACE4B7 /* CustomSwitchContainer.swift */,
@@ -2424,7 +2421,6 @@
584EBDBD2747C98F00A0C9FD /* NSAttributedString+Markdown.swift in Sources */,
5875960A26F371FC00BF6711 /* Tunnel+Messaging.swift in Sources */,
063687BA28EB234F00BE7161 /* PacketTunnelTransport.swift in Sources */,
- 58293FB725138B88005D0BB5 /* CustomNavigationController.swift in Sources */,
587425C12299833500CA2045 /* RootContainerViewController.swift in Sources */,
5896AE84246D5889005B36CB /* CustomDateComponentsFormatting.swift in Sources */,
5871167F2910035700D41AAC /* PreferencesInteractor.swift in Sources */,
diff --git a/ios/MullvadVPN/CustomNavigationController.swift b/ios/MullvadVPN/CustomNavigationController.swift
deleted file mode 100644
index 098ba7042f..0000000000
--- a/ios/MullvadVPN/CustomNavigationController.swift
+++ /dev/null
@@ -1,122 +0,0 @@
-//
-// 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
- )
- }
-}
diff --git a/ios/MullvadVPN/ProblemReportViewController.swift b/ios/MullvadVPN/ProblemReportViewController.swift
index 622cc1600c..c894664679 100644
--- a/ios/MullvadVPN/ProblemReportViewController.swift
+++ b/ios/MullvadVPN/ProblemReportViewController.swift
@@ -11,7 +11,7 @@ import MullvadTypes
import Operations
import UIKit
-class ProblemReportViewController: UIViewController, UITextFieldDelegate, ConditionalNavigation {
+final class ProblemReportViewController: UIViewController, UITextFieldDelegate {
private let interactor: ProblemReportInteractor
private var textViewKeyboardResponder: AutomaticKeyboardResponder?
@@ -629,6 +629,10 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit
validateForm()
}
+ private func setPopGestureEnabled(_ isEnabled: Bool) {
+ navigationController?.interactivePopGestureRecognizer?.isEnabled = isEnabled
+ }
+
private func clearPersistentViewModel() {
Self.persistentViewModel = ViewModel()
}
@@ -684,14 +688,16 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit
}
}
- // MARK: - Input fields' notifications
+ // MARK: - Input fields notifications
@objc private func messageTextViewDidBeginEditing() {
setDescriptionFieldExpanded(true)
+ setPopGestureEnabled(false)
}
@objc private func messageTextViewDidEndEditing() {
setDescriptionFieldExpanded(false)
+ setPopGestureEnabled(true)
}
@objc private func messageTextViewDidChange() {
@@ -704,27 +710,16 @@ class ProblemReportViewController: UIViewController, UITextFieldDelegate, Condit
// MARK: - UITextFieldDelegate
- func textFieldShouldReturn(_ textField: UITextField) -> Bool {
- messageTextView.becomeFirstResponder()
- return false
+ func textFieldDidBeginEditing(_ textField: UITextField) {
+ setPopGestureEnabled(false)
}
- // MARK: - ConditionalNavigation
-
- func shouldPopNavigationItem(
- _ navigationItem: UINavigationItem,
- trigger: NavigationPopTrigger
- ) -> Bool {
- switch trigger {
- case .interactiveGesture:
- // Disable swipe when editing
- return !emailTextField.isFirstResponder && !messageTextView.isFirstResponder
+ func textFieldDidEndEditing(_ textField: UITextField) {
+ setPopGestureEnabled(true)
+ }
- case .backButton:
- // Dismiss the keyboard to fix a visual glitch when moving back to the previous
- // controller
- view.endEditing(true)
- return true
- }
+ func textFieldShouldReturn(_ textField: UITextField) -> Bool {
+ messageTextView.becomeFirstResponder()
+ return false
}
}
diff --git a/ios/MullvadVPN/SettingsNavigationController.swift b/ios/MullvadVPN/SettingsNavigationController.swift
index c43da954cc..a8812419e5 100644
--- a/ios/MullvadVPN/SettingsNavigationController.swift
+++ b/ios/MullvadVPN/SettingsNavigationController.swift
@@ -32,10 +32,12 @@ protocol SettingsNavigationControllerDelegate: AnyObject {
)
}
-class SettingsNavigationController: CustomNavigationController, SettingsViewControllerDelegate,
- AccountViewControllerDelegate, UIAdaptivePresentationControllerDelegate
+class SettingsNavigationController: UINavigationController, SettingsViewControllerDelegate,
+ AccountViewControllerDelegate, UIAdaptivePresentationControllerDelegate,
+ UINavigationControllerDelegate
{
private let interactorFactory: SettingsInteractorFactory
+ private var currentRoutes: [SettingsNavigationRoute] = [.root]
weak var settingsDelegate: SettingsNavigationControllerDelegate?
@@ -56,17 +58,26 @@ class SettingsNavigationController: CustomNavigationController, SettingsViewCont
// Navigation controller ignores `prefersLargeTitles` when using `setViewControllers()`.
pushViewController(makeViewController(for: .root), animated: false)
+
+ delegate = self
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
- override func willPop(navigationItem: UINavigationItem) {
- let index = viewControllers.firstIndex { $0.navigationItem == navigationItem }
+ // MARK: - UINavigationControllerDelegate
+
+ func navigationController(
+ _ navigationController: UINavigationController,
+ willShow viewController: UIViewController,
+ animated: Bool
+ ) {
+ let newRoutes = viewControllers.compactMap { route(for: $0) }
- if viewControllers.count > 1, index == 1 {
- settingsDelegate?.settingsNavigationController(self, willNavigateTo: .root)
+ if currentRoutes != newRoutes, let nextRoute = newRoutes.last {
+ currentRoutes = newRoutes
+ settingsDelegate?.settingsNavigationController(self, willNavigateTo: nextRoute)
}
}
@@ -95,8 +106,13 @@ class SettingsNavigationController: CustomNavigationController, SettingsViewCont
let nextViewController = makeViewController(for: route)
if let rootController = viewControllers.first, viewControllers.count > 1 {
- setViewControllers([rootController, nextViewController], animated: animated)
+ let newChildren = [rootController, nextViewController]
+ let newRoutes = newChildren.compactMap { self.route(for: $0) }
+
+ currentRoutes = newRoutes
+ setViewControllers(newChildren, animated: animated)
} else {
+ currentRoutes.append(route)
pushViewController(nextViewController, animated: animated)
}
}
@@ -129,6 +145,21 @@ class SettingsNavigationController: CustomNavigationController, SettingsViewCont
}
}
+ private func route(for viewController: UIViewController) -> SettingsNavigationRoute? {
+ switch viewController {
+ case is SettingsViewController:
+ return .root
+ case is AccountViewController:
+ return .account
+ case is PreferencesViewController:
+ return .preferences
+ case is ProblemReportViewController:
+ return .problemReport
+ default:
+ return nil
+ }
+ }
+
// MARK: - UIAdaptivePresentationControllerDelegate
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {