SlideMenuController.swift 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051
  1. //
  2. // SlideMenuController.swift
  3. // Gasteizko Margolariak
  4. //
  5. // Created by Inigo Valentin on 2016-08-23
  6. // Copyright © 2016 Inigo Valentin. All rights reserved.
  7. //
  8. // Based on the work of Yuji Hato.
  9. //
  10. import Foundation
  11. import UIKit
  12. @objc public protocol SlideMenuControllerDelegate {
  13. optional func leftWillOpen()
  14. optional func leftDidOpen()
  15. optional func leftWillClose()
  16. optional func leftDidClose()
  17. optional func rightWillOpen()
  18. optional func rightDidOpen()
  19. optional func rightWillClose()
  20. optional func rightDidClose()
  21. }
  22. public struct SlideMenuOptions {
  23. public static var leftViewWidth: CGFloat = 270.0
  24. public static var leftBezelWidth: CGFloat? = 16.0
  25. public static var contentViewScale: CGFloat = 0.96
  26. public static var contentViewOpacity: CGFloat = 0.5
  27. public static var contentViewDrag: Bool = false
  28. public static var shadowOpacity: CGFloat = 0.0
  29. public static var shadowRadius: CGFloat = 0.0
  30. public static var shadowOffset: CGSize = CGSizeMake(0,0)
  31. public static var panFromBezel: Bool = true
  32. public static var animationDuration: CGFloat = 0.4
  33. public static var rightViewWidth: CGFloat = 270.0
  34. public static var rightBezelWidth: CGFloat? = 16.0
  35. public static var rightPanFromBezel: Bool = true
  36. public static var hideStatusBar: Bool = true
  37. public static var pointOfNoReturnWidth: CGFloat = 44.0
  38. public static var simultaneousGestureRecognizers: Bool = true
  39. public static var opacityViewBackgroundColor: UIColor = UIColor.blackColor()
  40. }
  41. public class SlideMenuController: UIViewController, UIGestureRecognizerDelegate {
  42. public enum SlideAction {
  43. case Open
  44. case Close
  45. }
  46. public enum TrackAction {
  47. case LeftTapOpen
  48. case LeftTapClose
  49. case LeftFlickOpen
  50. case LeftFlickClose
  51. case RightTapOpen
  52. case RightTapClose
  53. case RightFlickOpen
  54. case RightFlickClose
  55. }
  56. struct PanInfo {
  57. var action: SlideAction
  58. var shouldBounce: Bool
  59. var velocity: CGFloat
  60. }
  61. public weak var delegate: SlideMenuControllerDelegate?
  62. public var opacityView = UIView()
  63. public var mainContainerView = UIView()
  64. public var leftContainerView = UIView()
  65. public var rightContainerView = UIView()
  66. public var mainViewController: UIViewController?
  67. public var leftViewController: UIViewController?
  68. public var leftPanGesture: UIPanGestureRecognizer?
  69. public var leftTapGesture: UITapGestureRecognizer?
  70. public var rightViewController: UIViewController?
  71. public var rightPanGesture: UIPanGestureRecognizer?
  72. public var rightTapGesture: UITapGestureRecognizer?
  73. public required init?(coder aDecoder: NSCoder) {
  74. super.init(coder: aDecoder)
  75. }
  76. public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
  77. super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
  78. }
  79. public convenience init(mainViewController: UIViewController, leftMenuViewController: UIViewController) {
  80. self.init()
  81. self.mainViewController = mainViewController
  82. leftViewController = leftMenuViewController
  83. initView()
  84. }
  85. public convenience init(mainViewController: UIViewController, leftMenuViewController: UIViewController, rightMenuViewController: UIViewController) {
  86. self.init()
  87. self.mainViewController = mainViewController
  88. leftViewController = leftMenuViewController
  89. initView()
  90. }
  91. public override func awakeFromNib() {
  92. initView()
  93. }
  94. deinit { }
  95. public func initView() {
  96. mainContainerView = UIView(frame: view.bounds)
  97. mainContainerView.backgroundColor = UIColor.clearColor()
  98. mainContainerView.autoresizingMask = [.FlexibleHeight, .FlexibleWidth]
  99. view.insertSubview(mainContainerView, atIndex: 0)
  100. var opacityframe: CGRect = view.bounds
  101. let opacityOffset: CGFloat = 0
  102. opacityframe.origin.y = opacityframe.origin.y + opacityOffset
  103. opacityframe.size.height = opacityframe.size.height - opacityOffset
  104. opacityView = UIView(frame: opacityframe)
  105. opacityView.backgroundColor = SlideMenuOptions.opacityViewBackgroundColor
  106. opacityView.autoresizingMask = [UIViewAutoresizing.FlexibleHeight, UIViewAutoresizing.FlexibleWidth]
  107. opacityView.layer.opacity = 0.0
  108. view.insertSubview(opacityView, atIndex: 1)
  109. if leftViewController != nil {
  110. var leftFrame: CGRect = view.bounds
  111. leftFrame.size.width = SlideMenuOptions.leftViewWidth
  112. leftFrame.origin.x = leftMinOrigin();
  113. let leftOffset: CGFloat = 0
  114. leftFrame.origin.y = leftFrame.origin.y + leftOffset
  115. leftFrame.size.height = leftFrame.size.height - leftOffset
  116. leftContainerView = UIView(frame: leftFrame)
  117. leftContainerView.backgroundColor = UIColor.clearColor()
  118. leftContainerView.autoresizingMask = UIViewAutoresizing.FlexibleHeight
  119. view.insertSubview(leftContainerView, atIndex: 2)
  120. addLeftGestures()
  121. }
  122. }
  123. public override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
  124. super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
  125. mainContainerView.transform = CGAffineTransformMakeScale(1.0, 1.0)
  126. leftContainerView.hidden = true
  127. rightContainerView.hidden = true
  128. coordinator.animateAlongsideTransition(nil, completion: { (context: UIViewControllerTransitionCoordinatorContext!) -> Void in
  129. self.closeLeftNonAnimation()
  130. self.closeRightNonAnimation()
  131. self.leftContainerView.hidden = false
  132. self.rightContainerView.hidden = false
  133. if self.leftPanGesture != nil && self.leftPanGesture != nil {
  134. self.removeLeftGestures()
  135. self.addLeftGestures()
  136. }
  137. if self.rightPanGesture != nil && self.rightPanGesture != nil {
  138. self.removeRightGestures()
  139. self.addRightGestures()
  140. }
  141. })
  142. }
  143. public override func viewDidLoad() {
  144. super.viewDidLoad()
  145. edgesForExtendedLayout = UIRectEdge.None
  146. }
  147. public override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
  148. if let mainController = self.mainViewController{
  149. return mainController.supportedInterfaceOrientations()
  150. }
  151. return UIInterfaceOrientationMask.All
  152. }
  153. public override func shouldAutorotate() -> Bool {
  154. return mainViewController?.shouldAutorotate() ?? false
  155. }
  156. public override func viewWillLayoutSubviews() {
  157. // topLayoutGuideの値が確定するこのタイミングで各種ViewControllerをセットする
  158. setUpViewController(mainContainerView, targetViewController: mainViewController)
  159. setUpViewController(leftContainerView, targetViewController: leftViewController)
  160. setUpViewController(rightContainerView, targetViewController: rightViewController)
  161. }
  162. public override func openLeft() {
  163. guard let _ = leftViewController else { // If leftViewController is nil, then return
  164. return
  165. }
  166. self.delegate?.leftWillOpen?()
  167. setOpenWindowLevel()
  168. // for call viewWillAppear of leftViewController
  169. leftViewController?.beginAppearanceTransition(isLeftHidden(), animated: true)
  170. openLeftWithVelocity(0.0)
  171. track(.LeftTapOpen)
  172. }
  173. public override func openRight() {
  174. guard let _ = rightViewController else { // If rightViewController is nil, then return
  175. return
  176. }
  177. self.delegate?.rightWillOpen?()
  178. setOpenWindowLevel()
  179. rightViewController?.beginAppearanceTransition(isRightHidden(), animated: true)
  180. openRightWithVelocity(0.0)
  181. track(.RightTapOpen)
  182. }
  183. public override func closeLeft() {
  184. guard let _ = leftViewController else { // If leftViewController is nil, then return
  185. return
  186. }
  187. self.delegate?.leftWillClose?()
  188. leftViewController?.beginAppearanceTransition(isLeftHidden(), animated: true)
  189. closeLeftWithVelocity(0.0)
  190. setCloseWindowLevel()
  191. }
  192. public override func closeRight() {
  193. guard let _ = rightViewController else { // If rightViewController is nil, then return
  194. return
  195. }
  196. self.delegate?.rightWillClose?()
  197. rightViewController?.beginAppearanceTransition(isRightHidden(), animated: true)
  198. closeRightWithVelocity(0.0)
  199. setCloseWindowLevel()
  200. }
  201. public func addLeftGestures() {
  202. if (leftViewController != nil) {
  203. if leftPanGesture == nil {
  204. leftPanGesture = UIPanGestureRecognizer(target: self, action: #selector(self.handleLeftPanGesture(_:)))
  205. leftPanGesture!.delegate = self
  206. view.addGestureRecognizer(leftPanGesture!)
  207. }
  208. if leftTapGesture == nil {
  209. leftTapGesture = UITapGestureRecognizer(target: self, action: #selector(self.toggleLeft))
  210. leftTapGesture!.delegate = self
  211. view.addGestureRecognizer(leftTapGesture!)
  212. }
  213. }
  214. }
  215. public func addRightGestures() {
  216. if (rightViewController != nil) {
  217. if rightPanGesture == nil {
  218. rightPanGesture = UIPanGestureRecognizer(target: self, action: #selector(self.handleRightPanGesture(_:)))
  219. rightPanGesture!.delegate = self
  220. view.addGestureRecognizer(rightPanGesture!)
  221. }
  222. if rightTapGesture == nil {
  223. rightTapGesture = UITapGestureRecognizer(target: self, action: #selector(self.toggleRight))
  224. rightTapGesture!.delegate = self
  225. view.addGestureRecognizer(rightTapGesture!)
  226. }
  227. }
  228. }
  229. public func removeLeftGestures() {
  230. if leftPanGesture != nil {
  231. view.removeGestureRecognizer(leftPanGesture!)
  232. leftPanGesture = nil
  233. }
  234. if leftTapGesture != nil {
  235. view.removeGestureRecognizer(leftTapGesture!)
  236. leftTapGesture = nil
  237. }
  238. }
  239. public func removeRightGestures() {
  240. if rightPanGesture != nil {
  241. view.removeGestureRecognizer(rightPanGesture!)
  242. rightPanGesture = nil
  243. }
  244. if rightTapGesture != nil {
  245. view.removeGestureRecognizer(rightTapGesture!)
  246. rightTapGesture = nil
  247. }
  248. }
  249. public func isTagetViewController() -> Bool {
  250. // Function to determine the target ViewController
  251. // Please to override it if necessary
  252. return true
  253. }
  254. public func track(trackAction: TrackAction) {
  255. // function is for tracking
  256. // Please to override it if necessary
  257. }
  258. struct LeftPanState {
  259. static var frameAtStartOfPan: CGRect = CGRectZero
  260. static var startPointOfPan: CGPoint = CGPointZero
  261. static var wasOpenAtStartOfPan: Bool = false
  262. static var wasHiddenAtStartOfPan: Bool = false
  263. static var lastState : UIGestureRecognizerState = .Ended
  264. }
  265. func handleLeftPanGesture(panGesture: UIPanGestureRecognizer) {
  266. if !isTagetViewController() {
  267. return
  268. }
  269. if isRightOpen() {
  270. return
  271. }
  272. switch panGesture.state {
  273. case UIGestureRecognizerState.Began:
  274. if LeftPanState.lastState != .Ended && LeftPanState.lastState != .Cancelled && LeftPanState.lastState != .Failed {
  275. return
  276. }
  277. if isLeftHidden() {
  278. self.delegate?.leftWillOpen?()
  279. } else {
  280. self.delegate?.leftWillClose?()
  281. }
  282. LeftPanState.frameAtStartOfPan = leftContainerView.frame
  283. LeftPanState.startPointOfPan = panGesture.locationInView(view)
  284. LeftPanState.wasOpenAtStartOfPan = isLeftOpen()
  285. LeftPanState.wasHiddenAtStartOfPan = isLeftHidden()
  286. leftViewController?.beginAppearanceTransition(LeftPanState.wasHiddenAtStartOfPan, animated: true)
  287. addShadowToView(leftContainerView)
  288. setOpenWindowLevel()
  289. case UIGestureRecognizerState.Changed:
  290. if LeftPanState.lastState != .Began && LeftPanState.lastState != .Changed {
  291. return
  292. }
  293. let translation: CGPoint = panGesture.translationInView(panGesture.view!)
  294. leftContainerView.frame = applyLeftTranslation(translation, toFrame: LeftPanState.frameAtStartOfPan)
  295. applyLeftOpacity()
  296. applyLeftContentViewScale()
  297. case UIGestureRecognizerState.Ended, UIGestureRecognizerState.Cancelled:
  298. if LeftPanState.lastState != .Changed {
  299. setCloseWindowLevel()
  300. return
  301. }
  302. let velocity:CGPoint = panGesture.velocityInView(panGesture.view)
  303. let panInfo: PanInfo = panLeftResultInfoForVelocity(velocity)
  304. if panInfo.action == .Open {
  305. if !LeftPanState.wasHiddenAtStartOfPan {
  306. leftViewController?.beginAppearanceTransition(true, animated: true)
  307. }
  308. openLeftWithVelocity(panInfo.velocity)
  309. track(.LeftFlickOpen)
  310. } else {
  311. if LeftPanState.wasHiddenAtStartOfPan {
  312. leftViewController?.beginAppearanceTransition(false, animated: true)
  313. }
  314. closeLeftWithVelocity(panInfo.velocity)
  315. setCloseWindowLevel()
  316. track(.LeftFlickClose)
  317. }
  318. case UIGestureRecognizerState.Failed, UIGestureRecognizerState.Possible:
  319. break
  320. }
  321. LeftPanState.lastState = panGesture.state
  322. }
  323. struct RightPanState {
  324. static var frameAtStartOfPan: CGRect = CGRectZero
  325. static var startPointOfPan: CGPoint = CGPointZero
  326. static var wasOpenAtStartOfPan: Bool = false
  327. static var wasHiddenAtStartOfPan: Bool = false
  328. static var lastState : UIGestureRecognizerState = .Ended
  329. }
  330. func handleRightPanGesture(panGesture: UIPanGestureRecognizer) {
  331. if !isTagetViewController() {
  332. return
  333. }
  334. if isLeftOpen() {
  335. return
  336. }
  337. switch panGesture.state {
  338. case UIGestureRecognizerState.Began:
  339. if RightPanState.lastState != .Ended && RightPanState.lastState != .Cancelled && RightPanState.lastState != .Failed {
  340. return
  341. }
  342. if isRightHidden() {
  343. self.delegate?.rightWillOpen?()
  344. } else {
  345. self.delegate?.rightWillClose?()
  346. }
  347. RightPanState.frameAtStartOfPan = rightContainerView.frame
  348. RightPanState.startPointOfPan = panGesture.locationInView(view)
  349. RightPanState.wasOpenAtStartOfPan = isRightOpen()
  350. RightPanState.wasHiddenAtStartOfPan = isRightHidden()
  351. rightViewController?.beginAppearanceTransition(RightPanState.wasHiddenAtStartOfPan, animated: true)
  352. addShadowToView(rightContainerView)
  353. setOpenWindowLevel()
  354. case UIGestureRecognizerState.Changed:
  355. if RightPanState.lastState != .Began && RightPanState.lastState != .Changed {
  356. return
  357. }
  358. let translation: CGPoint = panGesture.translationInView(panGesture.view!)
  359. rightContainerView.frame = applyRightTranslation(translation, toFrame: RightPanState.frameAtStartOfPan)
  360. applyRightOpacity()
  361. applyRightContentViewScale()
  362. case UIGestureRecognizerState.Ended, UIGestureRecognizerState.Cancelled:
  363. if RightPanState.lastState != .Changed {
  364. setCloseWindowLevel()
  365. return
  366. }
  367. let velocity: CGPoint = panGesture.velocityInView(panGesture.view)
  368. let panInfo: PanInfo = panRightResultInfoForVelocity(velocity)
  369. if panInfo.action == .Open {
  370. if !RightPanState.wasHiddenAtStartOfPan {
  371. rightViewController?.beginAppearanceTransition(true, animated: true)
  372. }
  373. openRightWithVelocity(panInfo.velocity)
  374. track(.RightFlickOpen)
  375. } else {
  376. if RightPanState.wasHiddenAtStartOfPan {
  377. rightViewController?.beginAppearanceTransition(false, animated: true)
  378. }
  379. closeRightWithVelocity(panInfo.velocity)
  380. setCloseWindowLevel()
  381. track(.RightFlickClose)
  382. }
  383. case UIGestureRecognizerState.Failed, UIGestureRecognizerState.Possible:
  384. break
  385. }
  386. RightPanState.lastState = panGesture.state
  387. }
  388. public func openLeftWithVelocity(velocity: CGFloat) {
  389. let xOrigin: CGFloat = leftContainerView.frame.origin.x
  390. let finalXOrigin: CGFloat = 0.0
  391. var frame = leftContainerView.frame;
  392. frame.origin.x = finalXOrigin;
  393. var duration: NSTimeInterval = Double(SlideMenuOptions.animationDuration)
  394. if velocity != 0.0 {
  395. duration = Double(fabs(xOrigin - finalXOrigin) / velocity)
  396. duration = Double(fmax(0.1, fmin(1.0, duration)))
  397. }
  398. addShadowToView(leftContainerView)
  399. UIView.animateWithDuration(duration, delay: 0.0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { [weak self]() -> Void in
  400. if let strongSelf = self {
  401. strongSelf.leftContainerView.frame = frame
  402. strongSelf.opacityView.layer.opacity = Float(SlideMenuOptions.contentViewOpacity)
  403. SlideMenuOptions.contentViewDrag == true ? (strongSelf.mainContainerView.transform = CGAffineTransformMakeTranslation(SlideMenuOptions.leftViewWidth, 0)) : (strongSelf.mainContainerView.transform = CGAffineTransformMakeScale(SlideMenuOptions.contentViewScale, SlideMenuOptions.contentViewScale))
  404. }
  405. }) { [weak self](Bool) -> Void in
  406. if let strongSelf = self {
  407. strongSelf.disableContentInteraction()
  408. strongSelf.leftViewController?.endAppearanceTransition()
  409. strongSelf.delegate?.leftDidOpen?()
  410. }
  411. }
  412. }
  413. public func openRightWithVelocity(velocity: CGFloat) {
  414. let xOrigin: CGFloat = rightContainerView.frame.origin.x
  415. // CGFloat finalXOrigin = SlideMenuOptions.rightViewOverlapWidth;
  416. let finalXOrigin: CGFloat = CGRectGetWidth(view.bounds) - rightContainerView.frame.size.width
  417. var frame = rightContainerView.frame
  418. frame.origin.x = finalXOrigin
  419. var duration: NSTimeInterval = Double(SlideMenuOptions.animationDuration)
  420. if velocity != 0.0 {
  421. duration = Double(fabs(xOrigin - CGRectGetWidth(view.bounds)) / velocity)
  422. duration = Double(fmax(0.1, fmin(1.0, duration)))
  423. }
  424. addShadowToView(rightContainerView)
  425. UIView.animateWithDuration(duration, delay: 0.0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { [weak self]() -> Void in
  426. if let strongSelf = self {
  427. strongSelf.rightContainerView.frame = frame
  428. strongSelf.opacityView.layer.opacity = Float(SlideMenuOptions.contentViewOpacity)
  429. SlideMenuOptions.contentViewDrag == true ? (strongSelf.mainContainerView.transform = CGAffineTransformMakeTranslation(-SlideMenuOptions.rightViewWidth, 0)) : (strongSelf.mainContainerView.transform = CGAffineTransformMakeScale(SlideMenuOptions.contentViewScale, SlideMenuOptions.contentViewScale))
  430. }
  431. }) { [weak self](Bool) -> Void in
  432. if let strongSelf = self {
  433. strongSelf.disableContentInteraction()
  434. strongSelf.rightViewController?.endAppearanceTransition()
  435. strongSelf.delegate?.rightDidOpen?()
  436. }
  437. }
  438. }
  439. public func closeLeftWithVelocity(velocity: CGFloat) {
  440. let xOrigin: CGFloat = leftContainerView.frame.origin.x
  441. let finalXOrigin: CGFloat = leftMinOrigin()
  442. var frame: CGRect = leftContainerView.frame;
  443. frame.origin.x = finalXOrigin
  444. var duration: NSTimeInterval = Double(SlideMenuOptions.animationDuration)
  445. if velocity != 0.0 {
  446. duration = Double(fabs(xOrigin - finalXOrigin) / velocity)
  447. duration = Double(fmax(0.1, fmin(1.0, duration)))
  448. }
  449. UIView.animateWithDuration(duration, delay: 0.0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { [weak self]() -> Void in
  450. if let strongSelf = self {
  451. strongSelf.leftContainerView.frame = frame
  452. strongSelf.opacityView.layer.opacity = 0.0
  453. strongSelf.mainContainerView.transform = CGAffineTransformMakeScale(1.0, 1.0)
  454. }
  455. }) { [weak self](Bool) -> Void in
  456. if let strongSelf = self {
  457. strongSelf.removeShadow(strongSelf.leftContainerView)
  458. strongSelf.enableContentInteraction()
  459. strongSelf.leftViewController?.endAppearanceTransition()
  460. strongSelf.delegate?.leftDidClose?()
  461. }
  462. }
  463. }
  464. public func closeRightWithVelocity(velocity: CGFloat) {
  465. let xOrigin: CGFloat = rightContainerView.frame.origin.x
  466. let finalXOrigin: CGFloat = CGRectGetWidth(view.bounds)
  467. var frame: CGRect = rightContainerView.frame
  468. frame.origin.x = finalXOrigin
  469. var duration: NSTimeInterval = Double(SlideMenuOptions.animationDuration)
  470. if velocity != 0.0 {
  471. duration = Double(fabs(xOrigin - CGRectGetWidth(view.bounds)) / velocity)
  472. duration = Double(fmax(0.1, fmin(1.0, duration)))
  473. }
  474. UIView.animateWithDuration(duration, delay: 0.0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { [weak self]() -> Void in
  475. if let strongSelf = self {
  476. strongSelf.rightContainerView.frame = frame
  477. strongSelf.opacityView.layer.opacity = 0.0
  478. strongSelf.mainContainerView.transform = CGAffineTransformMakeScale(1.0, 1.0)
  479. }
  480. }) { [weak self](Bool) -> Void in
  481. if let strongSelf = self {
  482. strongSelf.removeShadow(strongSelf.rightContainerView)
  483. strongSelf.enableContentInteraction()
  484. strongSelf.rightViewController?.endAppearanceTransition()
  485. strongSelf.delegate?.rightDidClose?()
  486. }
  487. }
  488. }
  489. public override func toggleLeft() {
  490. if isLeftOpen() {
  491. closeLeft()
  492. setCloseWindowLevel()
  493. // Tracking of close tap is put in here. Because closeMenu is due to be call even when the menu tap.
  494. track(.LeftTapClose)
  495. } else {
  496. openLeft()
  497. }
  498. }
  499. public func isLeftOpen() -> Bool {
  500. return leftViewController != nil && leftContainerView.frame.origin.x == 0.0
  501. }
  502. public func isLeftHidden() -> Bool {
  503. return leftContainerView.frame.origin.x <= leftMinOrigin()
  504. }
  505. public override func toggleRight() {
  506. if isRightOpen() {
  507. closeRight()
  508. setCloseWindowLevel()
  509. // Tracking of close tap is put in here. Because closeMenu is due to be call even when the menu tap.
  510. track(.RightTapClose)
  511. } else {
  512. openRight()
  513. }
  514. }
  515. public func isRightOpen() -> Bool {
  516. return rightViewController != nil && rightContainerView.frame.origin.x == CGRectGetWidth(view.bounds) - rightContainerView.frame.size.width
  517. }
  518. public func isRightHidden() -> Bool {
  519. return rightContainerView.frame.origin.x >= CGRectGetWidth(view.bounds)
  520. }
  521. public func changeMainViewController(mainViewController: UIViewController, close: Bool) {
  522. removeViewController(self.mainViewController)
  523. self.mainViewController = mainViewController
  524. setUpViewController(mainContainerView, targetViewController: mainViewController)
  525. if (close) {
  526. closeLeft()
  527. closeRight()
  528. }
  529. }
  530. public func changeLeftViewWidth(width: CGFloat) {
  531. SlideMenuOptions.leftViewWidth = width;
  532. var leftFrame: CGRect = view.bounds
  533. leftFrame.size.width = width
  534. leftFrame.origin.x = leftMinOrigin();
  535. let leftOffset: CGFloat = 0
  536. leftFrame.origin.y = leftFrame.origin.y + leftOffset
  537. leftFrame.size.height = leftFrame.size.height - leftOffset
  538. leftContainerView.frame = leftFrame;
  539. }
  540. public func changeRightViewWidth(width: CGFloat) {
  541. SlideMenuOptions.rightBezelWidth = width;
  542. var rightFrame: CGRect = view.bounds
  543. rightFrame.size.width = width
  544. rightFrame.origin.x = rightMinOrigin()
  545. let rightOffset: CGFloat = 0
  546. rightFrame.origin.y = rightFrame.origin.y + rightOffset;
  547. rightFrame.size.height = rightFrame.size.height - rightOffset
  548. rightContainerView.frame = rightFrame;
  549. }
  550. public func changeLeftViewController(leftViewController: UIViewController, closeLeft:Bool) {
  551. removeViewController(self.leftViewController)
  552. self.leftViewController = leftViewController
  553. setUpViewController(leftContainerView, targetViewController: leftViewController)
  554. if closeLeft {
  555. self.closeLeft()
  556. }
  557. }
  558. public func changeRightViewController(rightViewController: UIViewController, closeRight:Bool) {
  559. removeViewController(self.rightViewController)
  560. self.rightViewController = rightViewController;
  561. setUpViewController(rightContainerView, targetViewController: rightViewController)
  562. if closeRight {
  563. self.closeRight()
  564. }
  565. }
  566. private func leftMinOrigin() -> CGFloat {
  567. return -SlideMenuOptions.leftViewWidth
  568. }
  569. private func rightMinOrigin() -> CGFloat {
  570. return CGRectGetWidth(view.bounds)
  571. }
  572. private func panLeftResultInfoForVelocity(velocity: CGPoint) -> PanInfo {
  573. let thresholdVelocity: CGFloat = 1000.0
  574. let pointOfNoReturn: CGFloat = CGFloat(floor(leftMinOrigin())) + SlideMenuOptions.pointOfNoReturnWidth
  575. let leftOrigin: CGFloat = leftContainerView.frame.origin.x
  576. var panInfo: PanInfo = PanInfo(action: .Close, shouldBounce: false, velocity: 0.0)
  577. panInfo.action = leftOrigin <= pointOfNoReturn ? .Close : .Open;
  578. if velocity.x >= thresholdVelocity {
  579. panInfo.action = .Open
  580. panInfo.velocity = velocity.x
  581. } else if velocity.x <= (-1.0 * thresholdVelocity) {
  582. panInfo.action = .Close
  583. panInfo.velocity = velocity.x
  584. }
  585. return panInfo
  586. }
  587. private func panRightResultInfoForVelocity(velocity: CGPoint) -> PanInfo {
  588. let thresholdVelocity: CGFloat = -1000.0
  589. let pointOfNoReturn: CGFloat = CGFloat(floor(CGRectGetWidth(view.bounds)) - SlideMenuOptions.pointOfNoReturnWidth)
  590. let rightOrigin: CGFloat = rightContainerView.frame.origin.x
  591. var panInfo: PanInfo = PanInfo(action: .Close, shouldBounce: false, velocity: 0.0)
  592. panInfo.action = rightOrigin >= pointOfNoReturn ? .Close : .Open
  593. if velocity.x <= thresholdVelocity {
  594. panInfo.action = .Open
  595. panInfo.velocity = velocity.x
  596. } else if (velocity.x >= (-1.0 * thresholdVelocity)) {
  597. panInfo.action = .Close
  598. panInfo.velocity = velocity.x
  599. }
  600. return panInfo
  601. }
  602. private func applyLeftTranslation(translation: CGPoint, toFrame:CGRect) -> CGRect {
  603. var newOrigin: CGFloat = toFrame.origin.x
  604. newOrigin += translation.x
  605. let minOrigin: CGFloat = leftMinOrigin()
  606. let maxOrigin: CGFloat = 0.0
  607. var newFrame: CGRect = toFrame
  608. if newOrigin < minOrigin {
  609. newOrigin = minOrigin
  610. } else if newOrigin > maxOrigin {
  611. newOrigin = maxOrigin
  612. }
  613. newFrame.origin.x = newOrigin
  614. return newFrame
  615. }
  616. private func applyRightTranslation(translation: CGPoint, toFrame: CGRect) -> CGRect {
  617. var newOrigin: CGFloat = toFrame.origin.x
  618. newOrigin += translation.x
  619. let minOrigin: CGFloat = rightMinOrigin()
  620. let maxOrigin: CGFloat = rightMinOrigin() - rightContainerView.frame.size.width
  621. var newFrame: CGRect = toFrame
  622. if newOrigin > minOrigin {
  623. newOrigin = minOrigin
  624. } else if newOrigin < maxOrigin {
  625. newOrigin = maxOrigin
  626. }
  627. newFrame.origin.x = newOrigin
  628. return newFrame
  629. }
  630. private func getOpenedLeftRatio() -> CGFloat {
  631. let width: CGFloat = leftContainerView.frame.size.width
  632. let currentPosition: CGFloat = leftContainerView.frame.origin.x - leftMinOrigin()
  633. return currentPosition / width
  634. }
  635. private func getOpenedRightRatio() -> CGFloat {
  636. let width: CGFloat = rightContainerView.frame.size.width
  637. let currentPosition: CGFloat = rightContainerView.frame.origin.x
  638. return -(currentPosition - CGRectGetWidth(view.bounds)) / width
  639. }
  640. private func applyLeftOpacity() {
  641. let openedLeftRatio: CGFloat = getOpenedLeftRatio()
  642. let opacity: CGFloat = SlideMenuOptions.contentViewOpacity * openedLeftRatio
  643. opacityView.layer.opacity = Float(opacity)
  644. }
  645. private func applyRightOpacity() {
  646. let openedRightRatio: CGFloat = getOpenedRightRatio()
  647. let opacity: CGFloat = SlideMenuOptions.contentViewOpacity * openedRightRatio
  648. opacityView.layer.opacity = Float(opacity)
  649. }
  650. private func applyLeftContentViewScale() {
  651. let openedLeftRatio: CGFloat = getOpenedLeftRatio()
  652. let scale: CGFloat = 1.0 - ((1.0 - SlideMenuOptions.contentViewScale) * openedLeftRatio);
  653. let drag: CGFloat = SlideMenuOptions.leftViewWidth + leftContainerView.frame.origin.x
  654. SlideMenuOptions.contentViewDrag == true ? (mainContainerView.transform = CGAffineTransformMakeTranslation(drag, 0)) : (mainContainerView.transform = CGAffineTransformMakeScale(scale, scale))
  655. }
  656. private func applyRightContentViewScale() {
  657. let openedRightRatio: CGFloat = getOpenedRightRatio()
  658. let scale: CGFloat = 1.0 - ((1.0 - SlideMenuOptions.contentViewScale) * openedRightRatio)
  659. let drag: CGFloat = rightContainerView.frame.origin.x - mainContainerView.frame.size.width
  660. SlideMenuOptions.contentViewDrag == true ? (mainContainerView.transform = CGAffineTransformMakeTranslation(drag, 0)) : (mainContainerView.transform = CGAffineTransformMakeScale(scale, scale))
  661. }
  662. private func addShadowToView(targetContainerView: UIView) {
  663. targetContainerView.layer.masksToBounds = false
  664. targetContainerView.layer.shadowOffset = SlideMenuOptions.shadowOffset
  665. targetContainerView.layer.shadowOpacity = Float(SlideMenuOptions.shadowOpacity)
  666. targetContainerView.layer.shadowRadius = SlideMenuOptions.shadowRadius
  667. targetContainerView.layer.shadowPath = UIBezierPath(rect: targetContainerView.bounds).CGPath
  668. }
  669. private func removeShadow(targetContainerView: UIView) {
  670. targetContainerView.layer.masksToBounds = true
  671. mainContainerView.layer.opacity = 1.0
  672. }
  673. private func removeContentOpacity() {
  674. opacityView.layer.opacity = 0.0
  675. }
  676. private func addContentOpacity() {
  677. opacityView.layer.opacity = Float(SlideMenuOptions.contentViewOpacity)
  678. }
  679. private func disableContentInteraction() {
  680. mainContainerView.userInteractionEnabled = false
  681. }
  682. private func enableContentInteraction() {
  683. mainContainerView.userInteractionEnabled = true
  684. }
  685. private func setOpenWindowLevel() {
  686. if (SlideMenuOptions.hideStatusBar) {
  687. dispatch_async(dispatch_get_main_queue(), {
  688. if let window = UIApplication.sharedApplication().keyWindow {
  689. window.windowLevel = UIWindowLevelStatusBar + 1
  690. }
  691. })
  692. }
  693. }
  694. private func setCloseWindowLevel() {
  695. if (SlideMenuOptions.hideStatusBar) {
  696. dispatch_async(dispatch_get_main_queue(), {
  697. if let window = UIApplication.sharedApplication().keyWindow {
  698. window.windowLevel = UIWindowLevelNormal
  699. }
  700. })
  701. }
  702. }
  703. private func setUpViewController(targetView: UIView, targetViewController: UIViewController?) {
  704. if let viewController = targetViewController {
  705. addChildViewController(viewController)
  706. viewController.view.frame = targetView.bounds
  707. targetView.addSubview(viewController.view)
  708. viewController.didMoveToParentViewController(self)
  709. }
  710. }
  711. private func removeViewController(viewController: UIViewController?) {
  712. if let _viewController = viewController {
  713. _viewController.view.layer.removeAllAnimations()
  714. _viewController.willMoveToParentViewController(nil)
  715. _viewController.view.removeFromSuperview()
  716. _viewController.removeFromParentViewController()
  717. }
  718. }
  719. public func closeLeftNonAnimation(){
  720. setCloseWindowLevel()
  721. let finalXOrigin: CGFloat = leftMinOrigin()
  722. var frame: CGRect = leftContainerView.frame;
  723. frame.origin.x = finalXOrigin
  724. leftContainerView.frame = frame
  725. opacityView.layer.opacity = 0.0
  726. mainContainerView.transform = CGAffineTransformMakeScale(1.0, 1.0)
  727. removeShadow(leftContainerView)
  728. enableContentInteraction()
  729. }
  730. public func closeRightNonAnimation(){
  731. setCloseWindowLevel()
  732. let finalXOrigin: CGFloat = CGRectGetWidth(view.bounds)
  733. var frame: CGRect = rightContainerView.frame
  734. frame.origin.x = finalXOrigin
  735. rightContainerView.frame = frame
  736. opacityView.layer.opacity = 0.0
  737. mainContainerView.transform = CGAffineTransformMakeScale(1.0, 1.0)
  738. removeShadow(rightContainerView)
  739. enableContentInteraction()
  740. }
  741. // MARK: UIGestureRecognizerDelegate
  742. public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
  743. let point: CGPoint = touch.locationInView(view)
  744. if gestureRecognizer == leftPanGesture {
  745. return slideLeftForGestureRecognizer(gestureRecognizer, point: point)
  746. } else if gestureRecognizer == rightPanGesture {
  747. return slideRightViewForGestureRecognizer(gestureRecognizer, withTouchPoint: point)
  748. } else if gestureRecognizer == leftTapGesture {
  749. return isLeftOpen() && !isPointContainedWithinLeftRect(point)
  750. } else if gestureRecognizer == rightTapGesture {
  751. return isRightOpen() && !isPointContainedWithinRightRect(point)
  752. }
  753. return true
  754. }
  755. // returning true here helps if the main view is fullwidth with a scrollview
  756. public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
  757. return SlideMenuOptions.simultaneousGestureRecognizers
  758. }
  759. private func slideLeftForGestureRecognizer( gesture: UIGestureRecognizer, point:CGPoint) -> Bool{
  760. return isLeftOpen() || SlideMenuOptions.panFromBezel && isLeftPointContainedWithinBezelRect(point)
  761. }
  762. private func isLeftPointContainedWithinBezelRect(point: CGPoint) -> Bool{
  763. if let bezelWidth = SlideMenuOptions.leftBezelWidth {
  764. var leftBezelRect: CGRect = CGRectZero
  765. var tempRect: CGRect = CGRectZero
  766. CGRectDivide(view.bounds, &leftBezelRect, &tempRect, bezelWidth, CGRectEdge.MinXEdge)
  767. return CGRectContainsPoint(leftBezelRect, point)
  768. } else {
  769. return true
  770. }
  771. }
  772. private func isPointContainedWithinLeftRect(point: CGPoint) -> Bool {
  773. return CGRectContainsPoint(leftContainerView.frame, point)
  774. }
  775. private func slideRightViewForGestureRecognizer(gesture: UIGestureRecognizer, withTouchPoint point: CGPoint) -> Bool {
  776. return isRightOpen() || SlideMenuOptions.rightPanFromBezel && isRightPointContainedWithinBezelRect(point)
  777. }
  778. private func isRightPointContainedWithinBezelRect(point: CGPoint) -> Bool {
  779. if let rightBezelWidth = SlideMenuOptions.rightBezelWidth {
  780. var rightBezelRect: CGRect = CGRectZero
  781. var tempRect: CGRect = CGRectZero
  782. let bezelWidth: CGFloat = CGRectGetWidth(view.bounds) - rightBezelWidth
  783. CGRectDivide(view.bounds, &tempRect, &rightBezelRect, bezelWidth, CGRectEdge.MinXEdge)
  784. return CGRectContainsPoint(rightBezelRect, point)
  785. } else {
  786. return true
  787. }
  788. }
  789. private func isPointContainedWithinRightRect(point: CGPoint) -> Bool {
  790. return CGRectContainsPoint(rightContainerView.frame, point)
  791. }
  792. }
  793. extension UIViewController {
  794. public func slideMenuController() -> SlideMenuController? {
  795. var viewController: UIViewController? = self
  796. while viewController != nil {
  797. if viewController is SlideMenuController {
  798. return viewController as? SlideMenuController
  799. }
  800. viewController = viewController?.parentViewController
  801. }
  802. return nil;
  803. }
  804. public func addLeftBarButtonWithImage(buttonImage: UIImage) {
  805. let leftButton: UIBarButtonItem = UIBarButtonItem(image: buttonImage, style: UIBarButtonItemStyle.Plain, target: self, action: #selector(self.toggleLeft))
  806. navigationItem.leftBarButtonItem = leftButton;
  807. }
  808. public func addRightBarButtonWithImage(buttonImage: UIImage) {
  809. let rightButton: UIBarButtonItem = UIBarButtonItem(image: buttonImage, style: UIBarButtonItemStyle.Plain, target: self, action: #selector(self.toggleRight))
  810. navigationItem.rightBarButtonItem = rightButton;
  811. }
  812. public func toggleLeft() {
  813. slideMenuController()?.toggleLeft()
  814. }
  815. public func toggleRight() {
  816. slideMenuController()?.toggleRight()
  817. }
  818. public func openLeft() {
  819. slideMenuController()?.openLeft()
  820. }
  821. public func openRight() {
  822. slideMenuController()?.openRight() }
  823. public func closeLeft() {
  824. slideMenuController()?.closeLeft()
  825. }
  826. public func closeRight() {
  827. slideMenuController()?.closeRight()
  828. }
  829. // Please specify if you want menu gesuture give priority to than targetScrollView
  830. public func addPriorityToMenuGesuture(targetScrollView: UIScrollView) {
  831. guard let slideController = slideMenuController(), let recognizers = slideController.view.gestureRecognizers else {
  832. return
  833. }
  834. for recognizer in recognizers where recognizer is UIPanGestureRecognizer {
  835. targetScrollView.panGestureRecognizer.requireGestureRecognizerToFail(recognizer)
  836. }
  837. }
  838. }