Files
POCloud-iOS/Pods/SideMenu/Pod/Classes/UISideMenuNavigationController.swift
2018-06-06 18:39:18 -05:00

344 lines
16 KiB
Swift

//
// UISideMenuNavigationController.swift
//
// Created by Jon Kent on 1/14/16.
// Copyright © 2016 Jon Kent. All rights reserved.
//
import UIKit
@objc public protocol UISideMenuNavigationControllerDelegate {
@objc optional func sideMenuWillAppear(menu: UISideMenuNavigationController, animated: Bool)
@objc optional func sideMenuDidAppear(menu: UISideMenuNavigationController, animated: Bool)
@objc optional func sideMenuWillDisappear(menu: UISideMenuNavigationController, animated: Bool)
@objc optional func sideMenuDidDisappear(menu: UISideMenuNavigationController, animated: Bool)
}
@objcMembers
open class UISideMenuNavigationController: UINavigationController {
fileprivate weak var foundDelegate: UISideMenuNavigationControllerDelegate?
fileprivate weak var activeDelegate: UISideMenuNavigationControllerDelegate? {
get {
guard !view.isHidden else {
return nil
}
return sideMenuDelegate ?? foundDelegate ?? findDelegate(forViewController: presentingViewController)
}
}
fileprivate func findDelegate(forViewController: UIViewController?) -> UISideMenuNavigationControllerDelegate? {
if let navigationController = forViewController as? UINavigationController {
return findDelegate(forViewController: navigationController.topViewController)
}
if let tabBarController = forViewController as? UITabBarController {
return findDelegate(forViewController: tabBarController.selectedViewController)
}
if let splitViewController = forViewController as? UISplitViewController {
return findDelegate(forViewController: splitViewController.viewControllers.last)
}
foundDelegate = forViewController as? UISideMenuNavigationControllerDelegate
return foundDelegate
}
fileprivate var usingInterfaceBuilder = false
internal var locked = false
internal var originalMenuBackgroundColor: UIColor?
internal var transition: SideMenuTransition {
get {
return sideMenuManager.transition
}
}
/// Delegate for receiving appear and disappear related events. If `nil` the visible view controller that displays a `UISideMenuNavigationController` automatically receives these events.
open weak var sideMenuDelegate: UISideMenuNavigationControllerDelegate?
/// SideMenuManager instance associated with this menu. Default is `SideMenuManager.default`. This property cannot be changed after the menu has loaded.
open weak var sideMenuManager: SideMenuManager! = SideMenuManager.default {
didSet {
if locked && oldValue != nil {
print("SideMenu Warning: a menu's sideMenuManager property cannot be changed after it has loaded.")
sideMenuManager = oldValue
}
}
}
/// Width of the menu when presented on screen, showing the existing view controller in the remaining space. Default is zero. When zero, `sideMenuManager.menuWidth` is used. This property cannot be changed while the isHidden property is false.
@IBInspectable open var menuWidth: CGFloat = 0 {
didSet {
if !isHidden && oldValue != menuWidth {
print("SideMenu Warning: a menu's width property can only be changed when it is hidden.")
menuWidth = oldValue
}
}
}
/// Whether the menu appears on the right or left side of the screen. Right is the default. This property cannot be changed after the menu has loaded.
@IBInspectable open var leftSide: Bool = false {
didSet {
if locked && leftSide != oldValue {
print("SideMenu Warning: a menu's leftSide property cannot be changed after it has loaded.")
leftSide = oldValue
}
}
}
/// Indicates if the menu is anywhere in the view hierarchy, even if covered by another view controller.
open var isHidden: Bool {
get {
return self.presentingViewController == nil
}
}
#if !STFU_SIDEMENU
// This override prevents newbie developers from creating black/blank menus and opening newbie issues.
// If you would like to remove this override, define STFU_SIDEMENU in the Active Compilation Conditions of your .plist file.
// Sorry for the inconvenience experienced developers :(
@available(*, unavailable, renamed: "init(rootViewController:)")
public init() {
fatalError("init is not available")
}
public override init(rootViewController: UIViewController) {
super.init(rootViewController: rootViewController)
}
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
#endif
open override func awakeFromNib() {
super.awakeFromNib()
usingInterfaceBuilder = true
}
override open func viewDidLoad() {
super.viewDidLoad()
if !locked && usingInterfaceBuilder {
if leftSide {
sideMenuManager.menuLeftNavigationController = self
} else {
sideMenuManager.menuRightNavigationController = self
}
}
}
open override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Dismiss keyboard to prevent weird keyboard animations from occurring during transition
presentingViewController?.view.endEditing(true)
foundDelegate = nil
activeDelegate?.sideMenuWillAppear?(menu: self, animated: animated)
}
override open func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// We had presented a view before, so lets dismiss ourselves as already acted upon
if view.isHidden {
transition.hideMenuComplete()
dismiss(animated: false, completion: { () -> Void in
self.view.isHidden = false
})
return
}
activeDelegate?.sideMenuDidAppear?(menu: self, animated: animated)
#if !STFU_SIDEMENU
if topViewController == nil {
print("SideMenu Warning: the menu doesn't have a view controller to show! UISideMenuNavigationController needs a view controller to display just like a UINavigationController.")
}
#endif
}
override open func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// When presenting a view controller from the menu, the menu view gets moved into another transition view above our transition container
// which can break the visual layout we had before. So, we move the menu view back to its original transition view to preserve it.
if !isBeingDismissed {
guard let sideMenuManager = sideMenuManager else {
return
}
if let mainView = transition.mainViewController?.view {
switch sideMenuManager.menuPresentMode {
case .viewSlideOut, .viewSlideInOut:
mainView.superview?.insertSubview(view, belowSubview: mainView)
case .menuSlideIn, .menuDissolveIn:
if let tapView = transition.tapView {
mainView.superview?.insertSubview(view, aboveSubview: tapView)
} else {
mainView.superview?.insertSubview(view, aboveSubview: mainView)
}
}
}
// We're presenting a view controller from the menu, so we need to hide the menu so it isn't showing when the presented view is dismissed.
UIView.animate(withDuration: animated ? sideMenuManager.menuAnimationDismissDuration : 0,
delay: 0,
usingSpringWithDamping: sideMenuManager.menuAnimationUsingSpringWithDamping,
initialSpringVelocity: sideMenuManager.menuAnimationInitialSpringVelocity,
options: sideMenuManager.menuAnimationOptions,
animations: {
self.transition.hideMenuStart()
self.activeDelegate?.sideMenuWillDisappear?(menu: self, animated: animated)
}) { (finished) -> Void in
self.activeDelegate?.sideMenuDidDisappear?(menu: self, animated: animated)
self.view.isHidden = true
}
return
}
activeDelegate?.sideMenuWillDisappear?(menu: self, animated: animated)
}
override open func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// Work-around: if the menu is dismissed without animation the transition logic is never called to restore the
// the view hierarchy leaving the screen black/empty. This is because the transition moves views within a container
// view, but dismissing without animation removes the container view before the original hierarchy is restored.
// This check corrects that.
if let sideMenuDelegate = activeDelegate as? UIViewController, sideMenuDelegate.view.window == nil {
transition.hideMenuStart().hideMenuComplete()
}
activeDelegate?.sideMenuDidDisappear?(menu: self, animated: animated)
// Clear selecton on UITableViewControllers when reappearing using custom transitions
guard let tableViewController = topViewController as? UITableViewController,
let tableView = tableViewController.tableView,
let indexPaths = tableView.indexPathsForSelectedRows,
tableViewController.clearsSelectionOnViewWillAppear else {
return
}
for indexPath in indexPaths {
tableView.deselectRow(at: indexPath, animated: false)
}
}
override open func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
// Don't bother resizing if the view isn't visible
guard !view.isHidden else {
return
}
NotificationCenter.default.removeObserver(self.transition, name: NSNotification.Name.UIApplicationWillChangeStatusBarFrame, object: nil)
coordinator.animate(alongsideTransition: { (context) in
self.transition.presentMenuStart()
}) { (context) in
NotificationCenter.default.addObserver(self.transition, selector:#selector(SideMenuTransition.handleNotification), name: NSNotification.Name.UIApplicationWillChangeStatusBarFrame, object: nil)
}
}
override open func pushViewController(_ viewController: UIViewController, animated: Bool) {
guard let sideMenuManager = sideMenuManager, viewControllers.count > 0 && sideMenuManager.menuPushStyle != .subMenu else {
// NOTE: pushViewController is called by init(rootViewController: UIViewController)
// so we must perform the normal super method in this case.
super.pushViewController(viewController, animated: animated)
return
}
let splitViewController = presentingViewController as? UISplitViewController
let tabBarController = presentingViewController as? UITabBarController
let potentialNavigationController = (splitViewController?.viewControllers.first ?? tabBarController?.selectedViewController) ?? presentingViewController
guard let navigationController = potentialNavigationController as? UINavigationController else {
print("SideMenu Warning: attempt to push a View Controller from \(String(describing: potentialNavigationController.self)) where its navigationController == nil. It must be embedded in a Navigation Controller for this to work.")
return
}
let activeDelegate = self.activeDelegate
foundDelegate = nil
// To avoid overlapping dismiss & pop/push calls, create a transaction block where the menu
// is dismissed after showing the appropriate screen
CATransaction.begin()
if sideMenuManager.menuDismissOnPush {
let animated = animated || sideMenuManager.menuAlwaysAnimate
CATransaction.setCompletionBlock( { () -> Void in
activeDelegate?.sideMenuDidDisappear?(menu: self, animated: animated)
if !animated {
self.transition.hideMenuStart().hideMenuComplete()
}
self.dismiss(animated: animated, completion: nil)
})
if animated {
let areAnimationsEnabled = UIView.areAnimationsEnabled
UIView.setAnimationsEnabled(true)
UIView.animate(withDuration: sideMenuManager.menuAnimationDismissDuration,
delay: 0,
usingSpringWithDamping: sideMenuManager.menuAnimationUsingSpringWithDamping,
initialSpringVelocity: sideMenuManager.menuAnimationInitialSpringVelocity,
options: sideMenuManager.menuAnimationOptions,
animations: {
activeDelegate?.sideMenuWillDisappear?(menu: self, animated: animated)
self.transition.hideMenuStart()
})
UIView.setAnimationsEnabled(areAnimationsEnabled)
}
}
if let lastViewController = navigationController.viewControllers.last, !sideMenuManager.menuAllowPushOfSameClassTwice && type(of: lastViewController) == type(of: viewController) {
CATransaction.commit()
return
}
switch sideMenuManager.menuPushStyle {
case .subMenu, .defaultBehavior: break // .subMenu handled earlier, .defaultBehavior falls through to end
case .popWhenPossible:
for subViewController in navigationController.viewControllers.reversed() {
if type(of: subViewController) == type(of: viewController) {
navigationController.popToViewController(subViewController, animated: animated)
CATransaction.commit()
return
}
}
case .preserve, .preserveAndHideBackButton:
var viewControllers = navigationController.viewControllers
let filtered = viewControllers.filter { preservedViewController in type(of: preservedViewController) == type(of: viewController) }
if let preservedViewController = filtered.last {
viewControllers = viewControllers.filter { subViewController in subViewController !== preservedViewController }
if sideMenuManager.menuPushStyle == .preserveAndHideBackButton {
preservedViewController.navigationItem.hidesBackButton = true
}
viewControllers.append(preservedViewController)
navigationController.setViewControllers(viewControllers, animated: animated)
CATransaction.commit()
return
}
if sideMenuManager.menuPushStyle == .preserveAndHideBackButton {
viewController.navigationItem.hidesBackButton = true
}
case .replace:
viewController.navigationItem.hidesBackButton = true
navigationController.setViewControllers([viewController], animated: animated)
CATransaction.commit()
return
}
navigationController.pushViewController(viewController, animated: animated)
CATransaction.commit()
}
}