Universal presentation

This commit is contained in:
Peter Iakovlev 2019-02-01 14:40:39 +04:00
parent 709df1faef
commit 076af3926c
7 changed files with 72 additions and 53 deletions

View File

@ -1,10 +1,20 @@
import UIKit import UIKit
import AsyncDisplayKit import AsyncDisplayKit
import SwiftSignalKit
public protocol ContainableController: class { public protocol ContainableController: class {
var view: UIView! { get } var view: UIView! { get }
var isViewLoaded: Bool { get }
var isOpaqueWhenInOverlay: Bool { get }
var ready: Promise<Bool> { get }
func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations
var deferScreenEdgeGestures: UIRectEdge { get }
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition)
func viewWillAppear(_ animated: Bool)
func viewWillDisappear(_ animated: Bool)
func viewDidAppear(_ animated: Bool)
func viewDidDisappear(_ animated: Bool)
} }

View File

@ -25,7 +25,7 @@ private func isViewVisibleInHierarchy(_ view: UIView, _ initial: Bool = true) ->
final class GlobalOverlayPresentationContext { final class GlobalOverlayPresentationContext {
private let statusBarHost: StatusBarHost? private let statusBarHost: StatusBarHost?
private var controllers: [ViewController] = [] private var controllers: [ContainableController] = []
private var presentationDisposables = DisposableSet() private var presentationDisposables = DisposableSet()
private var layout: ContainerViewLayout? private var layout: ContainerViewLayout?
@ -49,7 +49,7 @@ final class GlobalOverlayPresentationContext {
return nil return nil
} }
func present(_ controller: ViewController) { func present(_ controller: ContainableController) {
let controllerReady = controller.ready.get() let controllerReady = controller.ready.get()
|> filter({ $0 }) |> filter({ $0 })
|> take(1) |> take(1)
@ -68,12 +68,12 @@ final class GlobalOverlayPresentationContext {
strongSelf.controllers.append(controller) strongSelf.controllers.append(controller)
if let view = strongSelf.currentPresentationView(), let layout = strongSelf.layout { 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 { if let strongSelf = self, let controller = controller {
strongSelf.dismiss(controller) strongSelf.dismiss(controller)
} }
}, rootController: nil) }, rootController: nil)
controller.setIgnoreAppearanceMethodInvocations(true) (controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(true)
if layout != initialLayout { if layout != initialLayout {
controller.view.frame = CGRect(origin: CGPoint(), size: layout.size) controller.view.frame = CGRect(origin: CGPoint(), size: layout.size)
view.addSubview(controller.view) view.addSubview(controller.view)
@ -81,7 +81,7 @@ final class GlobalOverlayPresentationContext {
} else { } else {
view.addSubview(controller.view) view.addSubview(controller.view)
} }
controller.setIgnoreAppearanceMethodInvocations(false) (controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(false)
view.layer.invalidateUpTheTree() view.layer.invalidateUpTheTree()
controller.viewWillAppear(false) controller.viewWillAppear(false)
controller.viewDidAppear(false) controller.viewDidAppear(false)
@ -97,7 +97,7 @@ final class GlobalOverlayPresentationContext {
self.presentationDisposables.dispose() self.presentationDisposables.dispose()
} }
private func dismiss(_ controller: ViewController) { private func dismiss(_ controller: ContainableController) {
if let index = self.controllers.index(where: { $0 === controller }) { if let index = self.controllers.index(where: { $0 === controller }) {
self.controllers.remove(at: index) self.controllers.remove(at: index)
controller.viewWillDisappear(false) controller.viewWillDisappear(false)
@ -158,11 +158,11 @@ final class GlobalOverlayPresentationContext {
return nil return nil
} }
func combinedSupportedOrientations() -> ViewControllerSupportedOrientations { func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations {
var mask = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all) var mask = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all)
for controller in self.controllers { for controller in self.controllers {
mask = mask.intersection(controller.supportedOrientations) mask = mask.intersection(controller.combinedSupportedOrientations(currentOrientationToLock: currentOrientationToLock))
} }
return mask return mask

View File

@ -6,7 +6,7 @@ public protocol KeyShortcutResponder {
public class KeyShortcutsController: UIResponder { public class KeyShortcutsController: UIResponder {
private var effectiveShortcuts: [KeyShortcut]? private var effectiveShortcuts: [KeyShortcut]?
private var viewControllerEnumerator: ((ViewController) -> Bool) -> Void private var viewControllerEnumerator: ((ContainableController) -> Bool) -> Void
public static var isAvailable: Bool { public static var isAvailable: Bool {
if #available(iOSApplicationExtension 8.0, *), UIDevice.current.userInterfaceIdiom == .pad { 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 self.viewControllerEnumerator = enumerator
super.init() super.init()
} }

View File

@ -170,14 +170,14 @@ private final class NativeWindow: UIWindow, WindowHost {
var layoutSubviewsEvent: (() -> Void)? var layoutSubviewsEvent: (() -> Void)?
var updateIsUpdatingOrientationLayout: ((Bool) -> Void)? var updateIsUpdatingOrientationLayout: ((Bool) -> Void)?
var updateToInterfaceOrientation: (() -> Void)? var updateToInterfaceOrientation: (() -> Void)?
var presentController: ((ViewController, PresentationSurfaceLevel, Bool, @escaping () -> Void) -> Void)? var presentController: ((ContainableController, PresentationSurfaceLevel, Bool, @escaping () -> Void) -> Void)?
var presentControllerInGlobalOverlay: ((_ controller: ViewController) -> Void)? var presentControllerInGlobalOverlay: ((_ controller: ContainableController) -> Void)?
var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)? var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)?
var presentNativeImpl: ((UIViewController) -> Void)? var presentNativeImpl: ((UIViewController) -> Void)?
var invalidateDeferScreenEdgeGestureImpl: (() -> Void)? var invalidateDeferScreenEdgeGestureImpl: (() -> Void)?
var invalidatePreferNavigationUIHiddenImpl: (() -> Void)? var invalidatePreferNavigationUIHiddenImpl: (() -> Void)?
var cancelInteractiveKeyboardGesturesImpl: (() -> Void)? var cancelInteractiveKeyboardGesturesImpl: (() -> Void)?
var forEachControllerImpl: (((ViewController) -> Void) -> Void)? var forEachControllerImpl: (((ContainableController) -> Void) -> Void)?
var getAccessibilityElementsImpl: (() -> [Any]?)? var getAccessibilityElementsImpl: (() -> [Any]?)?
override var accessibilityElements: [Any]? { override var accessibilityElements: [Any]? {
@ -256,11 +256,11 @@ private final class NativeWindow: UIWindow, WindowHost {
self.updateToInterfaceOrientation?() 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) self.presentController?(controller, level, blockInteraction, completion)
} }
func presentInGlobalOverlay(_ controller: ViewController) { func presentInGlobalOverlay(_ controller: ContainableController) {
self.presentControllerInGlobalOverlay?(controller) self.presentControllerInGlobalOverlay?(controller)
} }
@ -284,7 +284,7 @@ private final class NativeWindow: UIWindow, WindowHost {
self.cancelInteractiveKeyboardGesturesImpl?() self.cancelInteractiveKeyboardGesturesImpl?()
} }
func forEachController(_ f: (ViewController) -> Void) { func forEachController(_ f: (ContainableController) -> Void) {
self.forEachControllerImpl?(f) self.forEachControllerImpl?(f)
} }
} }

View File

@ -95,6 +95,14 @@ public enum NavigationControllerMode {
} }
open class NavigationController: UINavigationController, ContainableController, UIGestureRecognizerDelegate { open class NavigationController: UINavigationController, ContainableController, UIGestureRecognizerDelegate {
public var isOpaqueWhenInOverlay: Bool = true
public var ready: Promise<Bool> = Promise(true)
public var lockOrientation: Bool = false
public var deferScreenEdgeGestures: UIRectEdge = UIRectEdge()
private let mode: NavigationControllerMode private let mode: NavigationControllerMode
private var theme: NavigationControllerTheme private var theme: NavigationControllerTheme

View File

@ -39,7 +39,7 @@ final class PresentationContext {
return self.view != nil && self.layout != nil return self.view != nil && self.layout != nil
} }
private(set) var controllers: [(ViewController, PresentationSurfaceLevel)] = [] private(set) var controllers: [(ContainableController, PresentationSurfaceLevel)] = []
private var presentationDisposables = DisposableSet() private var presentationDisposables = DisposableSet()
@ -47,8 +47,8 @@ final class PresentationContext {
var isCurrentlyOpaque: Bool { var isCurrentlyOpaque: Bool {
for (controller, _) in self.controllers { for (controller, _) in self.controllers {
if controller.isOpaqueWhenInOverlay && controller.isNodeLoaded { if controller.isOpaqueWhenInOverlay && controller.isViewLoaded {
if traceIsOpaque(layer: controller.displayNode.layer, rect: controller.displayNode.bounds) { if traceIsOpaque(layer: controller.view.layer, rect: controller.view.bounds) {
return true return true
} }
} }
@ -57,7 +57,7 @@ final class PresentationContext {
} }
private func topLevelSubview(for level: PresentationSurfaceLevel) -> UIView? { private func topLevelSubview(for level: PresentationSurfaceLevel) -> UIView? {
var topController: ViewController? var topController: ContainableController?
for (controller, controllerLevel) in self.controllers.reversed() { for (controller, controllerLevel) in self.controllers.reversed() {
if !controller.isViewLoaded || controller.view.superview == nil { if !controller.isViewLoaded || controller.view.superview == nil {
continue 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() let controllerReady = controller.ready.get()
|> filter({ $0 }) |> filter({ $0 })
|> take(1) |> take(1)
@ -105,15 +105,17 @@ final class PresentationContext {
|> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(true)) |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(true))
if let _ = self.view, let initialLayout = self.layout { if let _ = self.view, let initialLayout = self.layout {
if controller.lockOrientation { if let controller = controller as? ViewController {
let orientations: UIInterfaceOrientationMask if controller.lockOrientation {
if initialLayout.size.width < initialLayout.size.height { let orientations: UIInterfaceOrientationMask
orientations = .portrait if initialLayout.size.width < initialLayout.size.height {
} else { orientations = .portrait
orientations = .landscape } 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.view.frame = CGRect(origin: CGPoint(), size: initialLayout.size)
controller.containerLayoutUpdated(initialLayout, transition: .immediate) controller.containerLayoutUpdated(initialLayout, transition: .immediate)
@ -145,12 +147,12 @@ final class PresentationContext {
} }
strongSelf.controllers.insert((controller, level), at: insertIndex ?? strongSelf.controllers.count) strongSelf.controllers.insert((controller, level), at: insertIndex ?? strongSelf.controllers.count)
if let view = strongSelf.view, let layout = strongSelf.layout { 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 { if let strongSelf = self, let controller = controller {
strongSelf.dismiss(controller) strongSelf.dismiss(controller)
} }
}, rootController: nil) }, rootController: nil)
controller.setIgnoreAppearanceMethodInvocations(true) (controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(true)
if layout != initialLayout { if layout != initialLayout {
controller.view.frame = CGRect(origin: CGPoint(), size: layout.size) controller.view.frame = CGRect(origin: CGPoint(), size: layout.size)
if let topLevelSubview = strongSelf.topLevelSubview(for: level) { if let topLevelSubview = strongSelf.topLevelSubview(for: level) {
@ -166,7 +168,7 @@ final class PresentationContext {
view.addSubview(controller.view) view.addSubview(controller.view)
} }
} }
controller.setIgnoreAppearanceMethodInvocations(false) (controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(false)
view.layer.invalidateUpTheTree() view.layer.invalidateUpTheTree()
controller.viewWillAppear(false) controller.viewWillAppear(false)
controller.viewDidAppear(false) controller.viewDidAppear(false)
@ -182,7 +184,7 @@ final class PresentationContext {
self.presentationDisposables.dispose() self.presentationDisposables.dispose()
} }
private func dismiss(_ controller: ViewController) { private func dismiss(_ controller: ContainableController) {
if let index = self.controllers.index(where: { $0.0 === controller }) { if let index = self.controllers.index(where: { $0.0 === controller }) {
self.controllers.remove(at: index) self.controllers.remove(at: index)
controller.viewWillDisappear(false) controller.viewWillDisappear(false)
@ -247,11 +249,11 @@ final class PresentationContext {
return nil return nil
} }
func combinedSupportedOrientations() -> ViewControllerSupportedOrientations { func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations {
var mask = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all) var mask = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all)
for (controller, _) in self.controllers { for (controller, _) in self.controllers {
mask = mask.intersection(controller.supportedOrientations) mask = mask.intersection(controller.combinedSupportedOrientations(currentOrientationToLock: currentOrientationToLock))
} }
return mask return mask

View File

@ -208,8 +208,8 @@ public final class WindowHostView {
let updateDeferScreenEdgeGestures: (UIRectEdge) -> Void let updateDeferScreenEdgeGestures: (UIRectEdge) -> Void
let updatePreferNavigationUIHidden: (Bool) -> Void let updatePreferNavigationUIHidden: (Bool) -> Void
var present: ((ViewController, PresentationSurfaceLevel, Bool, @escaping () -> Void) -> Void)? var present: ((ContainableController, PresentationSurfaceLevel, Bool, @escaping () -> Void) -> Void)?
var presentInGlobalOverlay: ((_ controller: ViewController) -> Void)? var presentInGlobalOverlay: ((_ controller: ContainableController) -> Void)?
var presentNative: ((UIViewController) -> Void)? var presentNative: ((UIViewController) -> Void)?
var updateSize: ((CGSize, Double) -> Void)? var updateSize: ((CGSize, Double) -> Void)?
var layoutSubviews: (() -> Void)? var layoutSubviews: (() -> Void)?
@ -219,7 +219,7 @@ public final class WindowHostView {
var invalidateDeferScreenEdgeGesture: (() -> Void)? var invalidateDeferScreenEdgeGesture: (() -> Void)?
var invalidatePreferNavigationUIHidden: (() -> Void)? var invalidatePreferNavigationUIHidden: (() -> Void)?
var cancelInteractiveKeyboardGestures: (() -> Void)? var cancelInteractiveKeyboardGestures: (() -> Void)?
var forEachController: (((ViewController) -> Void) -> Void)? var forEachController: (((ContainableController) -> Void) -> Void)?
var getAccessibilityElements: (() -> [Any]?)? var getAccessibilityElements: (() -> [Any]?)?
init(containerView: UIView, eventView: UIView, isRotating: @escaping () -> Bool, updateSupportedInterfaceOrientations: @escaping (UIInterfaceOrientationMask) -> Void, updateDeferScreenEdgeGestures: @escaping (UIRectEdge) -> Void, updatePreferNavigationUIHidden: @escaping (Bool) -> Void) { 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 { public protocol WindowHost {
func forEachController(_ f: (ViewController) -> Void) func forEachController(_ f: (ContainableController) -> Void)
func present(_ controller: ViewController, on level: PresentationSurfaceLevel, blockInteraction: Bool, completion: @escaping () -> Void) func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool, completion: @escaping () -> Void)
func presentInGlobalOverlay(_ controller: ViewController) func presentInGlobalOverlay(_ controller: ContainableController)
func invalidateDeferScreenEdgeGestures() func invalidateDeferScreenEdgeGestures()
func invalidatePreferNavigationUIHidden() func invalidatePreferNavigationUIHidden()
func cancelInteractiveKeyboardGestures() func cancelInteractiveKeyboardGestures()
@ -742,18 +742,17 @@ public class Window1 {
keyboardManager.surfaces = keyboardSurfaces keyboardManager.surfaces = keyboardSurfaces
var supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all) 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 { 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(_rootController.combinedSupportedOrientations(currentOrientationToLock: orientationToLock))
} }
supportedOrientations = supportedOrientations.intersection(self.presentationContext.combinedSupportedOrientations()) supportedOrientations = supportedOrientations.intersection(self.presentationContext.combinedSupportedOrientations(currentOrientationToLock: orientationToLock))
supportedOrientations = supportedOrientations.intersection(self.overlayPresentationContext.combinedSupportedOrientations()) supportedOrientations = supportedOrientations.intersection(self.overlayPresentationContext.combinedSupportedOrientations(currentOrientationToLock: orientationToLock))
var resolvedOrientations: UIInterfaceOrientationMask var resolvedOrientations: UIInterfaceOrientationMask
switch self.windowLayout.metrics.widthClass { 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) self.presentationContext.present(controller, on: level, blockInteraction: blockInteraction, completion: completion)
} }
public func presentInGlobalOverlay(_ controller: ViewController) { public func presentInGlobalOverlay(_ controller: ContainableController) {
self.overlayPresentationContext.present(controller) self.overlayPresentationContext.present(controller)
} }
@ -1009,7 +1008,7 @@ public class Window1 {
return false return false
} }
public func forEachViewController(_ f: (ViewController) -> Bool) { public func forEachViewController(_ f: (ContainableController) -> Bool) {
for (controller, _) in self.presentationContext.controllers { for (controller, _) in self.presentationContext.controllers {
if !f(controller) { if !f(controller) {
break break