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 ab9e122..51e86c6 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 = NO; } - (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; @@ -406,7 +408,12 @@ - (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; + } + CGFloat xPositionDivider = (self.menuSlideAnimationEnabled) ? self.menuSlideAnimationFactor : 1.0; leftMenuFrame.origin.x = xOffset / xPositionDivider - _leftMenuWidth / xPositionDivider; @@ -417,11 +424,16 @@ - (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; + } + 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; } @@ -483,11 +495,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); } @@ -504,7 +516,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; @@ -528,7 +540,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 +565,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 +578,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,8 +588,12 @@ - (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); + // 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); + } + 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); @@ -591,7 +607,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,9 +619,17 @@ - (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 { + if (self.elastic && self.menuState == MFSideMenuStateLeftMenuOpen) { + [self setMenuState:MFSideMenuStateClosed]; + return; + } self.panGestureVelocity = 0; [self setCenterViewControllerOffset:adjustedOrigin.x animated:YES completion:nil]; } @@ -617,7 +641,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,8 +651,12 @@ - (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); + // 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); + } + 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); @@ -637,8 +665,6 @@ - (void) handleLeftPan:(UIPanGestureRecognizer *)recognizer { translatedPoint.x = MIN(translatedPoint.x, 0); } - [self setCenterViewControllerOffset:translatedPoint.x]; - if(recognizer.state == UIGestureRecognizerStateEnded) { CGPoint velocity = [recognizer velocityInView:view]; CGFloat finalX = translatedPoint.x + (.35*velocity.x); @@ -656,9 +682,17 @@ - (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 { + if (self.elastic && self.menuState == MFSideMenuStateRightMenuOpen) { + [self setMenuState:MFSideMenuStateClosed]; + return; + } self.panGestureVelocity = 0; [self setCenterViewControllerOffset:adjustedOrigin.x animated:YES completion:nil]; } @@ -743,6 +777,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 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.