From 9dd88c1a6ed9d7112ba8c8c0e11e2a635a3dd9c0 Mon Sep 17 00:00:00 2001 From: Robin Chou Date: Wed, 14 Aug 2013 18:58:50 -0400 Subject: [PATCH 1/5] Reset pan gesture velocity after user swipe ends --- MFSideMenu/MFSideMenuContainerViewController.m | 1 + 1 file changed, 1 insertion(+) diff --git a/MFSideMenu/MFSideMenuContainerViewController.m b/MFSideMenu/MFSideMenuContainerViewController.m index ab9e122..076135e 100644 --- a/MFSideMenu/MFSideMenuContainerViewController.m +++ b/MFSideMenu/MFSideMenuContainerViewController.m @@ -743,6 +743,7 @@ - (CGFloat)animationDurationFromStartPosition:(CGFloat)startPosition toEndPositi if(ABS(self.panGestureVelocity) > 1.0) { // try to continue the animation at the speed the user was swiping duration = animationPositionDelta / ABS(self.panGestureVelocity); + self.panGestureVelocity = 0.0; } else { // no swipe was used, user tapped the bar button item // TODO: full animation duration hard to calculate with two menu widths From d0b17d6aeaf86b7a70a4125664f9ed154e47f3de Mon Sep 17 00:00:00 2001 From: Robin Chou Date: Wed, 21 Aug 2013 11:54:45 -0400 Subject: [PATCH 2/5] Added elastic pan ability to make the side menu feel more life like --- .../MFSideMenuContainerViewController.h | 2 + .../MFSideMenuContainerViewController.m | 78 ++++++++++++------- 2 files changed, 52 insertions(+), 28 deletions(-) diff --git a/MFSideMenu/MFSideMenuContainerViewController.h b/MFSideMenu/MFSideMenuContainerViewController.h index 0537f1a..391b3e0 100644 --- a/MFSideMenu/MFSideMenuContainerViewController.h +++ b/MFSideMenu/MFSideMenuContainerViewController.h @@ -61,6 +61,8 @@ typedef enum { @property (nonatomic, assign) BOOL menuSlideAnimationEnabled; @property (nonatomic, assign) CGFloat menuSlideAnimationFactor; // higher = less menu movement on animation +// allow elastic panning when the side menu is in the open state +@property (nonatomic, assign) BOOL elastic; - (void)toggleLeftSideMenuCompletion:(void (^)(void))completion; - (void)toggleRightSideMenuCompletion:(void (^)(void))completion; diff --git a/MFSideMenu/MFSideMenuContainerViewController.m b/MFSideMenu/MFSideMenuContainerViewController.m index 076135e..58f1402 100644 --- a/MFSideMenu/MFSideMenuContainerViewController.m +++ b/MFSideMenu/MFSideMenuContainerViewController.m @@ -45,6 +45,7 @@ @implementation MFSideMenuContainerViewController @synthesize menuAnimationDefaultDuration; @synthesize menuAnimationMaxDuration; @synthesize shadow; +@synthesize elastic; #pragma mark - @@ -85,6 +86,7 @@ - (void)setDefaultSettings { self.menuAnimationMaxDuration = 0.4f; self.panMode = MFSideMenuPanModeDefault; self.viewHasAppeared = NO; + self.elastic = YES; } - (void)setupMenuContainerView { @@ -381,7 +383,7 @@ - (void)sendStateEventNotification:(MFSideMenuStateEvent)event { #pragma mark - #pragma mark - Side Menu Positioning -- (void) setLeftSideMenuFrameToClosedPosition { +- (void)setLeftSideMenuFrameToClosedPosition { if(!self.leftMenuViewController) return; CGRect leftFrame = [self.leftMenuViewController view].frame; leftFrame.size.width = self.leftMenuWidth; @@ -391,7 +393,7 @@ - (void) setLeftSideMenuFrameToClosedPosition { [self.leftMenuViewController view].autoresizingMask = UIViewAutoresizingFlexibleRightMargin|UIViewAutoresizingFlexibleHeight; } -- (void) setRightSideMenuFrameToClosedPosition { +- (void)setRightSideMenuFrameToClosedPosition { if(!self.rightMenuViewController) return; CGRect rightFrame = [self.rightMenuViewController view].frame; rightFrame.size.width = self.rightMenuWidth; @@ -402,11 +404,15 @@ - (void) setRightSideMenuFrameToClosedPosition { [self.rightMenuViewController view].autoresizingMask = UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleHeight; } -- (void)alignLeftMenuControllerWithCenterViewController { +- (void)alignLeftMenuControllerWithCenterViewController { CGRect leftMenuFrame = [self.leftMenuViewController view].frame; leftMenuFrame.size.width = _leftMenuWidth; CGFloat xOffset = [self.centerViewController view].frame.origin.x; + if (xOffset > self.leftMenuWidth) { + return; + } + CGFloat xPositionDivider = (self.menuSlideAnimationEnabled) ? self.menuSlideAnimationFactor : 1.0; leftMenuFrame.origin.x = xOffset / xPositionDivider - _leftMenuWidth / xPositionDivider; @@ -418,6 +424,10 @@ - (void)alignRightMenuControllerWithCenterViewController { rightMenuFrame.size.width = _rightMenuWidth; CGFloat xOffset = [self.centerViewController view].frame.origin.x; + if (xOffset < -1*self.rightMenuWidth) { + return; + } + CGFloat xPositionDivider = (self.menuSlideAnimationEnabled) ? self.menuSlideAnimationFactor : 1.0; rightMenuFrame.origin.x = self.menuContainerView.frame.size.width - _rightMenuWidth + xOffset / xPositionDivider @@ -483,11 +493,11 @@ - (void)setRightMenuWidth:(CGFloat)rightMenuWidth animated:(BOOL)animated { #pragma mark - #pragma mark - MFSideMenuPanMode -- (BOOL) centerViewControllerPanEnabled { +- (BOOL)centerViewControllerPanEnabled { return ((self.panMode & MFSideMenuPanModeCenterViewController) == MFSideMenuPanModeCenterViewController); } -- (BOOL) sideMenuPanEnabled { +- (BOOL)sideMenuPanEnabled { return ((self.panMode & MFSideMenuPanModeSideMenu) == MFSideMenuPanModeSideMenu); } @@ -528,7 +538,7 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer // this method handles any pan event // and sets the navigation controller's frame as needed -- (void) handlePan:(UIPanGestureRecognizer *)recognizer { +- (void)handlePan:(UIPanGestureRecognizer *)recognizer { UIView *view = [self.centerViewController view]; if(recognizer.state == UIGestureRecognizerStateBegan) { @@ -553,10 +563,10 @@ - (void) handlePan:(UIPanGestureRecognizer *)recognizer { } } - if((self.menuState == MFSideMenuStateRightMenuOpen && self.panDirection == MFSideMenuPanDirectionLeft) - || (self.menuState == MFSideMenuStateLeftMenuOpen && self.panDirection == MFSideMenuPanDirectionRight)) { + if (!self.elastic && + (self.menuState == MFSideMenuStateLeftMenuOpen && self.panDirection == MFSideMenuPanDirectionRight) && + (self.menuState == MFSideMenuStateRightMenuOpen && self.panDirection == MFSideMenuPanDirectionLeft)) { self.panDirection = MFSideMenuPanDirectionNone; - return; } if(self.panDirection == MFSideMenuPanDirectionLeft) { @@ -566,7 +576,7 @@ - (void) handlePan:(UIPanGestureRecognizer *)recognizer { } } -- (void) handleRightPan:(UIPanGestureRecognizer *)recognizer { +- (void)handleRightPan:(UIPanGestureRecognizer *)recognizer { if(!self.leftMenuViewController && self.menuState == MFSideMenuStateClosed) return; UIView *view = [self.centerViewController view]; @@ -576,14 +586,16 @@ - (void) handleRightPan:(UIPanGestureRecognizer *)recognizer { translatedPoint = CGPointMake(adjustedOrigin.x + translatedPoint.x, adjustedOrigin.y + translatedPoint.y); - translatedPoint.x = MAX(translatedPoint.x, -1*self.rightMenuWidth); - translatedPoint.x = MIN(translatedPoint.x, self.leftMenuWidth); - if(self.menuState == MFSideMenuStateRightMenuOpen) { - // menu is already open, the most the user can do is close it in this gesture - translatedPoint.x = MIN(translatedPoint.x, 0); - } else { - // we are opening the menu - translatedPoint.x = MAX(translatedPoint.x, 0); + if (!self.elastic) { + translatedPoint.x = MAX(translatedPoint.x, -1*self.rightMenuWidth); + translatedPoint.x = MIN(translatedPoint.x, self.leftMenuWidth); + if(self.menuState == MFSideMenuStateRightMenuOpen) { + // menu is already open, the most the user can do is close it in this gesture + translatedPoint.x = MIN(translatedPoint.x, 0); + } else { + // we are opening the menu + translatedPoint.x = MAX(translatedPoint.x, 0); + } } if(recognizer.state == UIGestureRecognizerStateEnded) { @@ -591,7 +603,7 @@ - (void) handleRightPan:(UIPanGestureRecognizer *)recognizer { CGFloat finalX = translatedPoint.x + (.35*velocity.x); CGFloat viewWidth = view.frame.size.width; - if(self.menuState == MFSideMenuStateClosed) { + if (self.menuState == MFSideMenuStateClosed) { BOOL showMenu = (finalX > viewWidth/2) || (finalX > self.leftMenuWidth/2); if(showMenu) { self.panGestureVelocity = velocity.x; @@ -603,6 +615,10 @@ - (void) handleRightPan:(UIPanGestureRecognizer *)recognizer { } else { BOOL hideMenu = (finalX > adjustedOrigin.x); if(hideMenu) { + if (self.elastic && self.menuState == MFSideMenuStateLeftMenuOpen) { + [self setMenuState:MFSideMenuStateLeftMenuOpen]; + return; + } self.panGestureVelocity = velocity.x; [self setMenuState:MFSideMenuStateClosed]; } else { @@ -617,7 +633,7 @@ - (void) handleRightPan:(UIPanGestureRecognizer *)recognizer { } } -- (void) handleLeftPan:(UIPanGestureRecognizer *)recognizer { +- (void)handleLeftPan:(UIPanGestureRecognizer *)recognizer { if(!self.rightMenuViewController && self.menuState == MFSideMenuStateClosed) return; UIView *view = [self.centerViewController view]; @@ -627,14 +643,16 @@ - (void) handleLeftPan:(UIPanGestureRecognizer *)recognizer { translatedPoint = CGPointMake(adjustedOrigin.x + translatedPoint.x, adjustedOrigin.y + translatedPoint.y); - translatedPoint.x = MAX(translatedPoint.x, -1*self.rightMenuWidth); - translatedPoint.x = MIN(translatedPoint.x, self.leftMenuWidth); - if(self.menuState == MFSideMenuStateLeftMenuOpen) { - // don't let the pan go less than 0 if the menu is already open - translatedPoint.x = MAX(translatedPoint.x, 0); - } else { - // we are opening the menu - translatedPoint.x = MIN(translatedPoint.x, 0); + if (!self.elastic) { + translatedPoint.x = MAX(translatedPoint.x, -1*self.rightMenuWidth); + translatedPoint.x = MIN(translatedPoint.x, self.leftMenuWidth); + if(self.menuState == MFSideMenuStateLeftMenuOpen) { + // don't let the pan go less than 0 if the menu is already open + translatedPoint.x = MAX(translatedPoint.x, 0); + } else { + // we are opening the menu + translatedPoint.x = MIN(translatedPoint.x, 0); + } } [self setCenterViewControllerOffset:translatedPoint.x]; @@ -656,6 +674,10 @@ - (void) handleLeftPan:(UIPanGestureRecognizer *)recognizer { } else { BOOL hideMenu = (finalX < adjustedOrigin.x); if(hideMenu) { + if (self.elastic && self.menuState == MFSideMenuStateRightMenuOpen) { + [self setMenuState:MFSideMenuStateRightMenuOpen]; + return; + } self.panGestureVelocity = velocity.x; [self setMenuState:MFSideMenuStateClosed]; } else { From b90eac22f7a028c5f7598c4f27e900b2f231387d Mon Sep 17 00:00:00 2001 From: Robin Chou Date: Thu, 22 Aug 2013 17:06:55 -0400 Subject: [PATCH 3/5] Prevent panning beyond closed position --- .../MFSideMenuContainerViewController.m | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/MFSideMenu/MFSideMenuContainerViewController.m b/MFSideMenu/MFSideMenuContainerViewController.m index 58f1402..574b19b 100644 --- a/MFSideMenu/MFSideMenuContainerViewController.m +++ b/MFSideMenu/MFSideMenuContainerViewController.m @@ -404,7 +404,7 @@ - (void)setRightSideMenuFrameToClosedPosition { [self.rightMenuViewController view].autoresizingMask = UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleHeight; } -- (void)alignLeftMenuControllerWithCenterViewController { +- (void)alignLeftMenuControllerWithCenterViewController { CGRect leftMenuFrame = [self.leftMenuViewController view].frame; leftMenuFrame.size.width = _leftMenuWidth; @@ -430,8 +430,8 @@ - (void)alignRightMenuControllerWithCenterViewController { CGFloat xPositionDivider = (self.menuSlideAnimationEnabled) ? self.menuSlideAnimationFactor : 1.0; rightMenuFrame.origin.x = self.menuContainerView.frame.size.width - _rightMenuWidth - + xOffset / xPositionDivider - + _rightMenuWidth / xPositionDivider; + + xOffset / xPositionDivider + + _rightMenuWidth / xPositionDivider; [self.rightMenuViewController view].frame = rightMenuFrame; } @@ -514,7 +514,7 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceive return [self centerViewControllerPanEnabled]; if([gestureRecognizer.view isEqual:self.menuContainerView]) - return [self sideMenuPanEnabled]; + return [self sideMenuPanEnabled]; // pan gesture is attached to a custom view return YES; @@ -589,13 +589,14 @@ - (void)handleRightPan:(UIPanGestureRecognizer *)recognizer { if (!self.elastic) { translatedPoint.x = MAX(translatedPoint.x, -1*self.rightMenuWidth); translatedPoint.x = MIN(translatedPoint.x, self.leftMenuWidth); - if(self.menuState == MFSideMenuStateRightMenuOpen) { - // menu is already open, the most the user can do is close it in this gesture - translatedPoint.x = MIN(translatedPoint.x, 0); - } else { - // we are opening the menu - translatedPoint.x = MAX(translatedPoint.x, 0); - } + } + + if(self.menuState == MFSideMenuStateRightMenuOpen) { + // menu is already open, the most the user can do is close it in this gesture + translatedPoint.x = MIN(translatedPoint.x, 0); + } else { + // we are opening the menu + translatedPoint.x = MAX(translatedPoint.x, 0); } if(recognizer.state == UIGestureRecognizerStateEnded) { @@ -622,6 +623,10 @@ - (void)handleRightPan:(UIPanGestureRecognizer *)recognizer { self.panGestureVelocity = velocity.x; [self setMenuState:MFSideMenuStateClosed]; } else { + if (self.elastic && self.menuState == MFSideMenuStateLeftMenuOpen) { + [self setMenuState:MFSideMenuStateClosed]; + return; + } self.panGestureVelocity = 0; [self setCenterViewControllerOffset:adjustedOrigin.x animated:YES completion:nil]; } @@ -646,16 +651,15 @@ - (void)handleLeftPan:(UIPanGestureRecognizer *)recognizer { if (!self.elastic) { translatedPoint.x = MAX(translatedPoint.x, -1*self.rightMenuWidth); translatedPoint.x = MIN(translatedPoint.x, self.leftMenuWidth); - if(self.menuState == MFSideMenuStateLeftMenuOpen) { - // don't let the pan go less than 0 if the menu is already open - translatedPoint.x = MAX(translatedPoint.x, 0); - } else { - // we are opening the menu - translatedPoint.x = MIN(translatedPoint.x, 0); - } } - [self setCenterViewControllerOffset:translatedPoint.x]; + if(self.menuState == MFSideMenuStateLeftMenuOpen) { + // don't let the pan go less than 0 if the menu is already open + translatedPoint.x = MAX(translatedPoint.x, 0); + } else { + // we are opening the menu + translatedPoint.x = MIN(translatedPoint.x, 0); + } if(recognizer.state == UIGestureRecognizerStateEnded) { CGPoint velocity = [recognizer velocityInView:view]; @@ -681,6 +685,10 @@ - (void)handleLeftPan:(UIPanGestureRecognizer *)recognizer { self.panGestureVelocity = velocity.x; [self setMenuState:MFSideMenuStateClosed]; } else { + if (self.elastic && self.menuState == MFSideMenuStateRightMenuOpen) { + [self setMenuState:MFSideMenuStateClosed]; + return; + } self.panGestureVelocity = 0; [self setCenterViewControllerOffset:adjustedOrigin.x animated:YES completion:nil]; } From 74333649c720120bf3e15677797afdbae16e8cde Mon Sep 17 00:00:00 2001 From: Robin Chou Date: Thu, 22 Aug 2013 17:10:30 -0400 Subject: [PATCH 4/5] Turn off elasticity by default, updated readme to reflect new boolean --- MFSideMenu/MFSideMenuContainerViewController.m | 2 +- README.mdown | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/MFSideMenu/MFSideMenuContainerViewController.m b/MFSideMenu/MFSideMenuContainerViewController.m index 574b19b..4511335 100644 --- a/MFSideMenu/MFSideMenuContainerViewController.m +++ b/MFSideMenu/MFSideMenuContainerViewController.m @@ -86,7 +86,7 @@ - (void)setDefaultSettings { self.menuAnimationMaxDuration = 0.4f; self.panMode = MFSideMenuPanModeDefault; self.viewHasAppeared = NO; - self.elastic = YES; + self.elastic = NO; } - (void)setupMenuContainerView { diff --git a/README.mdown b/README.mdown index 92fa3d6..cab402b 100644 --- a/README.mdown +++ b/README.mdown @@ -81,6 +81,14 @@ You can add panning to any view like so: [panView addGestureRecognizer:[self.menuContainerViewController panGestureRecognizer]; ``` +###Elasticity + +You can enable elasticity which allows the user to pan beyond the edges of the screen for a more natural feel. By default, this feature is disabled. + +```objective-c +self.menuContainerViewController.elastic = YES; +``` + ###Listening for Menu Events You can listen for menu state event changes (i.e. menu will open, menu did open, etc.). See MFSideMenuContainerViewController.h for the different types of events. From e83a6a58938afc77966b39dd1912a34ea08017a0 Mon Sep 17 00:00:00 2001 From: Robin Chou Date: Thu, 22 Aug 2013 17:16:30 -0400 Subject: [PATCH 5/5] Added comments --- MFSideMenu/MFSideMenuContainerViewController.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MFSideMenu/MFSideMenuContainerViewController.m b/MFSideMenu/MFSideMenuContainerViewController.m index 4511335..51e86c6 100644 --- a/MFSideMenu/MFSideMenuContainerViewController.m +++ b/MFSideMenu/MFSideMenuContainerViewController.m @@ -408,6 +408,7 @@ - (void)alignLeftMenuControllerWithCenterViewController { CGRect leftMenuFrame = [self.leftMenuViewController view].frame; leftMenuFrame.size.width = _leftMenuWidth; + // prevent the slide from left animation from going past the menuWidth CGFloat xOffset = [self.centerViewController view].frame.origin.x; if (xOffset > self.leftMenuWidth) { return; @@ -423,6 +424,7 @@ - (void)alignRightMenuControllerWithCenterViewController { CGRect rightMenuFrame = [self.rightMenuViewController view].frame; rightMenuFrame.size.width = _rightMenuWidth; + // prevent the slide from right animation from going past the menuWidth CGFloat xOffset = [self.centerViewController view].frame.origin.x; if (xOffset < -1*self.rightMenuWidth) { return; @@ -586,6 +588,7 @@ - (void)handleRightPan:(UIPanGestureRecognizer *)recognizer { translatedPoint = CGPointMake(adjustedOrigin.x + translatedPoint.x, adjustedOrigin.y + translatedPoint.y); + // Allow user to pan past the edge if elastic is YES if (!self.elastic) { translatedPoint.x = MAX(translatedPoint.x, -1*self.rightMenuWidth); translatedPoint.x = MIN(translatedPoint.x, self.leftMenuWidth); @@ -648,6 +651,7 @@ - (void)handleLeftPan:(UIPanGestureRecognizer *)recognizer { translatedPoint = CGPointMake(adjustedOrigin.x + translatedPoint.x, adjustedOrigin.y + translatedPoint.y); + // allow user to pan past the edge if elastic is enabled if (!self.elastic) { translatedPoint.x = MAX(translatedPoint.x, -1*self.rightMenuWidth); translatedPoint.x = MIN(translatedPoint.x, self.leftMenuWidth);