230 lines
8.1 KiB
Swift
230 lines
8.1 KiB
Swift
//
|
|
// SideMenuManager.swift
|
|
//
|
|
// Created by Jon Kent on 12/6/15.
|
|
// Copyright © 2015 Jon Kent. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
|
|
@objcMembers
|
|
public class SideMenuManager: NSObject {
|
|
|
|
final private class SideMenuPanGestureRecognizer: UIPanGestureRecognizer {}
|
|
final private class SideMenuScreenEdgeGestureRecognizer: UIScreenEdgePanGestureRecognizer {}
|
|
|
|
@objc public enum PresentDirection: Int { case
|
|
left = 1,
|
|
right = 0
|
|
|
|
init(leftSide: Bool) {
|
|
self.init(rawValue: leftSide ? 1 : 0)!
|
|
}
|
|
|
|
var edge: UIRectEdge {
|
|
switch self {
|
|
case .left: return .left
|
|
case .right: return .right
|
|
}
|
|
}
|
|
|
|
var name: String {
|
|
switch self {
|
|
case .left: return "leftMenuNavigationController"
|
|
case .right: return "rightMenuNavigationController"
|
|
}
|
|
}
|
|
}
|
|
|
|
private var _leftMenu: Protected<Menu?> = Protected(nil) { SideMenuManager.setMenu(fromMenu: $0, toMenu: $1) }
|
|
private var _rightMenu: Protected<Menu?> = Protected(nil) { SideMenuManager.setMenu(fromMenu: $0, toMenu: $1) }
|
|
|
|
private var switching: Bool = false
|
|
|
|
/// Default instance of SideMenuManager.
|
|
public static let `default` = SideMenuManager()
|
|
|
|
/// Default instance of SideMenuManager (objective-C).
|
|
public class var defaultManager: SideMenuManager {
|
|
return SideMenuManager.default
|
|
}
|
|
|
|
/// The left menu.
|
|
open var leftMenuNavigationController: SideMenuNavigationController? {
|
|
get {
|
|
if _leftMenu.value?.isHidden == true {
|
|
_leftMenu.value?.leftSide = true
|
|
}
|
|
return _leftMenu.value
|
|
}
|
|
set(menu) { _leftMenu.value = menu }
|
|
}
|
|
|
|
/// The right menu.
|
|
open var rightMenuNavigationController: SideMenuNavigationController? {
|
|
get {
|
|
if _rightMenu.value?.isHidden == true {
|
|
_rightMenu.value?.leftSide = false
|
|
}
|
|
return _rightMenu.value
|
|
}
|
|
set(menu) { _rightMenu.value = menu }
|
|
}
|
|
|
|
/**
|
|
Adds screen edge gestures for both left and right sides to a view to present a menu.
|
|
|
|
- Parameter toView: The view to add gestures to.
|
|
|
|
- Returns: The array of screen edge gestures added to `toView`.
|
|
*/
|
|
@discardableResult public func addScreenEdgePanGesturesToPresent(toView view: UIView) -> [UIScreenEdgePanGestureRecognizer] {
|
|
return [
|
|
addScreenEdgePanGesturesToPresent(toView: view, forMenu: .left),
|
|
addScreenEdgePanGesturesToPresent(toView: view, forMenu: .right)
|
|
]
|
|
}
|
|
|
|
/**
|
|
Adds screen edge gestures to a view to present a menu.
|
|
|
|
- Parameter toView: The view to add gestures to.
|
|
- Parameter forMenu: The menu (left or right) you want to add a gesture for.
|
|
|
|
- Returns: The screen edge gestures added to `toView`.
|
|
*/
|
|
@discardableResult public func addScreenEdgePanGesturesToPresent(toView view: UIView, forMenu side: PresentDirection) -> UIScreenEdgePanGestureRecognizer {
|
|
if menu(forSide: side) == nil {
|
|
let methodName = #function // "addScreenEdgePanGesturesToPresent"
|
|
let suggestedMethodName = "addScreenEdgePanGesturesToPresent(toView:forMenu:))"
|
|
Print.warning(.screenGestureAdded, arguments: methodName, side.name, suggestedMethodName)
|
|
}
|
|
return self.addScreenEdgeGesture(to: view, edge: side.edge)
|
|
}
|
|
|
|
/**
|
|
Adds a pan edge gesture to a view to present menus.
|
|
|
|
- Parameter toView: The view to add a pan gesture to.
|
|
|
|
- Returns: The pan gesture added to `toView`.
|
|
*/
|
|
@discardableResult public func addPanGestureToPresent(toView view: UIView) -> UIPanGestureRecognizer {
|
|
if leftMenuNavigationController ?? rightMenuNavigationController == nil {
|
|
Print.warning(.panGestureAdded, arguments: #function, PresentDirection.left.name, PresentDirection.right.name, required: true)
|
|
}
|
|
|
|
return addPresentPanGesture(to: view)
|
|
}
|
|
}
|
|
|
|
internal extension SideMenuManager {
|
|
|
|
func setMenu(_ menu: Menu?, forLeftSide leftSide: Bool) {
|
|
switch leftSide {
|
|
case true: leftMenuNavigationController = menu
|
|
case false: rightMenuNavigationController = menu
|
|
}
|
|
}
|
|
|
|
private class func setMenu(fromMenu: Menu?, toMenu: Menu?) -> Menu? {
|
|
if fromMenu?.isHidden == false {
|
|
Print.warning(.menuInUse, arguments: PresentDirection.left.name, required: true)
|
|
return fromMenu
|
|
}
|
|
return toMenu
|
|
}
|
|
}
|
|
|
|
private extension SideMenuManager {
|
|
|
|
@objc func handlePresentMenuScreenEdge(_ gesture: UIScreenEdgePanGestureRecognizer) {
|
|
handleMenuPan(gesture)
|
|
}
|
|
|
|
@objc func handlePresentMenuPan(_ gesture: UIPanGestureRecognizer) {
|
|
handleMenuPan(gesture)
|
|
}
|
|
|
|
func handleMenuPan(_ gesture: UIPanGestureRecognizer) {
|
|
if let activeMenu = activeMenu {
|
|
let width = activeMenu.menuWidth
|
|
let distance = gesture.xTranslation / width
|
|
switch (gesture.state) {
|
|
case .changed:
|
|
if gesture.canSwitch {
|
|
switching = (distance > 0 && !activeMenu.leftSide) || (distance < 0 && activeMenu.leftSide)
|
|
if switching {
|
|
activeMenu.cancelMenuPan(gesture)
|
|
return
|
|
}
|
|
}
|
|
default:
|
|
switching = false
|
|
}
|
|
|
|
} else {
|
|
let leftSide: Bool
|
|
if let gesture = gesture as? UIScreenEdgePanGestureRecognizer {
|
|
leftSide = gesture.edges.contains(.left)
|
|
} else {
|
|
// not sure which way the user is swiping yet, so do nothing
|
|
if gesture.xTranslation == 0 { return }
|
|
leftSide = gesture.xTranslation > 0
|
|
}
|
|
|
|
guard let menu = menu(forLeftSide: leftSide) else { return }
|
|
menu.present(from: topMostViewController, interactively: true)
|
|
}
|
|
|
|
activeMenu?.handleMenuPan(gesture, true)
|
|
}
|
|
|
|
var activeMenu: Menu? {
|
|
if leftMenuNavigationController?.isHidden == false { return leftMenuNavigationController }
|
|
if rightMenuNavigationController?.isHidden == false { return rightMenuNavigationController }
|
|
return nil
|
|
}
|
|
|
|
func menu(forSide: PresentDirection) -> Menu? {
|
|
switch forSide {
|
|
case .left: return leftMenuNavigationController
|
|
case .right: return rightMenuNavigationController
|
|
}
|
|
}
|
|
|
|
func menu(forLeftSide leftSide: Bool) -> Menu? {
|
|
return menu(forSide: leftSide ? .left : .right)
|
|
}
|
|
|
|
func addScreenEdgeGesture(to view: UIView, edge: UIRectEdge) -> UIScreenEdgePanGestureRecognizer {
|
|
if let screenEdgeGestureRecognizer = view.gestureRecognizers?.first(where: { $0 is SideMenuScreenEdgeGestureRecognizer }) as? SideMenuScreenEdgeGestureRecognizer,
|
|
screenEdgeGestureRecognizer.edges == edge {
|
|
screenEdgeGestureRecognizer.remove()
|
|
}
|
|
return SideMenuScreenEdgeGestureRecognizer(addTo: view, target: self, action: #selector(handlePresentMenuScreenEdge(_:))).with {
|
|
$0.edges = edge
|
|
}
|
|
}
|
|
|
|
@discardableResult func addPresentPanGesture(to view: UIView) -> UIPanGestureRecognizer {
|
|
if let panGestureRecognizer = view.gestureRecognizers?.first(where: { $0 is SideMenuPanGestureRecognizer }) as? SideMenuPanGestureRecognizer {
|
|
return panGestureRecognizer
|
|
}
|
|
return SideMenuPanGestureRecognizer(addTo: view, target: self, action: #selector(handlePresentMenuPan(_:)))
|
|
}
|
|
|
|
var topMostViewController: UIViewController? {
|
|
return UIApplication.shared.keyWindow?.rootViewController?.topMostViewController
|
|
}
|
|
}
|
|
|
|
extension SideMenuManager: SideMenuNavigationControllerTransitionDelegate {
|
|
|
|
internal func sideMenuTransitionDidDismiss(menu: Menu) {
|
|
defer { switching = false }
|
|
guard switching, let switchToMenu = self.menu(forLeftSide: !menu.leftSide) else { return }
|
|
switchToMenu.present(from: topMostViewController, interactively: true)
|
|
}
|
|
}
|