// // 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 = Protected(nil) { SideMenuManager.setMenu(fromMenu: $0, toMenu: $1) } private var _rightMenu: Protected = 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) } }