Swiftgram/Display/NavigationController.swift
2016-06-19 15:14:26 +03:00

502 lines
26 KiB
Swift

import Foundation
import UIKit
import AsyncDisplayKit
import SwiftSignalKit
private struct NavigationControllerLayout {
let layout: ViewControllerLayout
let statusBarHeight: CGFloat
}
public class NavigationController: NavigationControllerProxy, WindowContentController, UIGestureRecognizerDelegate, StatusBarSurfaceProvider {
private let statusBarSurface: StatusBarSurface = StatusBarSurface()
private var navigationTransitionCoordinator: NavigationTransitionCoordinator?
private var currentPushDisposable = MetaDisposable()
private var statusBarChangeObserver: AnyObject?
private var layout: NavigationControllerLayout?
private var pendingLayout: (NavigationControllerLayout, Double, Bool)?
private var _presentedViewController: UIViewController?
public override var presentedViewController: UIViewController? {
return self._presentedViewController
}
private var _viewControllers: [UIViewController] = []
public override var viewControllers: [UIViewController] {
get {
return self._viewControllers
} set(value) {
self.setViewControllers(_viewControllers, animated: false)
}
}
public override var topViewController: UIViewController? {
return self._viewControllers.last
}
public override init() {
super.init()
self.statusBarChangeObserver = NotificationCenter.default().addObserver(forName: NSNotification.Name.UIApplicationWillChangeStatusBarFrame, object: nil, queue: OperationQueue.main(), using: { [weak self] notification in
if let strongSelf = self {
let statusBarHeight: CGFloat = max(20.0, (notification.userInfo?[UIApplicationStatusBarFrameUserInfoKey] as? NSValue)?.cgRectValue().height ?? 20.0)
let previousLayout: NavigationControllerLayout?
if let pendingLayout = strongSelf.pendingLayout {
previousLayout = pendingLayout.0
} else {
previousLayout = strongSelf.layout
}
strongSelf.pendingLayout = (NavigationControllerLayout(layout: ViewControllerLayout(size: previousLayout?.layout.size ?? CGSize(), insets: previousLayout?.layout.insets ?? UIEdgeInsets(), inputViewHeight: 0.0, statusBarHeight: statusBarHeight), statusBarHeight: statusBarHeight), (strongSelf.pendingLayout?.2 ?? false) ? (strongSelf.pendingLayout?.1 ?? 0.3) : max(strongSelf.pendingLayout?.1 ?? 0.0, 0.35), true)
strongSelf.view.setNeedsLayout()
}
})
}
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
public required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func loadView() {
self.view = UIView()
//super.loadView()
self.view.clipsToBounds = true
self.navigationBar.removeFromSuperview()
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
panRecognizer.delegate = self
panRecognizer.cancelsTouchesInView = true
self.view.addGestureRecognizer(panRecognizer)
if self.topViewController != nil {
self.topViewController?.view.frame = CGRect(origin: CGPoint(), size: self.view.frame.size)
}
}
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case UIGestureRecognizerState.began:
if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil {
let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController
let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController
topController.viewWillDisappear(true)
let topView = topController.view!
bottomController.viewWillAppear(true)
let bottomView = bottomController.view!
var bottomStatusBar: StatusBar?
if let bottomController = bottomController as? ViewController {
bottomStatusBar = bottomController.statusBar
}
if let bottomStatusBar = bottomStatusBar {
self.statusBarSurface.insertStatusBar(bottomStatusBar, atIndex: 0)
}
(self.view.window as? Window)?.updateStatusBars()
let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.view, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar)
self.navigationTransitionCoordinator = navigationTransitionCoordinator
}
case UIGestureRecognizerState.changed:
if let navigationTransitionCoordinator = self.navigationTransitionCoordinator {
let translation = recognizer.translation(in: self.view).x
navigationTransitionCoordinator.progress = max(0.0, min(1.0, translation / self.view.frame.width))
}
case UIGestureRecognizerState.ended:
if let navigationTransitionCoordinator = self.navigationTransitionCoordinator {
let velocity = recognizer.velocity(in: self.view).x
if velocity > 1000 || navigationTransitionCoordinator.progress > 0.2 {
navigationTransitionCoordinator.animateCompletion(velocity, completion: {
self.navigationTransitionCoordinator = nil
//self._navigationBar.endInteractivePopProgress()
if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil {
let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController
let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController
topController.setIgnoreAppearanceMethodInvocations(true)
bottomController.setIgnoreAppearanceMethodInvocations(true)
let _ = self.popViewController(animated: false)
topController.setIgnoreAppearanceMethodInvocations(false)
bottomController.setIgnoreAppearanceMethodInvocations(false)
topController.viewDidDisappear(true)
bottomController.viewDidAppear(true)
if let topStatusBar = (topController as? ViewController)?.statusBar {
self.statusBarSurface.removeStatusBar(topStatusBar)
}
(self.view.window as? Window)?.updateStatusBars()
}
})
}
else {
if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil {
let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController
let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController
topController.viewWillAppear(true)
bottomController.viewWillDisappear(true)
}
navigationTransitionCoordinator.animateCancel({
self.navigationTransitionCoordinator = nil
//self._navigationBar.endInteractivePopProgress()
if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil {
let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController
let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController
topController.viewDidAppear(true)
bottomController.viewDidDisappear(true)
if let bottomStatusBar = (bottomController as? ViewController)?.statusBar {
self.statusBarSurface.removeStatusBar(bottomStatusBar)
}
(self.view.window as? Window)?.updateStatusBars()
}
})
}
}
case .cancelled:
if let navigationTransitionCoordinator = self.navigationTransitionCoordinator {
if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil {
let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController
let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController
topController.viewWillAppear(true)
bottomController.viewWillDisappear(true)
}
navigationTransitionCoordinator.animateCancel({
self.navigationTransitionCoordinator = nil
if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil {
let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController
let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController
topController.viewDidAppear(true)
bottomController.viewDidDisappear(true)
if let bottomStatusBar = (bottomController as? ViewController)?.statusBar {
self.statusBarSurface.removeStatusBar(bottomStatusBar)
}
(self.view.window as? Window)?.updateStatusBars()
}
})
}
default:
break
}
}
public func pushViewController(_ controller: ViewController) {
let layout: NavigationControllerLayout
if let currentLayout = self.layout {
layout = currentLayout
} else {
layout = NavigationControllerLayout(layout: ViewControllerLayout(size: self.view.bounds.size, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), inputViewHeight: 0.0, statusBarHeight: 20.0), statusBarHeight: 20.0)
}
controller.setParentLayout(self.childControllerLayoutForLayout(layout), duration: 0.0, curve: 0)
self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: {[weak self] _ in
if let strongSelf = self {
strongSelf.pushViewController(controller, animated: true)
}
}))
}
public override func pushViewController(_ viewController: UIViewController, animated: Bool) {
self.currentPushDisposable.set(nil)
var controllers = self.viewControllers
controllers.append(viewController)
self.setViewControllers(controllers, animated: animated)
}
public override func popViewController(animated: Bool) -> UIViewController? {
var controller: UIViewController?
var controllers = self.viewControllers
if controllers.count != 0 {
controller = controllers[controllers.count - 1] as UIViewController
controllers.remove(at: controllers.count - 1)
self.setViewControllers(controllers, animated: animated)
}
return controller
}
public override func setViewControllers(_ viewControllers: [UIViewController], animated: Bool) {
if viewControllers.count > 0 {
let topViewController = viewControllers[viewControllers.count - 1] as UIViewController
if let controller = topViewController as? WindowContentController {
let layout: NavigationControllerLayout
if let currentLayout = self.layout {
layout = currentLayout
} else {
layout = NavigationControllerLayout(layout: ViewControllerLayout(size: self.view.bounds.size, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), inputViewHeight: 0.0, statusBarHeight: 20.0), statusBarHeight: 20.0)
}
controller.setParentLayout(self.childControllerLayoutForLayout(layout), duration: 0.0, curve: 0)
} else {
topViewController.view.frame = CGRect(origin: CGPoint(), size: self.view.bounds.size)
}
}
if animated && self.viewControllers.count != 0 && viewControllers.count != 0 && self.viewControllers.last! !== viewControllers.last! {
let topController = viewControllers.last! as UIViewController
let bottomController = self.viewControllers.last! as UIViewController
if let topController = topController as? ViewController {
topController.navigationBar.previousItem = bottomController.navigationItem
}
bottomController.viewWillDisappear(true)
let bottomView = bottomController.view!
topController.viewWillAppear(true)
let topView = topController.view!
let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Push, container: self.view, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar)
self.navigationTransitionCoordinator = navigationTransitionCoordinator
if let topController = topController as? ViewController {
self.statusBarSurface.addStatusBar(topController.statusBar)
}
(self.view.window as? Window)?.updateStatusBars()
navigationTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in
if let strongSelf = self {
strongSelf.navigationTransitionCoordinator = nil
topController.setIgnoreAppearanceMethodInvocations(true)
bottomController.setIgnoreAppearanceMethodInvocations(true)
strongSelf.setViewControllers(viewControllers, animated: false)
topController.setIgnoreAppearanceMethodInvocations(false)
bottomController.setIgnoreAppearanceMethodInvocations(false)
bottomController.viewDidDisappear(true)
topController.viewDidAppear(true)
if let bottomController = bottomController as? ViewController {
strongSelf.statusBarSurface.removeStatusBar(bottomController.statusBar)
}
(strongSelf.view.window as? Window)?.updateStatusBars()
}
})
} else {
var previousStatusBar: StatusBar?
if let previousController = self.viewControllers.last as? ViewController {
previousStatusBar = previousController.statusBar
}
var newStatusBar: StatusBar?
if let newController = viewControllers.last as? ViewController {
newStatusBar = newController.statusBar
}
if previousStatusBar !== newStatusBar {
if let previousStatusBar = previousStatusBar {
self.statusBarSurface.removeStatusBar(previousStatusBar)
}
if let newStatusBar = newStatusBar {
self.statusBarSurface.addStatusBar(newStatusBar)
}
}
if let topController = self.viewControllers.last where topController.isViewLoaded() {
topController.navigation_setNavigationController(nil)
topController.view.removeFromSuperview()
}
self._viewControllers = viewControllers
if let topController = viewControllers.last {
topController.navigation_setNavigationController(self)
self.view.addSubview(topController.view)
}
//super.setViewControllers(viewControllers, animated: animated)
}
}
private func childControllerLayoutForLayout(_ layout: NavigationControllerLayout) -> ViewControllerLayout {
return ViewControllerLayout(size: layout.layout.size, insets: layout.layout.insets, inputViewHeight: 0.0, statusBarHeight: layout.statusBarHeight)
}
public func setParentLayout(_ layout: ViewControllerLayout, duration: Double, curve: UInt) {
let previousLayout: NavigationControllerLayout?
if let pendingLayout = self.pendingLayout {
previousLayout = pendingLayout.0
} else {
previousLayout = self.layout
}
self.pendingLayout = (NavigationControllerLayout(layout: ViewControllerLayout(size: layout.size, insets: layout.insets, inputViewHeight: layout.inputViewHeight, statusBarHeight: previousLayout?.statusBarHeight ?? 20.0), statusBarHeight: previousLayout?.statusBarHeight ?? 20.0), duration, false)
self.view.setNeedsLayout()
}
public override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let pendingLayout = self.pendingLayout {
self.layout = pendingLayout.0
if pendingLayout.1 > DBL_EPSILON {
animateRotation(self.view, toFrame: CGRect(x: 0.0, y: 0.0, width: pendingLayout.0.layout.size.width, height: pendingLayout.0.layout.size.height), duration: pendingLayout.1)
}
else {
self.view.frame = CGRect(x: 0.0, y: 0.0, width: pendingLayout.0.layout.size.width, height: pendingLayout.0.layout.size.height)
}
/*if pendingLayout.1 > DBL_EPSILON {
animateRotation(self._navigationBar, toFrame: self.navigationBarFrame(pendingLayout.0), duration: pendingLayout.1)
}
else {
self._navigationBar.frame = self.navigationBarFrame(pendingLayout.0)
}*/
if let navigationTransitionCoordinator = self.navigationTransitionCoordinator {
//navigationTransitionView.frame = CGRectMake(0.0, 0.0, toSize.width, toSize.height)
if self.viewControllers.count >= 2 {
let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController
if let controller = bottomController as? WindowContentController {
let layout: NavigationControllerLayout
if let currentLayout = self.layout {
layout = currentLayout
} else {
layout = NavigationControllerLayout(layout: ViewControllerLayout(size: self.view.bounds.size, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), inputViewHeight: 0.0, statusBarHeight: 20.0), statusBarHeight: 20.0)
}
controller.setParentLayout(self.childControllerLayoutForLayout(pendingLayout.0), duration: pendingLayout.1, curve: 0)
} else {
bottomController.view.frame = CGRect(x: 0.0, y: 0.0, width: pendingLayout.0.layout.size.width, height: pendingLayout.0.layout.size.height)
}
}
//self._navigationBar.setInteractivePopProgress(navigationTransitionCoordinator.progress)
}
if let topViewController = self.topViewController {
if let controller = topViewController as? WindowContentController {
controller.setParentLayout(self.childControllerLayoutForLayout(pendingLayout.0), duration: pendingLayout.1, curve: 0)
} else {
topViewController.view.frame = CGRect(x: 0.0, y: 0.0, width: pendingLayout.0.layout.size.width, height: pendingLayout.0.layout.size.height)
}
}
if let presentedViewController = self.presentedViewController {
if let controller = presentedViewController as? WindowContentController {
controller.setParentLayout(self.childControllerLayoutForLayout(pendingLayout.0), duration: pendingLayout.1, curve: 0)
} else {
presentedViewController.view.frame = CGRect(x: 0.0, y: 0.0, width: pendingLayout.0.layout.size.width, height: pendingLayout.0.layout.size.height)
}
}
if let navigationTransitionCoordinator = self.navigationTransitionCoordinator {
navigationTransitionCoordinator.updateProgress()
}
self.pendingLayout = nil
}
}
override public func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
if let controller = viewControllerToPresent as? NavigationController {
controller.navigation_setPresenting(self)
self._presentedViewController = controller
self.view.endEditing(true)
let layout: NavigationControllerLayout
if let currentLayout = self.layout {
layout = currentLayout
} else {
layout = NavigationControllerLayout(layout: ViewControllerLayout(size: self.view.bounds.size, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), inputViewHeight: 0.0, statusBarHeight: 20.0), statusBarHeight: 20.0)
}
controller.setParentLayout(self.childControllerLayoutForLayout(layout), duration: 0.0, curve: 0)
if flag {
controller.view.frame = self.view.bounds.offsetBy(dx: 0.0, dy: self.view.bounds.height)
self.view.addSubview(controller.view)
(self.view.window as? Window)?.updateStatusBars()
UIView.animate(withDuration: 0.3, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: {
controller.view.frame = self.view.bounds
(self.view.window as? Window)?.updateStatusBars()
}, completion: { _ in
if let completion = completion {
completion()
}
})
} else {
self.view.addSubview(controller.view)
(self.view.window as? Window)?.updateStatusBars()
if let completion = completion {
completion()
}
}
} else {
preconditionFailure("NavigationController can't present \(viewControllerToPresent). Only subclasses of NavigationController are allowed.")
}
}
override public func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
if let controller = self.presentedViewController {
if flag {
UIView.animate(withDuration: 0.3, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: {
controller.view.frame = self.view.bounds.offsetBy(dx: 0.0, dy: self.view.bounds.height)
(self.view.window as? Window)?.updateStatusBars()
}, completion: { _ in
controller.view.removeFromSuperview()
self._presentedViewController = nil
(self.view.window as? Window)?.updateStatusBars()
if let completion = completion {
completion()
}
})
} else {
self._presentedViewController = nil
(self.view.window as? Window)?.updateStatusBars()
if let completion = completion {
completion()
}
}
}
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return otherGestureRecognizer is UIPanGestureRecognizer
}
func statusBarSurfaces() -> [StatusBarSurface] {
var surfaces: [StatusBarSurface] = [self.statusBarSurface]
if let controller = self.presentedViewController as? StatusBarSurfaceProvider {
surfaces.append(contentsOf: controller.statusBarSurfaces())
}
return surfaces
}
}