Compare commits

...

44 Commits

Author SHA1 Message Date
jonkykong 4aa5ef2dcb Reverting README for Github. 2016-07-19 12:23:45 -07:00
jonkykong 347f476296 Updated README and pod spec. 2016-07-19 12:20:30 -07:00
jonkykong 4828c02cc8 Formatting. 2016-07-19 12:17:37 -07:00
Alexander Kukla 3863fffb7e Fixed crashes with force unwrap values when application returns to foreground 2016-07-18 15:16:19 +03:00
jonkykong a54bb35fde Reversing README for github. 2016-07-14 16:13:55 -07:00
jonkykong 129fd45f5e Merge tag '1.1.8'
* tag '1.1.8':
  Updated pod spec and README for cocoa pods.
  Fix to prevent side menu from responding to user interaction while being animated in or out.
  fix #44
2016-07-14 16:12:56 -07:00
jonkykong cb6b297068 Updated pod spec and README for cocoa pods. 2016-07-14 16:02:53 -07:00
jonkykong 49b5f0c4ab Merge branch 'pr/47' into 1.1.8
* pr/47:
  fix #44
2016-07-14 15:57:10 -07:00
jonkykong 7e1b32733a Fix to prevent side menu from responding to user interaction while being animated in or out. 2016-07-14 15:53:49 -07:00
Ngo Quoc Thang b7a769db31 fix #44 2016-07-13 10:25:01 +03:00
jonkykong a6464d3efa Merge tag '1.1.7'
* tag '1.1.7':
  Removed unneeded declaration.
  Updated pod spec and README for cocoa pods.
  - Refactoring to simplify SideMenuManager logic. - Fix for crashes by preventing side menus from being modified while actively presented.
2016-05-14 01:49:47 -07:00
jonkykong 1e6f192e28 Removed unneeded declaration. 2016-05-14 01:42:19 -07:00
jonkykong 771047f506 Updated pod spec and README for cocoa pods. 2016-05-14 01:37:08 -07:00
jonkykong c8ede5cd76 - Refactoring to simplify SideMenuManager logic.
- Fix for crashes by preventing side menus from being modified while actively presented.
2016-05-14 01:33:17 -07:00
jonkykong 335130ff7a Reversing README for github. 2016-05-02 16:57:04 -07:00
jonkykong 18fdc7f00e Merge tag '1.1.6'
* tag '1.1.6':
  Updates to README and podspec.
2016-05-02 16:56:42 -07:00
jonkykong e70f37f7b4 Updates to README and podspec. 2016-05-02 16:46:23 -07:00
jonkykong 21e873cc68 Logic and scope tweak. 2016-05-02 16:41:03 -07:00
Igor Savynskyi d81eb6277d variable marker corrections 2016-05-02 21:06:30 +03:00
Igor Savynskyi 2f6a1b8e6c code impruvements in solving issue with side menu wrong size 2016-05-02 15:23:42 +03:00
Ihor Savynskyi 7d766813fd fixing issue with view sizes for specific multitasking case
when running app with side menu on iPad device in Split View mode - it
takes wrong size for main view controller and side menu table view
controller (it is caused by taking sizes from UIScreen not from
UIApplication)
2016-04-27 16:40:34 +03:00
Jon Kent 78fad19e6f Update README.md 2016-04-23 22:55:54 -07:00
Jon Kent e91b9be969 Update README.md 2016-04-23 22:51:12 -07:00
Jon Kent 02252bc035 Update README.md 2016-04-22 15:00:39 -07:00
Jon Kent 4a658269a6 Merge pull request #15 from jonkykong/1.1.5
Reverse README for github.
2016-04-12 10:48:20 -07:00
jonkykong b5342a1c7c Merge tag '1.1.5'
* tag '1.1.5':
  Updated README and pod spec for cocoa pods.
  Added documentation comments for public methods.
  Animation support for parallax.
2016-04-12 10:46:10 -07:00
jonkykong bd2dc72a37 Reverse README for github. 2016-04-12 10:45:58 -07:00
jonkykong 58a1e19737 Updated README and pod spec for cocoa pods. 2016-04-12 10:43:00 -07:00
jonkykong 046c09765f Added documentation comments for public methods. 2016-04-12 10:39:17 -07:00
jonkykong dbeb741505 Animation support for parallax. 2016-04-12 10:37:43 -07:00
Jon Kent 2663b967d7 Update README.md 2016-04-08 14:17:40 -07:00
jonkykong 7a601b78b7 Reverse pod spec for github. 2016-04-08 01:13:50 -07:00
jonkykong 356f7c16e2 Merge tag '1.1.4'
* tag '1.1.4':
  One more logic tweak for animation smoothness.
2016-04-08 01:13:04 -07:00
jonkykong e512ae54be One more logic tweak for animation smoothness. 2016-04-08 01:07:07 -07:00
jonkykong 2029af7894 Updated pod spec version and README for cocoa pods. 2016-04-08 00:50:29 -07:00
jonkykong 0be7f0bc31 Bug workaround for iOS8 animations weirdly resetting. 2016-04-08 00:49:01 -07:00
Jon Kent d4732fbae5 Merge pull request #11 from jonkykong/1.1.2
Changed README back for github.
2016-04-07 18:02:20 -07:00
jonkykong 07f6dff8e8 Changed README back for github. 2016-04-07 17:58:23 -07:00
jonkykong a195757c1d Merge tag '1.1.2'
* tag '1.1.2':
  Removed unnecessary let.
2016-04-07 17:41:46 -07:00
jonkykong 68648ef3f1 Removed unnecessary let. 2016-04-07 17:40:30 -07:00
jonkykong fec44d0cc2 Updated README for cocoa pods. 2016-04-07 17:37:48 -07:00
jonkykong 4da251f6ea Upped pod spec. 2016-04-07 17:37:11 -07:00
jonkykong ddbc431d45 - Fix for edge gestures not firing on iOS 8.
- Improved logic around switching menus from rapid pans that can cause race condition crashes.
2016-04-07 17:37:02 -07:00
jonkykong 405a3ea56d Revert README back for github. 2016-04-06 00:07:29 -07:00
5 changed files with 372 additions and 248 deletions
+145 -75
View File
@@ -24,27 +24,70 @@ public class SideMenuManager {
case ViewSlideInOut
case MenuDissolveIn
}
private static var originalLeftMenuBackgroundColor: UIColor?
private static var originalRightMenuBackgroundColor: UIColor?
public static var menuPresentMode:MenuPresentMode = .ViewSlideOut
// Bounds which has been allocated for the app on the whole device screen
internal static var appScreenRect: CGRect {
let appWindowRect = UIApplication.sharedApplication().keyWindow?.bounds ?? UIWindow().bounds
return appWindowRect
}
/**
The presentation mode of the menu.
There are four modes in MenuPresentMode:
- MenuSlideIn: Menu slides in over of the existing view.
- ViewSlideOut: The existing view slides out to reveal the menu.
- ViewSlideInOut: The existing view slides out while the menu slides in.
- MenuDissolveIn: The menu dissolves in over the existing view controller.
*/
public static var menuPresentMode: MenuPresentMode = .ViewSlideOut
/// Prevents the same view controller (or a view controller of the same class) from being pushed more than once. Defaults to true.
public static var menuAllowPushOfSameClassTwice = true
/// Pops to any view controller already in the navigation stack instead of the view controller being pushed if they share the same class. Defaults to false.
public static var menuAllowPopIfPossible = false
public static var menuWidth: CGFloat = max(round(min(UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height) * 0.75), 240)
/// Width of the menu when presented on screen, showing the existing view controller in the remaining space. Default is 75% of the screen width.
public static var menuWidth: CGFloat = max(round(min((appScreenRect.width), (appScreenRect.height)) * 0.75), 240)
/// Duration of the animation when the menu is presented without gestures. Default is 0.35 seconds.
public static var menuAnimationPresentDuration = 0.35
/// Duration of the animation when the menu is dismissed without gestures. Default is 0.35 seconds.
public static var menuAnimationDismissDuration = 0.35
/// Amount to fade the existing view controller when the menu is presented. Default is 0 for no fade. Set to 1 to fade completely.
public static var menuAnimationFadeStrength: CGFloat = 0
/// The amount to scale the existing view controller or the menu view controller depending on the `menuPresentMode`. Default is 1 for no scaling. Less than 1 will shrink, greater than 1 will grow.
public static var menuAnimationTransformScaleFactor: CGFloat = 1
/// The background color behind menu animations. Depending on the animation settings this may not be visible. If `menuFadeStatusBar` is true, this color is used to fade it. Default is black.
public static var menuAnimationBackgroundColor: UIColor?
/// The shadow opacity around the menu view controller or existing view controller depending on the `menuPresentMode`. Default is 0.5 for 50% opacity.
public static var menuShadowOpacity: Float = 0.5
/// The shadow color around the menu view controller or existing view controller depending on the `menuPresentMode`. Default is black.
public static var menuShadowColor = UIColor.blackColor()
/// The radius of the shadow around the menu view controller or existing view controller depending on the `menuPresentMode`. Default is 5.
public static var menuShadowRadius: CGFloat = 5
/// The left menu swipe to dismiss gesture.
public static weak var menuLeftSwipeToDismissGesture: UIPanGestureRecognizer?
/// The right menu swipe to dismiss gesture.
public static weak var menuRightSwipeToDismissGesture: UIPanGestureRecognizer?
/// The strength of the parallax effect on the existing view controller. Does not apply to `menuPresentMode` when set to `ViewSlideOut`. Default is 0.
public static var menuParallaxStrength: Int = 0
/// Draws the `menuAnimationBackgroundColor` behind the status bar. Default is true.
public static var menuFadeStatusBar = true
/// - Warning: Deprecated. Use `menuAnimationTransformScaleFactor` instead.
@available(*, deprecated, renamed="menuAnimationTransformScaleFactor")
public static var menuAnimationShrinkStrength: CGFloat {
get {
@@ -58,124 +101,144 @@ public class SideMenuManager {
// prevent instantiation
private init() {}
// Note: if you want cells in a UITableViewController menu to look good, make them a subclass of UITableViewVibrantCell!
/**
The blur effect style of the menu if the menu's root view controller is a UITableViewController or UICollectionViewController.
- Note: If you want cells in a UITableViewController menu to show vibrancy, make them a subclass of UITableViewVibrantCell.
*/
public static var menuBlurEffectStyle: UIBlurEffectStyle? {
didSet {
updateMenuBlurIfNecessary()
if oldValue != menuBlurEffectStyle {
updateMenuBlurIfNecessary()
}
}
}
/// The left menu.
public static var menuLeftNavigationController: UISideMenuNavigationController? {
willSet {
if menuLeftNavigationController != nil {
let originalBlurEffectStyle = menuBlurEffectStyle
menuBlurEffectStyle = nil
updateMenuBlurIfNecessary()
menuBlurEffectStyle = originalBlurEffectStyle
if menuLeftNavigationController?.presentingViewController == nil {
removeMenuBlurForMenu(menuLeftNavigationController)
}
}
didSet {
if let menuLeftNavigationController = menuLeftNavigationController {
let exitPanGesture = UIPanGestureRecognizer()
exitPanGesture.addTarget(SideMenuTransition.self, action:#selector(SideMenuTransition.handleHideMenuPan(_:)))
menuLeftNavigationController.view.addGestureRecognizer(exitPanGesture)
menuLeftNavigationController.transitioningDelegate = SideMenuTransition.singleton
menuLeftNavigationController.modalPresentationStyle = .OverFullScreen
menuLeftSwipeToDismissGesture = exitPanGesture
updateMenuBlurIfNecessary()
guard oldValue?.presentingViewController == nil else {
print("SideMenu Warning: menuLeftNavigationController cannot be modified while it's presented.")
menuLeftNavigationController = oldValue
return
}
setupNavigationController(menuLeftNavigationController, leftSide: true)
}
}
/// The right menu.
public static var menuRightNavigationController: UISideMenuNavigationController? {
willSet {
if menuRightNavigationController != nil {
let originalBlurEffectStyle = menuBlurEffectStyle
menuBlurEffectStyle = nil
updateMenuBlurIfNecessary()
menuBlurEffectStyle = originalBlurEffectStyle
if menuRightNavigationController?.presentingViewController == nil {
removeMenuBlurForMenu(menuRightNavigationController)
}
}
didSet {
if let menuRightNavigationController = menuRightNavigationController {
let exitPanGesture = UIPanGestureRecognizer()
exitPanGesture.addTarget(SideMenuTransition.self, action:#selector(SideMenuTransition.handleHideMenuPan(_:)))
menuRightNavigationController.view.addGestureRecognizer(exitPanGesture)
menuRightNavigationController.transitioningDelegate = SideMenuTransition.singleton
menuRightNavigationController.modalPresentationStyle = .OverFullScreen
menuRightSwipeToDismissGesture = exitPanGesture
updateMenuBlurIfNecessary()
guard oldValue?.presentingViewController == nil else {
print("SideMenu Warning: menuRightNavigationController cannot be modified while it's presented.")
menuRightNavigationController = oldValue
return
}
setupNavigationController(menuRightNavigationController, leftSide: false)
}
}
private class func setupNavigationController(forMenu: UISideMenuNavigationController?, leftSide: Bool) {
guard let forMenu = forMenu else {
return
}
let exitPanGesture = UIPanGestureRecognizer()
exitPanGesture.addTarget(SideMenuTransition.self, action:#selector(SideMenuTransition.handleHideMenuPan(_:)))
forMenu.view.addGestureRecognizer(exitPanGesture)
forMenu.transitioningDelegate = SideMenuTransition.singleton
forMenu.modalPresentationStyle = .OverFullScreen
forMenu.leftSide = leftSide
if leftSide {
menuLeftSwipeToDismissGesture = exitPanGesture
} else {
menuRightSwipeToDismissGesture = exitPanGesture
}
updateMenuBlurIfNecessary()
}
private class func updateMenuBlurIfNecessary() {
if let menuLeftNavigationController = menuLeftNavigationController, let view = menuLeftNavigationController.visibleViewController?.view {
if !UIAccessibilityIsReduceTransparencyEnabled() && menuBlurEffectStyle != nil {
if originalLeftMenuBackgroundColor == nil {
originalLeftMenuBackgroundColor = view.backgroundColor
}
setupMenuBlurForMenu(menuLeftNavigationController)
} else if originalLeftMenuBackgroundColor != nil {
removeMenuBlurForMenu(menuLeftNavigationController)
view.backgroundColor = originalLeftMenuBackgroundColor!
originalLeftMenuBackgroundColor = nil
let menuBlurBlock = { (forMenu: UISideMenuNavigationController?) in
if let forMenu = forMenu {
setupMenuBlurForMenu(forMenu)
}
}
if let menuRightNavigationController = menuRightNavigationController, let view = menuRightNavigationController.visibleViewController?.view {
if !UIAccessibilityIsReduceTransparencyEnabled() && menuBlurEffectStyle != nil {
if originalRightMenuBackgroundColor == nil {
originalRightMenuBackgroundColor = view.backgroundColor
}
setupMenuBlurForMenu(menuRightNavigationController)
} else if originalRightMenuBackgroundColor != nil {
removeMenuBlurForMenu(menuRightNavigationController)
view.backgroundColor = originalRightMenuBackgroundColor!
originalRightMenuBackgroundColor = nil
}
}
menuBlurBlock(menuLeftNavigationController)
menuBlurBlock(menuRightNavigationController)
}
private class func setupMenuBlurForMenu(forMenu: UINavigationController) {
private class func setupMenuBlurForMenu(forMenu: UISideMenuNavigationController?) {
removeMenuBlurForMenu(forMenu)
guard let forMenu = forMenu,
menuBlurEffectStyle = menuBlurEffectStyle,
view = forMenu.visibleViewController?.view
where !UIAccessibilityIsReduceTransparencyEnabled() else {
return
}
if forMenu.originalMenuBackgroundColor == nil {
forMenu.originalMenuBackgroundColor = view.backgroundColor
}
let blurEffect = UIBlurEffect(style: menuBlurEffectStyle)
let blurView = UIVisualEffectView(effect: blurEffect)
view.backgroundColor = UIColor.clearColor()
if let tableViewController = forMenu.visibleViewController as? UITableViewController {
tableViewController.tableView.backgroundColor = UIColor.clearColor()
let blurEffect = UIBlurEffect(style: menuBlurEffectStyle!)
tableViewController.tableView.backgroundView = UIVisualEffectView(effect: blurEffect)
tableViewController.tableView.backgroundView = blurView
tableViewController.tableView.separatorEffect = UIVibrancyEffect(forBlurEffect: blurEffect)
tableViewController.tableView.reloadData()
} else if let viewController = forMenu.visibleViewController {
viewController.view.backgroundColor = UIColor.clearColor()
let blurView = UIVisualEffectView(effect: UIBlurEffect(style: menuBlurEffectStyle!))
} else {
blurView.autoresizingMask = [.FlexibleHeight, .FlexibleWidth]
blurView.frame = viewController.view.bounds
viewController.view.insertSubview(blurView, atIndex: 0)
blurView.frame = view.bounds
view.insertSubview(blurView, atIndex: 0)
}
}
private class func removeMenuBlurForMenu(forMenu: UINavigationController) {
private class func removeMenuBlurForMenu(forMenu: UISideMenuNavigationController?) {
guard let forMenu = forMenu,
originalMenuBackgroundColor = forMenu.originalMenuBackgroundColor,
view = forMenu.visibleViewController?.view else {
return
}
view.backgroundColor = originalMenuBackgroundColor
forMenu.originalMenuBackgroundColor = nil
if let tableViewController = forMenu.visibleViewController as? UITableViewController {
tableViewController.tableView.backgroundView = nil
tableViewController.tableView.separatorEffect = nil
tableViewController.tableView.reloadData()
} else if let viewController = forMenu.visibleViewController {
if let blurView = viewController.view.subviews[0] as? UIVisualEffectView {
blurView.removeFromSuperview()
}
} else if let blurView = view.subviews[0] as? UIVisualEffectView {
blurView.removeFromSuperview()
}
}
/**
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. If unspecified, gestures will be added for both sides.
- Returns: The array of screen edge gestures added to `toView`.
*/
public class func menuAddScreenEdgePanGesturesToPresent(toView toView: UIView, forMenu:UIRectEdge? = nil) -> [UIScreenEdgePanGestureRecognizer] {
var array = [UIScreenEdgePanGestureRecognizer]()
if forMenu != .Right {
let leftScreenEdgeGestureRecognizer = UIScreenEdgePanGestureRecognizer()
leftScreenEdgeGestureRecognizer.addTarget(SideMenuTransition.self, action:#selector(SideMenuTransition.handlePresentMenuPan(_:)))
leftScreenEdgeGestureRecognizer.addTarget(SideMenuTransition.self, action:#selector(SideMenuTransition.handlePresentMenuLeftScreenEdge(_:)))
leftScreenEdgeGestureRecognizer.edges = .Left
leftScreenEdgeGestureRecognizer.cancelsTouchesInView = true
toView.addGestureRecognizer(leftScreenEdgeGestureRecognizer)
@@ -184,7 +247,7 @@ public class SideMenuManager {
if forMenu != .Left {
let rightScreenEdgeGestureRecognizer = UIScreenEdgePanGestureRecognizer()
rightScreenEdgeGestureRecognizer.addTarget(SideMenuTransition.self, action:#selector(SideMenuTransition.handlePresentMenuPan(_:)))
rightScreenEdgeGestureRecognizer.addTarget(SideMenuTransition.self, action:#selector(SideMenuTransition.handlePresentMenuRightScreenEdge(_:)))
rightScreenEdgeGestureRecognizer.edges = .Right
rightScreenEdgeGestureRecognizer.cancelsTouchesInView = true
toView.addGestureRecognizer(rightScreenEdgeGestureRecognizer)
@@ -194,6 +257,13 @@ public class SideMenuManager {
return array
}
/**
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`.
*/
public class func menuAddPanGestureToPresent(toView toView: UIView) -> UIPanGestureRecognizer {
let panGestureRecognizer = UIPanGestureRecognizer()
panGestureRecognizer.addTarget(SideMenuTransition.self, action:#selector(SideMenuTransition.handlePresentMenuPan(_:)))
+162 -129
View File
@@ -13,6 +13,7 @@ internal class SideMenuTransition: UIPercentDrivenInteractiveTransition, UIViewC
private var presenting = false
private var interactive = false
private static weak var originalSuperview: UIView?
private static var switchMenus = false
internal static let singleton = SideMenuTransition()
internal static var presentDirection: UIRectEdge = .Left;
@@ -46,62 +47,75 @@ internal class SideMenuTransition: UIPercentDrivenInteractiveTransition, UIViewC
return viewController
}
class func handlePresentMenuLeftScreenEdge(edge: UIScreenEdgePanGestureRecognizer) {
SideMenuTransition.presentDirection = .Left
handlePresentMenuPan(edge)
}
class func handlePresentMenuRightScreenEdge(edge: UIScreenEdgePanGestureRecognizer) {
SideMenuTransition.presentDirection = .Right
handlePresentMenuPan(edge)
}
class func handlePresentMenuPan(pan: UIPanGestureRecognizer) {
// how much distance have we panned in reference to the parent view?
if let view = viewControllerForPresentedMenu != nil ? viewControllerForPresentedMenu?.view : pan.view {
let transform = view.transform
view.transform = CGAffineTransformIdentity
let translation = pan.translationInView(pan.view!)
view.transform = transform
// do some math to translate this to a percentage based value
if !singleton.interactive {
if translation.x == 0 {
return // not sure which way the user is swiping yet, so do nothing
}
if let edge = pan as? UIScreenEdgePanGestureRecognizer {
SideMenuTransition.presentDirection = edge.edges == .Left ? .Left : .Right
} else {
SideMenuTransition.presentDirection = translation.x > 0 ? .Left : .Right
}
if let menuViewController: UINavigationController = SideMenuTransition.presentDirection == .Left ? SideMenuManager.menuLeftNavigationController : SideMenuManager.menuRightNavigationController {
singleton.interactive = true
if let visibleViewController = visibleViewController {
visibleViewController.presentViewController(menuViewController, animated: true, completion: nil)
}
}
guard let view = viewControllerForPresentedMenu != nil ? viewControllerForPresentedMenu?.view : pan.view else {
return
}
let transform = view.transform
view.transform = CGAffineTransformIdentity
let translation = pan.translationInView(pan.view!)
view.transform = transform
// do some math to translate this to a percentage based value
if !singleton.interactive {
if translation.x == 0 {
return // not sure which way the user is swiping yet, so do nothing
}
let direction:CGFloat = SideMenuTransition.presentDirection == .Left ? 1 : -1
let distance = translation.x / SideMenuManager.menuWidth
// now lets deal with different states that the gesture recognizer sends
switch (pan.state) {
case .Began, .Changed:
if pan is UIScreenEdgePanGestureRecognizer {
singleton.updateInteractiveTransition(min(distance * direction, 1))
} else if distance > 0 && SideMenuTransition.presentDirection == .Right && SideMenuManager.menuLeftNavigationController != nil {
SideMenuTransition.presentDirection = .Left
singleton.cancelInteractiveTransition()
viewControllerForPresentedMenu?.presentViewController(SideMenuManager.menuLeftNavigationController!, animated: true, completion: nil)
} else if distance < 0 && SideMenuTransition.presentDirection == .Left && SideMenuManager.menuRightNavigationController != nil {
SideMenuTransition.presentDirection = .Right
singleton.cancelInteractiveTransition()
viewControllerForPresentedMenu?.presentViewController(SideMenuManager.menuRightNavigationController!, animated: true, completion: nil)
} else {
singleton.updateInteractiveTransition(min(distance * direction, 1))
}
default:
singleton.interactive = false
view.transform = CGAffineTransformIdentity
let velocity = pan.velocityInView(pan.view!).x * direction
view.transform = transform
if velocity >= 100 || velocity >= -50 && abs(distance) >= 0.5 {
singleton.finishInteractiveTransition()
} else {
singleton.cancelInteractiveTransition()
if !(pan is UIScreenEdgePanGestureRecognizer) {
SideMenuTransition.presentDirection = translation.x > 0 ? .Left : .Right
}
if let menuViewController = SideMenuTransition.presentDirection == .Left ? SideMenuManager.menuLeftNavigationController : SideMenuManager.menuRightNavigationController,
visibleViewController = visibleViewController {
singleton.interactive = true
visibleViewController.presentViewController(menuViewController, animated: true, completion: nil)
}
}
let direction: CGFloat = SideMenuTransition.presentDirection == .Left ? 1 : -1
let distance = translation.x / SideMenuManager.menuWidth
// now lets deal with different states that the gesture recognizer sends
switch (pan.state) {
case .Began, .Changed:
if pan is UIScreenEdgePanGestureRecognizer {
singleton.updateInteractiveTransition(min(distance * direction, 1))
} else if distance > 0 && SideMenuTransition.presentDirection == .Right && SideMenuManager.menuLeftNavigationController != nil {
SideMenuTransition.presentDirection = .Left
switchMenus = true
singleton.cancelInteractiveTransition()
} else if distance < 0 && SideMenuTransition.presentDirection == .Left && SideMenuManager.menuRightNavigationController != nil {
SideMenuTransition.presentDirection = .Right
switchMenus = true
singleton.cancelInteractiveTransition()
} else {
singleton.updateInteractiveTransition(min(distance * direction, 1))
}
default:
singleton.interactive = false
view.transform = CGAffineTransformIdentity
let velocity = pan.velocityInView(pan.view!).x * direction
view.transform = transform
if velocity >= 100 || velocity >= -50 && abs(distance) >= 0.5 {
// bug workaround: animation briefly resets after call to finishInteractiveTransition() but before animateTransition completion is called.
if NSProcessInfo().operatingSystemVersion.majorVersion == 8 && singleton.percentComplete > 1 - CGFloat(FLT_EPSILON) {
singleton.updateInteractiveTransition(0.9999)
}
singleton.finishInteractiveTransition()
} else {
singleton.cancelInteractiveTransition()
}
}
}
@@ -122,9 +136,12 @@ internal class SideMenuTransition: UIPercentDrivenInteractiveTransition, UIViewC
singleton.interactive = false
let velocity = pan.velocityInView(pan.view!).x * direction
if velocity >= 100 || velocity >= -50 && distance >= 0.5 {
// bug workaround: animation briefly resets after call to finishInteractiveTransition() but before animateTransition completion is called.
if NSProcessInfo().operatingSystemVersion.majorVersion == 8 && singleton.percentComplete > 1 - CGFloat(FLT_EPSILON) {
singleton.updateInteractiveTransition(0.9999)
}
singleton.finishInteractiveTransition()
}
else {
} else {
singleton.cancelInteractiveTransition()
}
}
@@ -136,9 +153,9 @@ internal class SideMenuTransition: UIPercentDrivenInteractiveTransition, UIViewC
internal class func hideMenuStart() {
NSNotificationCenter.defaultCenter().removeObserver(SideMenuTransition.singleton)
let mainViewController = SideMenuTransition.viewControllerForPresentedMenu!
let menuView = SideMenuTransition.presentDirection == .Left ? SideMenuManager.menuLeftNavigationController!.view : SideMenuManager.menuRightNavigationController!.view
guard let mainViewController = SideMenuTransition.viewControllerForPresentedMenu,
let menuView = SideMenuTransition.presentDirection == .Left ? SideMenuManager.menuLeftNavigationController?.view : SideMenuManager.menuRightNavigationController?.view else {return}
menuView.transform = CGAffineTransformIdentity
mainViewController.view.transform = CGAffineTransformIdentity
mainViewController.view.alpha = 1
@@ -174,8 +191,11 @@ internal class SideMenuTransition: UIPercentDrivenInteractiveTransition, UIViewC
}
internal class func hideMenuComplete() {
let mainViewController = SideMenuTransition.viewControllerForPresentedMenu!
let menuView = SideMenuTransition.presentDirection == .Left ? SideMenuManager.menuLeftNavigationController!.view : SideMenuManager.menuRightNavigationController!.view
guard let mainViewController = SideMenuTransition.viewControllerForPresentedMenu,
let menuView = SideMenuTransition.presentDirection == .Left ? SideMenuManager.menuLeftNavigationController?.view : SideMenuManager.menuRightNavigationController?.view else {
return
}
SideMenuTransition.tapView.removeFromSuperview()
SideMenuTransition.statusBarView?.removeFromSuperview()
mainViewController.view.motionEffects.removeAll()
@@ -187,58 +207,62 @@ internal class SideMenuTransition: UIPercentDrivenInteractiveTransition, UIViewC
originalSuperview?.addSubview(mainViewController.view)
}
internal class func presentMenuStart(forSize size: CGSize = UIScreen.mainScreen().bounds.size) {
let mainViewController = SideMenuTransition.viewControllerForPresentedMenu!
if let menuView = SideMenuTransition.presentDirection == .Left ? SideMenuManager.menuLeftNavigationController?.view : SideMenuManager.menuRightNavigationController?.view {
menuView.transform = CGAffineTransformIdentity
mainViewController.view.transform = CGAffineTransformIdentity
menuView.frame.size.width = SideMenuManager.menuWidth
menuView.frame.size.height = size.height
menuView.frame.origin.x = SideMenuTransition.presentDirection == .Left ? 0 : size.width - SideMenuManager.menuWidth
SideMenuTransition.statusBarView?.frame = UIApplication.sharedApplication().statusBarFrame
SideMenuTransition.statusBarView?.alpha = 1
internal class func presentMenuStart(forSize size: CGSize = SideMenuManager.appScreenRect.size) {
guard let menuView = SideMenuTransition.presentDirection == .Left ? SideMenuManager.menuLeftNavigationController?.view : SideMenuManager.menuRightNavigationController?.view,
let mainViewController = SideMenuTransition.viewControllerForPresentedMenu else {
return
}
menuView.transform = CGAffineTransformIdentity
mainViewController.view.transform = CGAffineTransformIdentity
menuView.frame.size.width = SideMenuManager.menuWidth
menuView.frame.size.height = size.height
menuView.frame.origin.x = SideMenuTransition.presentDirection == .Left ? 0 : size.width - SideMenuManager.menuWidth
SideMenuTransition.statusBarView?.frame = UIApplication.sharedApplication().statusBarFrame
SideMenuTransition.statusBarView?.alpha = 1
switch SideMenuManager.menuPresentMode {
switch SideMenuManager.menuPresentMode {
case .ViewSlideOut:
menuView.alpha = 1
let direction:CGFloat = SideMenuTransition.presentDirection == .Left ? 1 : -1
mainViewController.view.frame.origin.x = direction * (menuView.frame.width)
mainViewController.view.layer.shadowColor = SideMenuManager.menuShadowColor.CGColor
mainViewController.view.layer.shadowRadius = SideMenuManager.menuShadowRadius
mainViewController.view.layer.shadowOpacity = SideMenuManager.menuShadowOpacity
mainViewController.view.layer.shadowOffset = CGSizeMake(0, 0)
case .ViewSlideInOut:
menuView.alpha = 1
menuView.layer.shadowColor = SideMenuManager.menuShadowColor.CGColor
menuView.layer.shadowRadius = SideMenuManager.menuShadowRadius
menuView.layer.shadowOpacity = SideMenuManager.menuShadowOpacity
menuView.layer.shadowOffset = CGSizeMake(0, 0)
let direction:CGFloat = SideMenuTransition.presentDirection == .Left ? 1 : -1
mainViewController.view.frame.origin.x = direction * (menuView.frame.width)
mainViewController.view.transform = CGAffineTransformMakeScale(SideMenuManager.menuAnimationTransformScaleFactor, SideMenuManager.menuAnimationTransformScaleFactor)
mainViewController.view.alpha = 1 - SideMenuManager.menuAnimationFadeStrength
case .MenuSlideIn, .MenuDissolveIn:
menuView.alpha = 1
menuView.layer.shadowColor = SideMenuManager.menuShadowColor.CGColor
menuView.layer.shadowRadius = SideMenuManager.menuShadowRadius
menuView.layer.shadowOpacity = SideMenuManager.menuShadowOpacity
menuView.layer.shadowOffset = CGSizeMake(0, 0)
mainViewController.view.frame = CGRectMake(0, 0, size.width, size.height)
mainViewController.view.transform = CGAffineTransformMakeScale(SideMenuManager.menuAnimationTransformScaleFactor, SideMenuManager.menuAnimationTransformScaleFactor)
mainViewController.view.alpha = 1 - SideMenuManager.menuAnimationFadeStrength
}
case .ViewSlideOut:
menuView.alpha = 1
let direction:CGFloat = SideMenuTransition.presentDirection == .Left ? 1 : -1
mainViewController.view.frame.origin.x = direction * (menuView.frame.width)
mainViewController.view.layer.shadowColor = SideMenuManager.menuShadowColor.CGColor
mainViewController.view.layer.shadowRadius = SideMenuManager.menuShadowRadius
mainViewController.view.layer.shadowOpacity = SideMenuManager.menuShadowOpacity
mainViewController.view.layer.shadowOffset = CGSizeMake(0, 0)
case .ViewSlideInOut:
menuView.alpha = 1
menuView.layer.shadowColor = SideMenuManager.menuShadowColor.CGColor
menuView.layer.shadowRadius = SideMenuManager.menuShadowRadius
menuView.layer.shadowOpacity = SideMenuManager.menuShadowOpacity
menuView.layer.shadowOffset = CGSizeMake(0, 0)
let direction:CGFloat = SideMenuTransition.presentDirection == .Left ? 1 : -1
mainViewController.view.frame.origin.x = direction * (menuView.frame.width)
mainViewController.view.transform = CGAffineTransformMakeScale(SideMenuManager.menuAnimationTransformScaleFactor, SideMenuManager.menuAnimationTransformScaleFactor)
mainViewController.view.alpha = 1 - SideMenuManager.menuAnimationFadeStrength
case .MenuSlideIn, .MenuDissolveIn:
menuView.alpha = 1
menuView.layer.shadowColor = SideMenuManager.menuShadowColor.CGColor
menuView.layer.shadowRadius = SideMenuManager.menuShadowRadius
menuView.layer.shadowOpacity = SideMenuManager.menuShadowOpacity
menuView.layer.shadowOffset = CGSizeMake(0, 0)
mainViewController.view.frame = CGRectMake(0, 0, size.width, size.height)
mainViewController.view.transform = CGAffineTransformMakeScale(SideMenuManager.menuAnimationTransformScaleFactor, SideMenuManager.menuAnimationTransformScaleFactor)
mainViewController.view.alpha = 1 - SideMenuManager.menuAnimationFadeStrength
}
}
internal class func presentMenuComplete() {
NSNotificationCenter.defaultCenter().addObserver(SideMenuTransition.singleton, selector:#selector(SideMenuTransition.applicationDidEnterBackgroundNotification), name: UIApplicationDidEnterBackgroundNotification, object: nil)
let mainViewController = SideMenuTransition.viewControllerForPresentedMenu!
guard let mainViewController = SideMenuTransition.viewControllerForPresentedMenu else {
return
}
switch SideMenuManager.menuPresentMode {
case .MenuSlideIn, .MenuDissolveIn:
case .MenuSlideIn, .MenuDissolveIn, .ViewSlideInOut:
if SideMenuManager.menuParallaxStrength != 0 {
let horizontal = UIInterpolatingMotionEffect(keyPath: "center.x", type: .TiltAlongHorizontalAxis)
horizontal.minimumRelativeValue = -SideMenuManager.menuParallaxStrength
@@ -252,7 +276,7 @@ internal class SideMenuTransition: UIPercentDrivenInteractiveTransition, UIViewC
group.motionEffects = [horizontal, vertical]
mainViewController.view.addMotionEffect(group)
}
case .ViewSlideOut, .ViewSlideInOut: break;
case .ViewSlideOut: break;
}
if let topNavigationController = mainViewController as? UINavigationController {
topNavigationController.interactivePopGestureRecognizer!.enabled = false
@@ -264,8 +288,6 @@ internal class SideMenuTransition: UIPercentDrivenInteractiveTransition, UIViewC
// animate a change from one viewcontroller to another
internal func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let statusBarStyle = SideMenuTransition.visibleViewController?.preferredStatusBarStyle()
// get reference to our fromView, toView and the container view that we should perform the transition in
let container = transitionContext.containerView()!
if let menuBackgroundColor = SideMenuManager.menuAnimationBackgroundColor {
@@ -330,41 +352,52 @@ internal class SideMenuTransition: UIPercentDrivenInteractiveTransition, UIViewC
UIView.animateWithDuration(duration, delay: 0, options: options, animations: { () -> Void in
if self.presenting {
SideMenuTransition.presentMenuStart() // onstage items: slide in
}
else {
} else {
SideMenuTransition.hideMenuStart()
}
menuView.userInteractionEnabled = false
}) { (finished) -> Void in
if SideMenuTransition.visibleViewController?.preferredStatusBarStyle() != statusBarStyle {
print("Warning: do not change the status bar style while using custom transitions or you risk transitions not properly completing and locking up the UI. See http://www.openradar.me/21961293")
}
// tell our transitionContext object that we've finished animating
if transitionContext.transitionWasCancelled() {
let viewControllerForPresentedMenu = SideMenuTransition.viewControllerForPresentedMenu
if self.presenting {
SideMenuTransition.hideMenuComplete()
} else {
SideMenuTransition.presentMenuComplete()
}
menuView.userInteractionEnabled = true
transitionContext.completeTransition(false)
} else {
if self.presenting {
SideMenuTransition.presentMenuComplete()
transitionContext.completeTransition(true)
switch SideMenuManager.menuPresentMode {
case .ViewSlideOut:
container.addSubview(topView)
case .MenuSlideIn, .MenuDissolveIn, .ViewSlideInOut:
container.insertSubview(topView, atIndex: 0)
}
if let statusBarView = SideMenuTransition.statusBarView {
container.bringSubviewToFront(statusBarView)
}
} else {
SideMenuTransition.hideMenuComplete()
transitionContext.completeTransition(true)
menuView.removeFromSuperview()
if SideMenuTransition.switchMenus {
SideMenuTransition.switchMenus = false
viewControllerForPresentedMenu?.presentViewController(SideMenuTransition.presentDirection == .Left ? SideMenuManager.menuLeftNavigationController! : SideMenuManager.menuRightNavigationController!, animated: true, completion: nil)
}
return
}
if self.presenting {
SideMenuTransition.presentMenuComplete()
menuView.userInteractionEnabled = true
transitionContext.completeTransition(true)
switch SideMenuManager.menuPresentMode {
case .ViewSlideOut:
container.addSubview(topView)
case .MenuSlideIn, .MenuDissolveIn, .ViewSlideInOut:
container.insertSubview(topView, atIndex: 0)
}
if let statusBarView = SideMenuTransition.statusBarView {
container.bringSubviewToFront(statusBarView)
}
return
}
SideMenuTransition.hideMenuComplete()
transitionContext.completeTransition(true)
menuView.removeFromSuperview()
}
}
@@ -375,7 +408,7 @@ internal class SideMenuTransition: UIPercentDrivenInteractiveTransition, UIViewC
// MARK: UIViewControllerTransitioningDelegate protocol methods
// return the animataor when presenting a viewcontroller
// return the animator when presenting a viewcontroller
// rememeber that an animator (or animation controller) is any object that aheres to the UIViewControllerAnimatedTransitioning protocol
internal func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
self.presenting = true
@@ -9,6 +9,8 @@ import UIKit
public class UISideMenuNavigationController: UINavigationController {
internal var originalMenuBackgroundColor: UIColor?
public override func awakeFromNib() {
super.awakeFromNib()
@@ -18,9 +20,10 @@ public class UISideMenuNavigationController: UINavigationController {
modalPresentationStyle = .OverFullScreen
}
/// Whether the menu appears on the right or left side of the screen. Right is the default.
@IBInspectable public var leftSide:Bool = false {
didSet {
if isViewLoaded() { // suppress warnings
if isViewLoaded() && oldValue != leftSide { // suppress warnings
didSetSide()
}
}
@@ -112,44 +115,49 @@ public class UISideMenuNavigationController: UINavigationController {
}
override public func pushViewController(viewController: UIViewController, animated: Bool) {
if let menuViewController: UINavigationController = SideMenuTransition.presentDirection == .Left ? SideMenuManager.menuLeftNavigationController : SideMenuManager.menuRightNavigationController {
if let presentingViewController = menuViewController.presentingViewController as? UINavigationController {
// to avoid overlapping dismiss & pop/push calls, create a transaction block where the menu
// is dismissed after showing the appropriate screen
CATransaction.begin()
CATransaction.setCompletionBlock( { () -> Void in
self.dismissViewControllerAnimated(true, completion: nil)
self.visibleViewController?.viewWillAppear(false) // Hack: force selection to get cleared on UITableViewControllers when reappearing using custom transitions
})
UIView.animateWithDuration(SideMenuManager.menuAnimationDismissDuration, animations: { () -> Void in
SideMenuTransition.hideMenuStart()
})
if SideMenuManager.menuAllowPopIfPossible {
for subViewController in presentingViewController.viewControllers {
if subViewController.dynamicType == viewController.dynamicType {
presentingViewController.popToViewController(subViewController, animated: animated)
CATransaction.commit()
return
}
}
guard viewControllers.count > 0 else {
// NOTE: pushViewController is called by init(rootViewController: UIViewController)
// so we must perform the normal super method in this case.
super.pushViewController(viewController, animated: true)
return
}
guard let presentingViewController = presentingViewController as? UINavigationController else {
presentViewController(viewController, animated: animated, completion: nil)
print("SideMenu Warning: cannot push a ViewController from a ViewController without a NavigationController. It will be presented it instead.")
return
}
// to avoid overlapping dismiss & pop/push calls, create a transaction block where the menu
// is dismissed after showing the appropriate screen
CATransaction.begin()
CATransaction.setCompletionBlock( { () -> Void in
self.dismissViewControllerAnimated(true, completion: nil)
self.visibleViewController?.viewWillAppear(false) // Hack: force selection to get cleared on UITableViewControllers when reappearing using custom transitions
})
UIView.animateWithDuration(SideMenuManager.menuAnimationDismissDuration, animations: { () -> Void in
SideMenuTransition.hideMenuStart()
})
if SideMenuManager.menuAllowPopIfPossible {
for subViewController in presentingViewController.viewControllers {
if subViewController.dynamicType == viewController.dynamicType {
presentingViewController.popToViewController(subViewController, animated: animated)
CATransaction.commit()
return
}
if !SideMenuManager.menuAllowPushOfSameClassTwice {
if presentingViewController.viewControllers.last?.dynamicType == viewController.dynamicType {
CATransaction.commit()
return
}
}
presentingViewController.pushViewController(viewController, animated: animated)
CATransaction.commit()
} else {
menuViewController.presentViewController(viewController, animated: animated, completion: nil)
print("Warning: attempted to push a ViewController from a ViewController that doesn't have a NavigationController. It will be presented it instead.")
}
}
if !SideMenuManager.menuAllowPushOfSameClassTwice {
if presentingViewController.viewControllers.last?.dynamicType == viewController.dynamicType {
CATransaction.commit()
return
}
}
presentingViewController.pushViewController(viewController, animated: animated)
CATransaction.commit()
}
}
+20 -7
View File
@@ -3,14 +3,20 @@
[![License](https://img.shields.io/cocoapods/l/SideMenu.svg?style=flat)](http://cocoapods.org/pods/SideMenu)
[![Platform](https://img.shields.io/cocoapods/p/SideMenu.svg?style=flat)](http://cocoapods.org/pods/SideMenu)
### If you like SideMenu, give it a ★ at the top right of its [GitHub](https://github.com/jonkykong/SideMenu) page.
## Shameless Requests First
**1. If you like SideMenu, give it a ★ at the top right of this page.**
**2. I need an invite to Dribbble to share SideMenu. Invite me: https://dribbble.com/jonkykong**
## Overview
SideMenu is a simple and versatile side menu control written in Swift.
* **It can be implemented in storyboard without a single line of code.**
* Three standard animation styles to choose from.
* Highly customizable without needing to write tons of custom code.
* Supports continuous swiping between each side menu in a single gesture.
* Menus can be presented and dismissed the same as any other View Controller since this control uses custom transitions.
- [x] **It can be implemented in storyboard without a single line of [code](#code-less-storyboard-implementation).**
- [x] Four standard animation styles to choose from (even parallax if you want to get weird).
- [x] Highly customizable without needing to write tons of custom code.
- [x] Supports continuous swiping between side menus on boths sides in a single gesture.
- [x] Global menu configuration. Set-up once and be done for all screens.
- [x] Menus can be presented and dismissed the same as any other View Controller since this control uses custom transitions.
Check out the example project or this [interactive demo](https://appetize.io/app/64a9v3e6b8c6f53zvn5pnny80m) to see it in action!
@@ -20,7 +26,7 @@ Check out the example project or this [interactive demo](https://appetize.io/app
![](etc/InOut.gif)
## Requirements
* iOS 8 or higher
- [x] iOS 8 or higher
## Installation
@@ -117,6 +123,13 @@ menuAddPanGestureToPresent(toView toView: UIView) -> UIPanGestureRecognizer
## Known Issues
Don't try to change the status bar appearance when presenting a menu. When used with quick gestures/animations, it causes the presentation animation to not complete properly and locks the UI. See [radar 21961293](http://www.openradar.me/21961293) for more information.
## About Me
My name is Jon Kent and I'm a freelance iOS designer, developer, and mobile strategist. I love coffee and play the drums. **Hire me!**
🌎 Web: [http://jonkent.me](http://jonkent.me)
✉️ Email: [contact@jonkent.me](mailto:contact@jonkent.me)
## License
SideMenu is available under the MIT license. See the LICENSE file for more info.
+1 -1
View File
@@ -8,7 +8,7 @@
Pod::Spec.new do |s|
s.name = "SideMenu"
s.version = "1.1.1"
s.version = "1.1.9"
s.summary = "Simple side menu control for iOS in Swift inspired by Facebook. Right and Left sides. No coding required."
# This description is used to generate tags and improve search results.