summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2023-05-05 13:48:03 +0200
committerAndrej Mihajlov <and@mullvad.net>2023-05-05 16:27:26 +0200
commitdc63878e37c7d739281b78770d2cb1bfbb53f299 (patch)
treea0c0c8949eaa1652a7a58b01262f510e9001ea3c
parenta8d0abf0c054c1aa3eb7ad51a00edc387e241133 (diff)
downloadmullvadvpn-dc63878e37c7d739281b78770d2cb1bfbb53f299.tar.xz
mullvadvpn-dc63878e37c7d739281b78770d2cb1bfbb53f299.zip
Re-parent notification controller between primary and secondary containers on iPad based on environment (compact, regular)
-rw-r--r--ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift68
-rw-r--r--ios/MullvadVPN/Presentation controllers/FormsheetPresentationController.swift39
2 files changed, 105 insertions, 2 deletions
diff --git a/ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift b/ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift
index 7bdff9081a..53032583d1 100644
--- a/ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift
@@ -115,7 +115,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
setupSplitView()
}
- primaryNavigationContainer.notificationController = notificationController
+ setNotificationControllerParent(isPrimary: true)
continueFlow(animated: false)
}
@@ -341,6 +341,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
private func beginHorizontalFlow(animated: Bool, completion: @escaping () -> Void) {
if isPad, secondaryNavigationContainer.presentingViewController == nil {
secondaryRootConfiguration.apply(to: secondaryNavigationContainer)
+ addSecondaryContextPresentationStyleObserver()
primaryNavigationContainer.present(
secondaryNavigationContainer,
@@ -362,12 +363,75 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
*/
private func endHorizontalFlow(animated: Bool = true, completion: (() -> Void)? = nil) {
if isPad {
- secondaryNavigationContainer.dismiss(animated: animated, completion: completion)
+ removeSecondaryContextPresentationStyleObserver()
+
+ secondaryNavigationContainer.dismiss(animated: animated) {
+ // Put notification controller back into primary container.
+ self.setNotificationControllerParent(isPrimary: true)
+
+ completion?()
+ }
} else {
completion?()
}
}
+ /**
+ Assigns notification controller to either primary or secondary container making sure that only one of them holds
+ the reference.
+ */
+ private func setNotificationControllerParent(isPrimary: Bool) {
+ if isPrimary {
+ secondaryNavigationContainer.notificationController = nil
+ primaryNavigationContainer.notificationController = notificationController
+ } else {
+ primaryNavigationContainer.notificationController = nil
+ secondaryNavigationContainer.notificationController = notificationController
+ }
+ }
+
+ /**
+ Start observing secondary context presentation style which is in compact environment turns into fullscreen
+ and otherwise looks like formsheet.
+
+ In response to compact environment and fullscreen presentation, the observer re-assigns notification controller
+ from primary to secondary context to mimic the look and feel of iPhone app. The opposite is also true, that it
+ will make sure that notification controller is presented within primary context when secondary context is in
+ formsheet presentation style.
+ */
+ private func addSecondaryContextPresentationStyleObserver() {
+ removeSecondaryContextPresentationStyleObserver()
+
+ NotificationCenter.default.addObserver(
+ self,
+ selector: #selector(formSheetControllerWillChangeFullscreenPresentation(_:)),
+ name: FormsheetPresentationController.willChangeFullScreenPresentation,
+ object: secondaryNavigationContainer
+ )
+ }
+
+ /**
+ Stop observing secondary context presentation style.
+ */
+ private func removeSecondaryContextPresentationStyleObserver() {
+ NotificationCenter.default.removeObserver(
+ self,
+ name: FormsheetPresentationController.willChangeFullScreenPresentation,
+ object: secondaryNavigationContainer
+ )
+ }
+
+ /**
+ This method is called in response to changes in fullscreen presentation style of form sheet presentation
+ controller.
+ */
+ @objc private func formSheetControllerWillChangeFullscreenPresentation(_ note: Notification) {
+ guard let isFullscreenNumber = note
+ .userInfo?[SecondaryContextPresentationController.isFullScreenUserInfoKey] as? NSNumber else { return }
+
+ setNotificationControllerParent(isPrimary: !isFullscreenNumber.boolValue)
+ }
+
private var isPad: Bool {
return UIDevice.current.userInterfaceIdiom == .pad
}
diff --git a/ios/MullvadVPN/Presentation controllers/FormsheetPresentationController.swift b/ios/MullvadVPN/Presentation controllers/FormsheetPresentationController.swift
index 047fd84bca..97ab75fefc 100644
--- a/ios/MullvadVPN/Presentation controllers/FormsheetPresentationController.swift
+++ b/ios/MullvadVPN/Presentation controllers/FormsheetPresentationController.swift
@@ -16,6 +16,23 @@ private let animationDuration: TimeInterval = 0.5
Custom implementation of a formsheet presentation controller.
*/
class FormsheetPresentationController: UIPresentationController {
+ /**
+ Name of notification posted when fullscreen presentation changes, including during initial presentation.
+ */
+ static let willChangeFullScreenPresentation = Notification
+ .Name(rawValue: "FormsheetPresentationControllerWillChangeFullScreenPresentation")
+
+ /**
+ User info key passed along with `willChangeFullScreenPresentation` notification that contains boolean value that
+ indicates if presentation controller is using fullscreen presentation.
+ */
+ static let isFullScreenUserInfoKey = "isFullScreen"
+
+ /**
+ Last known presentation style used to prevent emitting duplicate `willChangeFullScreenPresentation` notifications.
+ */
+ private var lastKnownIsInFullScreen: Bool?
+
private let dimmingView: UIView = {
let dimmingView = UIView()
dimmingView.backgroundColor = .black
@@ -85,6 +102,8 @@ class FormsheetPresentationController: UIPresentationController {
} else {
revealDimmingView()
}
+
+ postFullscreenPresentationWillChangeIfNeeded()
}
override func presentationTransitionDidEnd(_ completed: Bool) {
@@ -112,6 +131,26 @@ class FormsheetPresentationController: UIPresentationController {
dimmingView.removeFromSuperview()
}
}
+
+ override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
+ super.traitCollectionDidChange(previousTraitCollection)
+
+ postFullscreenPresentationWillChangeIfNeeded()
+ }
+
+ private func postFullscreenPresentationWillChangeIfNeeded() {
+ let currentIsInFullScreen = isInFullScreenPresentation
+
+ guard lastKnownIsInFullScreen != currentIsInFullScreen else { return }
+
+ lastKnownIsInFullScreen = currentIsInFullScreen
+
+ NotificationCenter.default.post(
+ name: Self.willChangeFullScreenPresentation,
+ object: presentedViewController,
+ userInfo: [Self.isFullScreenUserInfoKey: NSNumber(booleanLiteral: currentIsInFullScreen)]
+ )
+ }
}
class FormsheetTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {