diff --git a/Display/ContainableController.swift b/Display/ContainableController.swift index d0a6f0f0cb..055fa46254 100644 --- a/Display/ContainableController.swift +++ b/Display/ContainableController.swift @@ -1,10 +1,20 @@ import UIKit import AsyncDisplayKit +import SwiftSignalKit public protocol ContainableController: class { var view: UIView! { get } + var isViewLoaded: Bool { get } + var isOpaqueWhenInOverlay: Bool { get } + var ready: Promise { get } func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations + var deferScreenEdgeGestures: UIRectEdge { get } func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) + + func viewWillAppear(_ animated: Bool) + func viewWillDisappear(_ animated: Bool) + func viewDidAppear(_ animated: Bool) + func viewDidDisappear(_ animated: Bool) } diff --git a/Display/GlobalOverlayPresentationContext.swift b/Display/GlobalOverlayPresentationContext.swift index 75eafc370c..c033cccae8 100644 --- a/Display/GlobalOverlayPresentationContext.swift +++ b/Display/GlobalOverlayPresentationContext.swift @@ -25,7 +25,7 @@ private func isViewVisibleInHierarchy(_ view: UIView, _ initial: Bool = true) -> final class GlobalOverlayPresentationContext { private let statusBarHost: StatusBarHost? - private var controllers: [ViewController] = [] + private var controllers: [ContainableController] = [] private var presentationDisposables = DisposableSet() private var layout: ContainerViewLayout? @@ -49,7 +49,7 @@ final class GlobalOverlayPresentationContext { return nil } - func present(_ controller: ViewController) { + func present(_ controller: ContainableController) { let controllerReady = controller.ready.get() |> filter({ $0 }) |> take(1) @@ -68,12 +68,12 @@ final class GlobalOverlayPresentationContext { strongSelf.controllers.append(controller) if let view = strongSelf.currentPresentationView(), let layout = strongSelf.layout { - controller.navigation_setDismiss({ [weak controller] in + (controller as? UIViewController)?.navigation_setDismiss({ [weak controller] in if let strongSelf = self, let controller = controller { strongSelf.dismiss(controller) } }, rootController: nil) - controller.setIgnoreAppearanceMethodInvocations(true) + (controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(true) if layout != initialLayout { controller.view.frame = CGRect(origin: CGPoint(), size: layout.size) view.addSubview(controller.view) @@ -81,7 +81,7 @@ final class GlobalOverlayPresentationContext { } else { view.addSubview(controller.view) } - controller.setIgnoreAppearanceMethodInvocations(false) + (controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(false) view.layer.invalidateUpTheTree() controller.viewWillAppear(false) controller.viewDidAppear(false) @@ -97,7 +97,7 @@ final class GlobalOverlayPresentationContext { self.presentationDisposables.dispose() } - private func dismiss(_ controller: ViewController) { + private func dismiss(_ controller: ContainableController) { if let index = self.controllers.index(where: { $0 === controller }) { self.controllers.remove(at: index) controller.viewWillDisappear(false) @@ -158,11 +158,11 @@ final class GlobalOverlayPresentationContext { return nil } - func combinedSupportedOrientations() -> ViewControllerSupportedOrientations { + func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations { var mask = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all) for controller in self.controllers { - mask = mask.intersection(controller.supportedOrientations) + mask = mask.intersection(controller.combinedSupportedOrientations(currentOrientationToLock: currentOrientationToLock)) } return mask diff --git a/Display/KeyShortcutsController.swift b/Display/KeyShortcutsController.swift index 9e9d321269..84ce20c38d 100644 --- a/Display/KeyShortcutsController.swift +++ b/Display/KeyShortcutsController.swift @@ -6,7 +6,7 @@ public protocol KeyShortcutResponder { public class KeyShortcutsController: UIResponder { private var effectiveShortcuts: [KeyShortcut]? - private var viewControllerEnumerator: ((ViewController) -> Bool) -> Void + private var viewControllerEnumerator: ((ContainableController) -> Bool) -> Void public static var isAvailable: Bool { if #available(iOSApplicationExtension 8.0, *), UIDevice.current.userInterfaceIdiom == .pad { @@ -16,7 +16,7 @@ public class KeyShortcutsController: UIResponder { } } - public init(enumerator: @escaping ((ViewController) -> Bool) -> Void) { + public init(enumerator: @escaping ((ContainableController) -> Bool) -> Void) { self.viewControllerEnumerator = enumerator super.init() } diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index 6cd86d8d61..4e853a0bb1 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -170,14 +170,14 @@ private final class NativeWindow: UIWindow, WindowHost { var layoutSubviewsEvent: (() -> Void)? var updateIsUpdatingOrientationLayout: ((Bool) -> Void)? var updateToInterfaceOrientation: (() -> Void)? - var presentController: ((ViewController, PresentationSurfaceLevel, Bool, @escaping () -> Void) -> Void)? - var presentControllerInGlobalOverlay: ((_ controller: ViewController) -> Void)? + var presentController: ((ContainableController, PresentationSurfaceLevel, Bool, @escaping () -> Void) -> Void)? + var presentControllerInGlobalOverlay: ((_ controller: ContainableController) -> Void)? var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)? var presentNativeImpl: ((UIViewController) -> Void)? var invalidateDeferScreenEdgeGestureImpl: (() -> Void)? var invalidatePreferNavigationUIHiddenImpl: (() -> Void)? var cancelInteractiveKeyboardGesturesImpl: (() -> Void)? - var forEachControllerImpl: (((ViewController) -> Void) -> Void)? + var forEachControllerImpl: (((ContainableController) -> Void) -> Void)? var getAccessibilityElementsImpl: (() -> [Any]?)? override var accessibilityElements: [Any]? { @@ -256,11 +256,11 @@ private final class NativeWindow: UIWindow, WindowHost { self.updateToInterfaceOrientation?() }*/ - func present(_ controller: ViewController, on level: PresentationSurfaceLevel, blockInteraction: Bool, completion: @escaping () -> Void) { + func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool, completion: @escaping () -> Void) { self.presentController?(controller, level, blockInteraction, completion) } - func presentInGlobalOverlay(_ controller: ViewController) { + func presentInGlobalOverlay(_ controller: ContainableController) { self.presentControllerInGlobalOverlay?(controller) } @@ -284,7 +284,7 @@ private final class NativeWindow: UIWindow, WindowHost { self.cancelInteractiveKeyboardGesturesImpl?() } - func forEachController(_ f: (ViewController) -> Void) { + func forEachController(_ f: (ContainableController) -> Void) { self.forEachControllerImpl?(f) } } diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 0d99faae43..b40e5656fa 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -95,6 +95,14 @@ public enum NavigationControllerMode { } open class NavigationController: UINavigationController, ContainableController, UIGestureRecognizerDelegate { + public var isOpaqueWhenInOverlay: Bool = true + + public var ready: Promise = Promise(true) + + public var lockOrientation: Bool = false + + public var deferScreenEdgeGestures: UIRectEdge = UIRectEdge() + private let mode: NavigationControllerMode private var theme: NavigationControllerTheme diff --git a/Display/PresentationContext.swift b/Display/PresentationContext.swift index 360bbb5c62..39a8922f40 100644 --- a/Display/PresentationContext.swift +++ b/Display/PresentationContext.swift @@ -39,7 +39,7 @@ final class PresentationContext { return self.view != nil && self.layout != nil } - private(set) var controllers: [(ViewController, PresentationSurfaceLevel)] = [] + private(set) var controllers: [(ContainableController, PresentationSurfaceLevel)] = [] private var presentationDisposables = DisposableSet() @@ -47,8 +47,8 @@ final class PresentationContext { var isCurrentlyOpaque: Bool { for (controller, _) in self.controllers { - if controller.isOpaqueWhenInOverlay && controller.isNodeLoaded { - if traceIsOpaque(layer: controller.displayNode.layer, rect: controller.displayNode.bounds) { + if controller.isOpaqueWhenInOverlay && controller.isViewLoaded { + if traceIsOpaque(layer: controller.view.layer, rect: controller.view.bounds) { return true } } @@ -57,7 +57,7 @@ final class PresentationContext { } private func topLevelSubview(for level: PresentationSurfaceLevel) -> UIView? { - var topController: ViewController? + var topController: ContainableController? for (controller, controllerLevel) in self.controllers.reversed() { if !controller.isViewLoaded || controller.view.superview == nil { continue @@ -97,7 +97,7 @@ final class PresentationContext { } } - public func present(_ controller: ViewController, on level: PresentationSurfaceLevel, blockInteraction: Bool = false, completion: @escaping () -> Void) { + public func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool = false, completion: @escaping () -> Void) { let controllerReady = controller.ready.get() |> filter({ $0 }) |> take(1) @@ -105,15 +105,17 @@ final class PresentationContext { |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(true)) if let _ = self.view, let initialLayout = self.layout { - if controller.lockOrientation { - let orientations: UIInterfaceOrientationMask - if initialLayout.size.width < initialLayout.size.height { - orientations = .portrait - } else { - orientations = .landscape + if let controller = controller as? ViewController { + if controller.lockOrientation { + let orientations: UIInterfaceOrientationMask + if initialLayout.size.width < initialLayout.size.height { + orientations = .portrait + } else { + orientations = .landscape + } + + controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: orientations, compactSize: orientations) } - - controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: orientations, compactSize: orientations) } controller.view.frame = CGRect(origin: CGPoint(), size: initialLayout.size) controller.containerLayoutUpdated(initialLayout, transition: .immediate) @@ -145,12 +147,12 @@ final class PresentationContext { } strongSelf.controllers.insert((controller, level), at: insertIndex ?? strongSelf.controllers.count) if let view = strongSelf.view, let layout = strongSelf.layout { - controller.navigation_setDismiss({ [weak controller] in + (controller as? UIViewController)?.navigation_setDismiss({ [weak controller] in if let strongSelf = self, let controller = controller { strongSelf.dismiss(controller) } }, rootController: nil) - controller.setIgnoreAppearanceMethodInvocations(true) + (controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(true) if layout != initialLayout { controller.view.frame = CGRect(origin: CGPoint(), size: layout.size) if let topLevelSubview = strongSelf.topLevelSubview(for: level) { @@ -166,7 +168,7 @@ final class PresentationContext { view.addSubview(controller.view) } } - controller.setIgnoreAppearanceMethodInvocations(false) + (controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(false) view.layer.invalidateUpTheTree() controller.viewWillAppear(false) controller.viewDidAppear(false) @@ -182,7 +184,7 @@ final class PresentationContext { self.presentationDisposables.dispose() } - private func dismiss(_ controller: ViewController) { + private func dismiss(_ controller: ContainableController) { if let index = self.controllers.index(where: { $0.0 === controller }) { self.controllers.remove(at: index) controller.viewWillDisappear(false) @@ -247,11 +249,11 @@ final class PresentationContext { return nil } - func combinedSupportedOrientations() -> ViewControllerSupportedOrientations { + func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations { var mask = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all) for (controller, _) in self.controllers { - mask = mask.intersection(controller.supportedOrientations) + mask = mask.intersection(controller.combinedSupportedOrientations(currentOrientationToLock: currentOrientationToLock)) } return mask diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 083de9d572..dfdf9b6899 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -208,8 +208,8 @@ public final class WindowHostView { let updateDeferScreenEdgeGestures: (UIRectEdge) -> Void let updatePreferNavigationUIHidden: (Bool) -> Void - var present: ((ViewController, PresentationSurfaceLevel, Bool, @escaping () -> Void) -> Void)? - var presentInGlobalOverlay: ((_ controller: ViewController) -> Void)? + var present: ((ContainableController, PresentationSurfaceLevel, Bool, @escaping () -> Void) -> Void)? + var presentInGlobalOverlay: ((_ controller: ContainableController) -> Void)? var presentNative: ((UIViewController) -> Void)? var updateSize: ((CGSize, Double) -> Void)? var layoutSubviews: (() -> Void)? @@ -219,7 +219,7 @@ public final class WindowHostView { var invalidateDeferScreenEdgeGesture: (() -> Void)? var invalidatePreferNavigationUIHidden: (() -> Void)? var cancelInteractiveKeyboardGestures: (() -> Void)? - var forEachController: (((ViewController) -> Void) -> Void)? + var forEachController: (((ContainableController) -> Void) -> Void)? var getAccessibilityElements: (() -> [Any]?)? init(containerView: UIView, eventView: UIView, isRotating: @escaping () -> Bool, updateSupportedInterfaceOrientations: @escaping (UIInterfaceOrientationMask) -> Void, updateDeferScreenEdgeGestures: @escaping (UIRectEdge) -> Void, updatePreferNavigationUIHidden: @escaping (Bool) -> Void) { @@ -246,9 +246,9 @@ public struct WindowTracingTags { } public protocol WindowHost { - func forEachController(_ f: (ViewController) -> Void) - func present(_ controller: ViewController, on level: PresentationSurfaceLevel, blockInteraction: Bool, completion: @escaping () -> Void) - func presentInGlobalOverlay(_ controller: ViewController) + func forEachController(_ f: (ContainableController) -> Void) + func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool, completion: @escaping () -> Void) + func presentInGlobalOverlay(_ controller: ContainableController) func invalidateDeferScreenEdgeGestures() func invalidatePreferNavigationUIHidden() func cancelInteractiveKeyboardGestures() @@ -742,18 +742,17 @@ public class Window1 { keyboardManager.surfaces = keyboardSurfaces var supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all) + let orientationToLock: UIInterfaceOrientationMask + if self.windowLayout.size.width < self.windowLayout.size.height { + orientationToLock = .portrait + } else { + orientationToLock = .landscape + } if let _rootController = self._rootController { - let orientationToLock: UIInterfaceOrientationMask - if self.windowLayout.size.width < self.windowLayout.size.height { - orientationToLock = .portrait - } else { - orientationToLock = .landscape - } - supportedOrientations = supportedOrientations.intersection(_rootController.combinedSupportedOrientations(currentOrientationToLock: orientationToLock)) } - supportedOrientations = supportedOrientations.intersection(self.presentationContext.combinedSupportedOrientations()) - supportedOrientations = supportedOrientations.intersection(self.overlayPresentationContext.combinedSupportedOrientations()) + supportedOrientations = supportedOrientations.intersection(self.presentationContext.combinedSupportedOrientations(currentOrientationToLock: orientationToLock)) + supportedOrientations = supportedOrientations.intersection(self.overlayPresentationContext.combinedSupportedOrientations(currentOrientationToLock: orientationToLock)) var resolvedOrientations: UIInterfaceOrientationMask switch self.windowLayout.metrics.widthClass { @@ -904,11 +903,11 @@ public class Window1 { } } - public func present(_ controller: ViewController, on level: PresentationSurfaceLevel, blockInteraction: Bool = false, completion: @escaping () -> Void = {}) { + public func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool = false, completion: @escaping () -> Void = {}) { self.presentationContext.present(controller, on: level, blockInteraction: blockInteraction, completion: completion) } - public func presentInGlobalOverlay(_ controller: ViewController) { + public func presentInGlobalOverlay(_ controller: ContainableController) { self.overlayPresentationContext.present(controller) } @@ -1009,7 +1008,7 @@ public class Window1 { return false } - public func forEachViewController(_ f: (ViewController) -> Bool) { + public func forEachViewController(_ f: (ContainableController) -> Bool) { for (controller, _) in self.presentationContext.controllers { if !f(controller) { break