Swiftgram/Display/NavigationController.swift
2015-11-19 14:37:53 +03:00

318 lines
16 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 {
private var _navigationBar: NavigationBar!
private var navigationTransitionCoordinator: NavigationTransitionCoordinator?
private var currentPushDisposable = MetaDisposable()
private var statusBarChangeObserver: AnyObject?
private var layout: NavigationControllerLayout?
private var pendingLayout: (NavigationControllerLayout, NSTimeInterval, Bool)?
public override init() {
self._navigationBar = nil
super.init()
self._navigationBar = NavigationBar()
self._navigationBar.frame = CGRect(x: 0.0, y: 0.0, width: 320.0, height: 44.0)
self._navigationBar.proxy = self.navigationBar as? NavigationBarProxy
self._navigationBar.backPressed = { [weak self] in
if let strongSelf = self {
if strongSelf.viewControllers.count > 1 {
strongSelf.popViewControllerAnimated(true)
}
}
return
}
self.statusBarChangeObserver = NSNotificationCenter.defaultCenter().addObserverForName(UIApplicationWillChangeStatusBarFrameNotification, object: nil, queue: NSOperationQueue.mainQueue(), usingBlock: { [weak self] notification in
if let strongSelf = self {
let statusBarHeight: CGFloat = (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), (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: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
public required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func loadView() {
super.loadView()
self.navigationBar.superview?.insertSubview(_navigationBar.view, aboveSubview: self.navigationBar)
self.navigationBar.removeFromSuperview()
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: Selector("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)
}
}
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
let navigationTransitionCoordinator = NavigationTransitionCoordinator(container: self.view, topView: topView, bottomView: bottomView, navigationBar: self._navigationBar)
self.navigationTransitionCoordinator = navigationTransitionCoordinator
self._navigationBar.beginInteractivePopProgress(bottomController.navigationItem, evenMorePreviousItem: self.viewControllers.count >= 3 ? (self.viewControllers[self.viewControllers.count - 3] as UIViewController).navigationItem : nil)
}
case UIGestureRecognizerState.Changed:
if let navigationTransitionCoordinator = self.navigationTransitionCoordinator {
let translation = recognizer.translationInView(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.velocityInView(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)
self.popViewControllerAnimated(false)
topController.setIgnoreAppearanceMethodInvocations(false)
bottomController.setIgnoreAppearanceMethodInvocations(false)
topController.viewDidDisappear(true)
bottomController.viewDidAppear(true)
}
})
}
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)
}
})
}
}
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)
}
})
}
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)
}
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 popViewControllerAnimated(animated: Bool) -> UIViewController? {
var controller: UIViewController?
var controllers = self.viewControllers
if controllers.count != 0 {
controller = controllers[controllers.count - 1] as UIViewController
controllers.removeAtIndex(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)
}
controller.setParentLayout(self.childControllerLayoutForLayout(layout), duration: 0.0, curve: 0)
} else {
topViewController.view.frame = CGRect(origin: CGPoint(), size: self.view.bounds.size)
}
}
super.setViewControllers(viewControllers, animated: animated)
}
private func navigationBarFrame(layout: NavigationControllerLayout) -> CGRect {
return CGRect(x: 0.0, y: layout.statusBarHeight - 20.0, width: layout.layout.size.width, height: 20.0 + (layout.layout.size.height >= layout.layout.size.width ? 44.0 : 32.0))
}
private func childControllerLayoutForLayout(layout: NavigationControllerLayout) -> ViewControllerLayout {
var insets = layout.layout.insets
insets.top = self.navigationBarFrame(layout).maxY
return ViewControllerLayout(size: layout.layout.size, insets: insets, inputViewHeight: 0.0)
}
public func setParentLayout(layout: ViewControllerLayout, duration: NSTimeInterval, curve: UInt) {
let previousLayout: NavigationControllerLayout?
if let pendingLayout = self.pendingLayout {
previousLayout = pendingLayout.0
} else {
previousLayout = self.layout
}
self.pendingLayout = (NavigationControllerLayout(layout: layout, 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 {
controller.setParentLayout(self.childControllerLayoutForLayout(pendingLayout.0), duration: pendingLayout.1, curve: 0)
} else {
bottomController.view.frame = CGRectMake(0.0, 0.0, pendingLayout.0.layout.size.width, 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 = CGRectMake(0.0, 0.0, pendingLayout.0.layout.size.width, pendingLayout.0.layout.size.height)
}
}
if let navigationTransitionCoordinator = self.navigationTransitionCoordinator {
navigationTransitionCoordinator.updateProgress()
}
self.pendingLayout = nil
}
}
public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailByGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return otherGestureRecognizer is UIPanGestureRecognizer
}
}