diff options
Diffstat (limited to 'ios')
| -rw-r--r-- | ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift | 68 | ||||
| -rw-r--r-- | ios/MullvadVPN/Presentation controllers/FormsheetPresentationController.swift | 39 |
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 { |
