Skip to content

Instantly share code, notes, and snippets.

@youweit
Created March 17, 2015 20:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save youweit/9cf494f0413a3f995aac to your computer and use it in GitHub Desktop.
Save youweit/9cf494f0413a3f995aac to your computer and use it in GitHub Desktop.
// Copyright (c) 2013 Mutual Mobile (http://mutualmobile.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "MMDrawerController.h"
#import "UIViewController+MMDrawerController.h"
#import <QuartzCore/QuartzCore.h>
CGFloat const MMDrawerDefaultWidth = 280.0f;
CGFloat const MMDrawerDefaultAnimationVelocity = 840.0f;
NSTimeInterval const MMDrawerDefaultFullAnimationDelay = 0.10f;
CGFloat const MMDrawerDefaultBounceDistance = 50.0f;
NSTimeInterval const MMDrawerDefaultBounceAnimationDuration = 0.2f;
CGFloat const MMDrawerDefaultSecondBounceDistancePercentage = .25f;
CGFloat const MMDrawerDefaultShadowRadius = 10.0f;
CGFloat const MMDrawerDefaultShadowOpacity = 0.8;
NSTimeInterval const MMDrawerMinimumAnimationDuration = 0.15f;
CGFloat const MMDrawerBezelRange = 20.0f;
CGFloat const MMDrawerPanVelocityXAnimationThreshold = 200.0f;
/** The amount of overshoot that is panned linearly. The remaining percentage nonlinearly asymptotes to the max percentage. */
CGFloat const MMDrawerOvershootLinearRangePercentage = 0.75f;
/** The percent of the possible overshoot width to use as the actual overshoot percentage. */
CGFloat const MMDrawerOvershootPercentage = 0.1f;
typedef BOOL (^MMDrawerGestureShouldRecognizeTouchBlock)(MMDrawerController * drawerController, UIGestureRecognizer * gesture, UITouch * touch);
typedef void (^MMDrawerGestureCompletionBlock)(MMDrawerController * drawerController, UIGestureRecognizer * gesture);
static CAKeyframeAnimation * bounceKeyFrameAnimationForDistanceOnView(CGFloat distance, UIView * view) {
CGFloat factors[32] = {0, 32, 60, 83, 100, 114, 124, 128, 128, 124, 114, 100, 83, 60, 32,
0, 24, 42, 54, 62, 64, 62, 54, 42, 24, 0, 18, 28, 32, 28, 18, 0};
NSMutableArray *values = [NSMutableArray array];
for (int i=0; i<32; i++)
{
CGFloat positionOffset = factors[i]/128.0f * distance + CGRectGetMidX(view.bounds);
[values addObject:@(positionOffset)];
}
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position.x"];
animation.repeatCount = 1;
animation.duration = .8;
animation.fillMode = kCAFillModeForwards;
animation.values = values;
animation.removedOnCompletion = YES;
animation.autoreverses = NO;
return animation;
}
static NSString *MMDrawerLeftDrawerKey = @"MMDrawerLeftDrawer";
static NSString *MMDrawerRightDrawerKey = @"MMDrawerRightDrawer";
static NSString *MMDrawerCenterKey = @"MMDrawerCenter";
static NSString *MMDrawerOpenSideKey = @"MMDrawerOpenSide";
@interface MMDrawerCenterContainerView : UIView
@property (nonatomic,assign) MMDrawerOpenCenterInteractionMode centerInteractionMode;
@property (nonatomic,assign) MMDrawerSide openSide;
@end
@implementation MMDrawerCenterContainerView
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
UIView *hitView = [super hitTest:point withEvent:event];
if(hitView &&
self.openSide != MMDrawerSideNone){
UINavigationBar * navBar = [self navigationBarContainedWithinSubviewsOfView:self];
CGRect navBarFrame = [navBar convertRect:navBar.bounds toView:self];
if((self.centerInteractionMode == MMDrawerOpenCenterInteractionModeNavigationBarOnly &&
CGRectContainsPoint(navBarFrame, point) == NO) ||
self.centerInteractionMode == MMDrawerOpenCenterInteractionModeNone){
hitView = nil;
}
}
return hitView;
}
-(UINavigationBar*)navigationBarContainedWithinSubviewsOfView:(UIView*)view{
UINavigationBar * navBar = nil;
for(UIView * subview in [view subviews]){
if([view isKindOfClass:[UINavigationBar class]]){
navBar = (UINavigationBar*)view;
break;
}
else {
navBar = [self navigationBarContainedWithinSubviewsOfView:subview];
if (navBar != nil) {
break;
}
}
}
return navBar;
}
@end
@interface MMDrawerController () <UIGestureRecognizerDelegate>{
CGFloat _maximumRightDrawerWidth;
CGFloat _maximumLeftDrawerWidth;
UIColor * _statusBarViewBackgroundColor;
}
@property (nonatomic, assign, readwrite) MMDrawerSide openSide;
@property (nonatomic, strong) UIView * childControllerContainerView;
@property (nonatomic, strong) MMDrawerCenterContainerView * centerContainerView;
@property (nonatomic, strong) UIView * dummyStatusBarView;
@property (nonatomic, assign) CGRect startingPanRect;
@property (nonatomic, copy) MMDrawerControllerDrawerVisualStateBlock drawerVisualState;
@property (nonatomic, copy) MMDrawerGestureShouldRecognizeTouchBlock gestureShouldRecognizeTouch;
@property (nonatomic, copy) MMDrawerGestureCompletionBlock gestureCompletion;
@property (nonatomic, assign, getter = isAnimatingDrawer) BOOL animatingDrawer;
@end
@implementation MMDrawerController
#pragma mark - Init
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
[self commonSetup];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if (self) {
[self commonSetup];
}
return self;
}
-(id)initWithCenterViewController:(UIViewController *)centerViewController leftDrawerViewController:(UIViewController *)leftDrawerViewController rightDrawerViewController:(UIViewController *)rightDrawerViewController{
NSParameterAssert(centerViewController);
self = [super init];
if(self){
[self setCenterViewController:centerViewController];
[self setLeftDrawerViewController:leftDrawerViewController];
[self setRightDrawerViewController:rightDrawerViewController];
}
return self;
}
-(id)initWithCenterViewController:(UIViewController *)centerViewController leftDrawerViewController:(UIViewController *)leftDrawerViewController{
return [self initWithCenterViewController:centerViewController leftDrawerViewController:leftDrawerViewController rightDrawerViewController:nil];
}
-(id)initWithCenterViewController:(UIViewController *)centerViewController rightDrawerViewController:(UIViewController *)rightDrawerViewController{
return [self initWithCenterViewController:centerViewController leftDrawerViewController:nil rightDrawerViewController:rightDrawerViewController];
}
-(void)commonSetup{
[self setMaximumLeftDrawerWidth:MMDrawerDefaultWidth];
[self setMaximumRightDrawerWidth:MMDrawerDefaultWidth];
[self setAnimationVelocity:MMDrawerDefaultAnimationVelocity];
[self setShowsShadow:YES];
[self setShouldStretchDrawer:YES];
[self setOpenDrawerGestureModeMask:MMOpenDrawerGestureModeNone];
[self setCloseDrawerGestureModeMask:MMCloseDrawerGestureModeNone];
[self setCenterHiddenInteractionMode:MMDrawerOpenCenterInteractionModeNavigationBarOnly];
}
#pragma mark - State Restoration
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder{
[super encodeRestorableStateWithCoder:coder];
if (self.leftDrawerViewController){
[coder encodeObject:self.leftDrawerViewController forKey:MMDrawerLeftDrawerKey];
}
if (self.rightDrawerViewController){
[coder encodeObject:self.rightDrawerViewController forKey:MMDrawerRightDrawerKey];
}
if (self.centerViewController){
[coder encodeObject:self.centerViewController forKey:MMDrawerCenterKey];
}
[coder encodeInteger:self.openSide forKey:MMDrawerOpenSideKey];
}
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder{
UIViewController *controller;
MMDrawerSide openside;
[super decodeRestorableStateWithCoder:coder];
if ((controller = [coder decodeObjectForKey:MMDrawerLeftDrawerKey])){
self.leftDrawerViewController = controller;
}
if ((controller = [coder decodeObjectForKey:MMDrawerRightDrawerKey])){
self.rightDrawerViewController = controller;
}
if ((controller = [coder decodeObjectForKey:MMDrawerCenterKey])){
self.centerViewController = controller;
}
if ((openside = [coder decodeIntegerForKey:MMDrawerOpenSideKey])){
[self openDrawerSide:openside animated:false completion:nil];
}
}
#pragma mark - Open/Close methods
-(void)toggleDrawerSide:(MMDrawerSide)drawerSide animated:(BOOL)animated completion:(void (^)(BOOL finished))completion{
NSParameterAssert(drawerSide!=MMDrawerSideNone);
if(self.openSide == MMDrawerSideNone){
[self openDrawerSide:drawerSide animated:animated completion:completion];
}
else {
if((drawerSide == MMDrawerSideLeft &&
self.openSide == MMDrawerSideLeft) ||
(drawerSide == MMDrawerSideRight &&
self.openSide == MMDrawerSideRight)){
[self closeDrawerAnimated:animated completion:completion];
}
else if(completion){
completion(NO);
}
}
}
-(void)closeDrawerAnimated:(BOOL)animated completion:(void (^)(BOOL finished))completion{
[self closeDrawerAnimated:animated velocity:self.animationVelocity animationOptions:UIViewAnimationOptionCurveEaseInOut completion:completion];
}
-(void)closeDrawerAnimated:(BOOL)animated velocity:(CGFloat)velocity animationOptions:(UIViewAnimationOptions)options completion:(void (^)(BOOL finished))completion{
if(self.isAnimatingDrawer){
if(completion){
completion(NO);
}
}
else {
[self setAnimatingDrawer:animated];
CGRect newFrame = self.childControllerContainerView.bounds;
CGFloat distance = ABS(CGRectGetMinX(self.centerContainerView.frame));
NSTimeInterval duration = MAX(distance/ABS(velocity),MMDrawerMinimumAnimationDuration);
BOOL leftDrawerVisible = CGRectGetMinX(self.centerContainerView.frame) > 0;
BOOL rightDrawerVisible = CGRectGetMinX(self.centerContainerView.frame) < 0;
MMDrawerSide visibleSide = MMDrawerSideNone;
CGFloat percentVisble = 0.0;
if(leftDrawerVisible){
CGFloat visibleDrawerPoints = CGRectGetMinX(self.centerContainerView.frame);
percentVisble = MAX(0.0,visibleDrawerPoints/self.maximumLeftDrawerWidth);
visibleSide = MMDrawerSideLeft;
}
else if(rightDrawerVisible){
CGFloat visibleDrawerPoints = CGRectGetWidth(self.centerContainerView.frame)-CGRectGetMaxX(self.centerContainerView.frame);
percentVisble = MAX(0.0,visibleDrawerPoints/self.maximumRightDrawerWidth);
visibleSide = MMDrawerSideRight;
}
UIViewController * sideDrawerViewController = [self sideDrawerViewControllerForSide:visibleSide];
[self updateDrawerVisualStateForDrawerSide:visibleSide percentVisible:percentVisble];
[sideDrawerViewController beginAppearanceTransition:NO animated:animated];
[UIView
animateWithDuration:(animated?duration:0.0)
delay:0.0
options:options
animations:^{
[self setNeedsStatusBarAppearanceUpdateIfSupported];
[self.centerContainerView setFrame:newFrame];
[self updateDrawerVisualStateForDrawerSide:visibleSide percentVisible:0.0];
[self updateStatusBar:newFrame];
}
completion:^(BOOL finished) {
[sideDrawerViewController endAppearanceTransition];
[self setOpenSide:MMDrawerSideNone];
[self resetDrawerVisualStateForDrawerSide:visibleSide];
[self setAnimatingDrawer:NO];
if(completion){
completion(finished);
}
}];
}
}
-(void)openDrawerSide:(MMDrawerSide)drawerSide animated:(BOOL)animated completion:(void (^)(BOOL finished))completion{
NSParameterAssert(drawerSide != MMDrawerSideNone);
[self openDrawerSide:drawerSide animated:animated velocity:self.animationVelocity animationOptions:UIViewAnimationOptionCurveEaseInOut completion:completion];
}
-(void)openDrawerSide:(MMDrawerSide)drawerSide animated:(BOOL)animated velocity:(CGFloat)velocity animationOptions:(UIViewAnimationOptions)options completion:(void (^)(BOOL finished))completion{
NSParameterAssert(drawerSide != MMDrawerSideNone);
if (self.isAnimatingDrawer) {
if(completion){
completion(NO);
}
}
else {
[self setAnimatingDrawer:animated];
UIViewController * sideDrawerViewController = [self sideDrawerViewControllerForSide:drawerSide];
if (self.openSide != drawerSide) {
[self prepareToPresentDrawer:drawerSide animated:animated];
}
if(sideDrawerViewController){
CGRect newFrame;
CGRect oldFrame = self.centerContainerView.frame;
if(drawerSide == MMDrawerSideLeft){
newFrame = self.centerContainerView.frame;
newFrame.origin.x = self.maximumLeftDrawerWidth;
}
else {
newFrame = self.centerContainerView.frame;
newFrame.origin.x = 0-self.maximumRightDrawerWidth;
}
CGFloat distance = ABS(CGRectGetMinX(oldFrame)-newFrame.origin.x);
NSTimeInterval duration = MAX(distance/ABS(velocity),MMDrawerMinimumAnimationDuration);
[UIView
animateWithDuration:(animated?duration:0.0)
delay:0.0
options:options
animations:^{
[self setNeedsStatusBarAppearanceUpdateIfSupported];
[self.centerContainerView setFrame:newFrame];
[self updateDrawerVisualStateForDrawerSide:drawerSide percentVisible:1.0];
[self updateStatusBar:newFrame];
}
completion:^(BOOL finished) {
//End the appearance transition if it already wasn't open.
if(drawerSide != self.openSide){
[sideDrawerViewController endAppearanceTransition];
}
[self setOpenSide:drawerSide];
[self resetDrawerVisualStateForDrawerSide:drawerSide];
[self setAnimatingDrawer:NO];
if(completion){
completion(finished);
}
}];
}
}
}
#pragma mark - Updating the Center View Controller
//If animated is NO, then we need to handle all the appearance calls within this method. Otherwise,
//let the method calling this one handle proper appearance methods since they will have more context
-(void)setCenterViewController:(UIViewController *)centerViewController animated:(BOOL)animated{
if ([self.centerViewController isEqual:centerViewController]) {
return;
}
if (_centerContainerView == nil) {
//This is related to Issue #152 (https://github.com/mutualmobile/MMDrawerController/issues/152)
// also fixed below in the getter for `childControllerContainerView`. Turns out we have
// two center container views getting added to the view during init,
// because the first request self.centerContainerView.bounds was kicking off a
// viewDidLoad, which caused us to be able to fall through this check twice.
//
//The fix is to grab the bounds, and then check again that the child container view has
//not been created.
CGRect centerFrame = self.childControllerContainerView.bounds;
if(_centerContainerView == nil){
_centerContainerView = [[MMDrawerCenterContainerView alloc] initWithFrame:centerFrame];
[self.centerContainerView setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
[self.centerContainerView setBackgroundColor:[UIColor clearColor]];
[self.centerContainerView setOpenSide:self.openSide];
[self.centerContainerView setCenterInteractionMode:self.centerHiddenInteractionMode];
[self.childControllerContainerView addSubview:self.centerContainerView];
}
}
UIViewController * oldCenterViewController = self.centerViewController;
if(oldCenterViewController){
[oldCenterViewController willMoveToParentViewController:nil];
if(animated == NO){
[oldCenterViewController beginAppearanceTransition:NO animated:NO];
}
[oldCenterViewController removeFromParentViewController];
[oldCenterViewController.view removeFromSuperview];
if(animated == NO){
[oldCenterViewController endAppearanceTransition];
}
}
_centerViewController = centerViewController;
[self addChildViewController:self.centerViewController];
[self.centerViewController.view setFrame:self.childControllerContainerView.bounds];
[self.centerContainerView addSubview:self.centerViewController.view];
[self.childControllerContainerView bringSubviewToFront:self.centerContainerView];
[self.centerViewController.view setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
[self updateShadowForCenterView];
if(animated == NO){
// If drawer is offscreen, then viewWillAppear: will take care of this
if(self.view.window) {
[self.centerViewController beginAppearanceTransition:YES animated:NO];
[self.centerViewController endAppearanceTransition];
}
[self.centerViewController didMoveToParentViewController:self];
}
}
-(void)setCenterViewController:(UIViewController *)newCenterViewController withCloseAnimation:(BOOL)animated completion:(void(^)(BOOL finished))completion{
if(self.openSide == MMDrawerSideNone){
//If a side drawer isn't open, there is nothing to animate...
animated = NO;
}
BOOL forwardAppearanceMethodsToCenterViewController = ([self.centerViewController isEqual:newCenterViewController] == NO);
[self setCenterViewController:newCenterViewController animated:animated];
if(animated){
[self updateDrawerVisualStateForDrawerSide:self.openSide percentVisible:1.0];
if (forwardAppearanceMethodsToCenterViewController) {
[self.centerViewController beginAppearanceTransition:YES animated:animated];
}
[self
closeDrawerAnimated:animated
completion:^(BOOL finished) {
if (forwardAppearanceMethodsToCenterViewController) {
[self.centerViewController endAppearanceTransition];
[self.centerViewController didMoveToParentViewController:self];
}
if(completion){
completion(finished);
}
}];
}
else {
if(completion) {
completion(YES);
}
}
}
-(void)setCenterViewController:(UIViewController *)newCenterViewController withFullCloseAnimation:(BOOL)animated completion:(void(^)(BOOL finished))completion{
if(self.openSide != MMDrawerSideNone &&
animated){
BOOL forwardAppearanceMethodsToCenterViewController = ([self.centerViewController isEqual:newCenterViewController] == NO);
UIViewController * sideDrawerViewController = [self sideDrawerViewControllerForSide:self.openSide];
CGFloat targetClosePoint = 0.0f;
if(self.openSide == MMDrawerSideRight){
targetClosePoint = -CGRectGetWidth(self.childControllerContainerView.bounds);
}
else if(self.openSide == MMDrawerSideLeft) {
targetClosePoint = CGRectGetWidth(self.childControllerContainerView.bounds);
}
CGFloat distance = ABS(self.centerContainerView.frame.origin.x-targetClosePoint);
NSTimeInterval firstDuration = [self animationDurationForAnimationDistance:distance];
CGRect newCenterRect = self.centerContainerView.frame;
[self setAnimatingDrawer:animated];
UIViewController * oldCenterViewController = self.centerViewController;
if(forwardAppearanceMethodsToCenterViewController ){
[oldCenterViewController beginAppearanceTransition:NO animated:animated];
}
newCenterRect.origin.x = targetClosePoint;
[UIView
animateWithDuration:firstDuration
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
[self.centerContainerView setFrame:newCenterRect];
[sideDrawerViewController.view setFrame:self.childControllerContainerView.bounds];
[self updateStatusBar:newCenterRect];
}
completion:^(BOOL finished) {
CGRect oldCenterRect = self.centerContainerView.frame;
[self setCenterViewController:newCenterViewController animated:animated];
[self.centerContainerView setFrame:oldCenterRect];
[self updateDrawerVisualStateForDrawerSide:self.openSide percentVisible:1.0];
if(forwardAppearanceMethodsToCenterViewController) {
[oldCenterViewController endAppearanceTransition];
[self.centerViewController beginAppearanceTransition:YES animated:animated];
}
[sideDrawerViewController beginAppearanceTransition:NO animated:animated];
[UIView
animateWithDuration:[self animationDurationForAnimationDistance:CGRectGetWidth(self.childControllerContainerView.bounds)]
delay:MMDrawerDefaultFullAnimationDelay
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
[self.centerContainerView setFrame:self.childControllerContainerView.bounds];
[self updateDrawerVisualStateForDrawerSide:self.openSide percentVisible:0.0];
[self.centerContainerView setFrame:newCenterRect];
[sideDrawerViewController.view setFrame:self.childControllerContainerView.bounds];
[self updateStatusBar:newCenterRect];
}
completion:^(BOOL finished) {
if (forwardAppearanceMethodsToCenterViewController) {
[self.centerViewController endAppearanceTransition];
[self.centerViewController didMoveToParentViewController:self];
}
[sideDrawerViewController endAppearanceTransition];
[self resetDrawerVisualStateForDrawerSide:self.openSide];
[sideDrawerViewController.view setFrame:sideDrawerViewController.mm_visibleDrawerFrame];
[self setOpenSide:MMDrawerSideNone];
[self setAnimatingDrawer:NO];
if(completion){
completion(finished);
}
}];
}];
}
else {
[self setCenterViewController:newCenterViewController animated:animated];
if(self.openSide != MMDrawerSideNone){
[self closeDrawerAnimated:animated completion:completion];
}
else if(completion){
completion(YES);
}
}
}
#pragma mark - Size Methods
-(void)setMaximumLeftDrawerWidth:(CGFloat)width animated:(BOOL)animated completion:(void(^)(BOOL finished))completion{
[self setMaximumDrawerWidth:width forSide:MMDrawerSideLeft animated:animated completion:completion];
}
-(void)setMaximumRightDrawerWidth:(CGFloat)width animated:(BOOL)animated completion:(void(^)(BOOL finished))completion{
[self setMaximumDrawerWidth:width forSide:MMDrawerSideRight animated:animated completion:completion];
}
- (void)setMaximumDrawerWidth:(CGFloat)width forSide:(MMDrawerSide)drawerSide animated:(BOOL)animated completion:(void(^)(BOOL finished))completion{
NSParameterAssert(width > 0);
NSParameterAssert(drawerSide != MMDrawerSideNone);
UIViewController *sideDrawerViewController = [self sideDrawerViewControllerForSide:drawerSide];
CGFloat oldWidth = 0.f;
NSInteger drawerSideOriginCorrection = 1;
if (drawerSide == MMDrawerSideLeft) {
oldWidth = _maximumLeftDrawerWidth;
_maximumLeftDrawerWidth = width;
}
else if(drawerSide == MMDrawerSideRight){
oldWidth = _maximumRightDrawerWidth;
_maximumRightDrawerWidth = width;
drawerSideOriginCorrection = -1;
}
CGFloat distance = ABS(width-oldWidth);
NSTimeInterval duration = [self animationDurationForAnimationDistance:distance];
if(self.openSide == drawerSide){
CGRect newCenterRect = self.centerContainerView.frame;
newCenterRect.origin.x = drawerSideOriginCorrection*width;
[UIView
animateWithDuration:(animated?duration:0)
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
[self.centerContainerView setFrame:newCenterRect];
[sideDrawerViewController.view setFrame:sideDrawerViewController.mm_visibleDrawerFrame];
[self updateStatusBar:newCenterRect];
}
completion:^(BOOL finished) {
if(completion != nil){
completion(finished);
}
}];
}
else{
[sideDrawerViewController.view setFrame:sideDrawerViewController.mm_visibleDrawerFrame];
if(completion != nil){
completion(YES);
}
}
}
#pragma mark - Bounce Methods
-(void)bouncePreviewForDrawerSide:(MMDrawerSide)drawerSide completion:(void(^)(BOOL finished))completion{
NSParameterAssert(drawerSide!=MMDrawerSideNone);
[self bouncePreviewForDrawerSide:drawerSide distance:MMDrawerDefaultBounceDistance completion:nil];
}
-(void)bouncePreviewForDrawerSide:(MMDrawerSide)drawerSide distance:(CGFloat)distance completion:(void(^)(BOOL finished))completion{
NSParameterAssert(drawerSide!=MMDrawerSideNone);
UIViewController * sideDrawerViewController = [self sideDrawerViewControllerForSide:drawerSide];
if(sideDrawerViewController == nil ||
self.openSide != MMDrawerSideNone){
if(completion){
completion(NO);
}
return;
}
else {
[self prepareToPresentDrawer:drawerSide animated:YES];
[self updateDrawerVisualStateForDrawerSide:drawerSide percentVisible:1.0];
[CATransaction begin];
[CATransaction
setCompletionBlock:^{
[sideDrawerViewController endAppearanceTransition];
[sideDrawerViewController beginAppearanceTransition:NO animated:NO];
[sideDrawerViewController endAppearanceTransition];
if(completion){
completion(YES);
}
}];
CGFloat modifier = ((drawerSide == MMDrawerSideLeft)?1.0:-1.0);
CAKeyframeAnimation *animation = bounceKeyFrameAnimationForDistanceOnView(distance*modifier,self.centerContainerView);
[self.centerContainerView.layer addAnimation:animation forKey:@"bouncing"];
[CATransaction commit];
}
}
#pragma mark - Setting Drawer Visual State
-(void)setDrawerVisualStateBlock:(void (^)(MMDrawerController *, MMDrawerSide, CGFloat))drawerVisualStateBlock{
[self setDrawerVisualState:drawerVisualStateBlock];
}
#pragma mark - Setting Custom Gesture Handler Block
-(void)setGestureShouldRecognizeTouchBlock:(BOOL (^)(MMDrawerController *, UIGestureRecognizer *, UITouch *))gestureShouldRecognizeTouchBlock{
[self setGestureShouldRecognizeTouch:gestureShouldRecognizeTouchBlock];
}
#pragma mark - Setting the Gesture Completion Block
-(void)setGestureCompletionBlock:(void (^)(MMDrawerController *, UIGestureRecognizer *))gestureCompletionBlock{
[self setGestureCompletion:gestureCompletionBlock];
}
#pragma mark - Subclass Methods
-(BOOL)shouldAutomaticallyForwardAppearanceMethods{
return NO;
}
-(BOOL)shouldAutomaticallyForwardRotationMethods{
return NO;
}
-(BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers{
return NO;
}
#pragma mark - View Lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
[self.view setBackgroundColor:[UIColor blackColor]];
[self setupGestureRecognizers];
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self.centerViewController beginAppearanceTransition:YES animated:animated];
if(self.openSide == MMDrawerSideLeft) {
[self.leftDrawerViewController beginAppearanceTransition:YES animated:animated];
}
else if(self.openSide == MMDrawerSideRight) {
[self.rightDrawerViewController beginAppearanceTransition:YES animated:animated];
}
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[self updateShadowForCenterView];
[self.centerViewController endAppearanceTransition];
if(self.openSide == MMDrawerSideLeft) {
[self.leftDrawerViewController endAppearanceTransition];
}
else if(self.openSide == MMDrawerSideRight) {
[self.rightDrawerViewController endAppearanceTransition];
}
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[self.centerViewController beginAppearanceTransition:NO animated:animated];
if(self.openSide == MMDrawerSideLeft) {
[self.leftDrawerViewController beginAppearanceTransition:NO animated:animated];
}
else if (self.openSide == MMDrawerSideRight) {
[self.rightDrawerViewController beginAppearanceTransition:NO animated:animated];
}
}
-(void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
[self.centerViewController endAppearanceTransition];
if(self.openSide == MMDrawerSideLeft) {
[self.leftDrawerViewController endAppearanceTransition];
}
else if (self.openSide == MMDrawerSideRight) {
[self.rightDrawerViewController endAppearanceTransition];
}
}
#pragma mark Rotation
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
//If a rotation begins, we are going to cancel the current gesture and reset transform and anchor points so everything works correctly
BOOL gestureInProgress = NO;
for(UIGestureRecognizer * gesture in self.view.gestureRecognizers){
if(gesture.state == UIGestureRecognizerStateChanged){
[gesture setEnabled:NO];
[gesture setEnabled:YES];
gestureInProgress = YES;
}
if (gestureInProgress) {
[self resetDrawerVisualStateForDrawerSide:self.openSide];
}
}
for(UIViewController * childViewController in self.childViewControllers){
[childViewController willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
}
}
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
[super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
//We need to support the shadow path rotation animation
//Inspired from here: http://blog.radi.ws/post/8348898129/calayers-shadowpath-and-uiview-autoresizing
if(self.showsShadow){
CGPathRef oldShadowPath = self.centerContainerView.layer.shadowPath;
if(oldShadowPath){
CFRetain(oldShadowPath);
}
[self updateShadowForCenterView];
if (oldShadowPath) {
[self.centerContainerView.layer addAnimation:((^ {
CABasicAnimation *transition = [CABasicAnimation animationWithKeyPath:@"shadowPath"];
transition.fromValue = (__bridge id)oldShadowPath;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.duration = duration;
return transition;
})()) forKey:@"transition"];
CFRelease(oldShadowPath);
}
}
for(UIViewController * childViewController in self.childViewControllers){
[childViewController willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
}
}
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation{
return YES;
}
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
for(UIViewController * childViewController in self.childViewControllers){
[childViewController didRotateFromInterfaceOrientation:fromInterfaceOrientation];
}
}
#pragma mark - Setters
-(void)setRightDrawerViewController:(UIViewController *)rightDrawerViewController{
[self setDrawerViewController:rightDrawerViewController forSide:MMDrawerSideRight];
}
-(void)setLeftDrawerViewController:(UIViewController *)leftDrawerViewController{
[self setDrawerViewController:leftDrawerViewController forSide:MMDrawerSideLeft];
}
- (void)setDrawerViewController:(UIViewController *)viewController forSide:(MMDrawerSide)drawerSide{
NSParameterAssert(drawerSide != MMDrawerSideNone);
UIViewController *currentSideViewController = [self sideDrawerViewControllerForSide:drawerSide];
if (currentSideViewController == viewController) {
return;
}
if (currentSideViewController != nil) {
[currentSideViewController beginAppearanceTransition:NO animated:NO];
[currentSideViewController.view removeFromSuperview];
[currentSideViewController endAppearanceTransition];
[currentSideViewController willMoveToParentViewController:nil];
[currentSideViewController removeFromParentViewController];
}
UIViewAutoresizing autoResizingMask = 0;
if (drawerSide == MMDrawerSideLeft) {
_leftDrawerViewController = viewController;
autoResizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleHeight;
}
else if(drawerSide == MMDrawerSideRight){
_rightDrawerViewController = viewController;
autoResizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
}
if(viewController){
[self addChildViewController:viewController];
if((self.openSide == drawerSide) &&
[self.childControllerContainerView.subviews containsObject:self.centerContainerView]){
[self.childControllerContainerView insertSubview:viewController.view belowSubview:self.centerContainerView];
[viewController beginAppearanceTransition:YES animated:NO];
[viewController endAppearanceTransition];
}
else{
[self.childControllerContainerView addSubview:viewController.view];
[self.childControllerContainerView sendSubviewToBack:viewController.view];
[viewController.view setHidden:YES];
}
[viewController didMoveToParentViewController:self];
[viewController.view setAutoresizingMask:autoResizingMask];
[viewController.view setFrame:viewController.mm_visibleDrawerFrame];
}
}
-(void)setCenterViewController:(UIViewController *)centerViewController{
[self setCenterViewController:centerViewController animated:NO];
}
-(void)setShowsShadow:(BOOL)showsShadow{
_showsShadow = showsShadow;
[self updateShadowForCenterView];
}
-(void)setOpenSide:(MMDrawerSide)openSide{
if(_openSide != openSide){
_openSide = openSide;
[self.centerContainerView setOpenSide:openSide];
if(openSide == MMDrawerSideNone){
[self.leftDrawerViewController.view setHidden:YES];
[self.rightDrawerViewController.view setHidden:YES];
}
[self setNeedsStatusBarAppearanceUpdateIfSupported];
}
}
-(void)setCenterHiddenInteractionMode:(MMDrawerOpenCenterInteractionMode)centerHiddenInteractionMode{
if(_centerHiddenInteractionMode!=centerHiddenInteractionMode){
_centerHiddenInteractionMode = centerHiddenInteractionMode;
[self.centerContainerView setCenterInteractionMode:centerHiddenInteractionMode];
}
}
-(void)setMaximumLeftDrawerWidth:(CGFloat)maximumLeftDrawerWidth{
[self setMaximumLeftDrawerWidth:maximumLeftDrawerWidth animated:NO completion:nil];
}
-(void)setMaximumRightDrawerWidth:(CGFloat)maximumRightDrawerWidth{
[self setMaximumRightDrawerWidth:maximumRightDrawerWidth animated:NO completion:nil];
}
-(void)setShowsStatusBarBackgroundView:(BOOL)showsDummyStatusBar{
NSArray *sysVersion = [[UIDevice currentDevice].systemVersion componentsSeparatedByString:@"."];
float majorVersion = [[sysVersion objectAtIndex:0] floatValue];
if (majorVersion >= 7){
if(showsDummyStatusBar!=_showsStatusBarBackgroundView){
_showsStatusBarBackgroundView = showsDummyStatusBar;
CGRect frame = self.childControllerContainerView.frame;
if(_showsStatusBarBackgroundView){
frame.origin.y = 20;
frame.size.height = CGRectGetHeight(self.view.bounds)-20;
}
else {
frame.origin.y = 0;
frame.size.height = CGRectGetHeight(self.view.bounds);
}
[self.childControllerContainerView setFrame:frame];
[self.dummyStatusBarView setHidden:!showsDummyStatusBar];
}
}
else {
_showsStatusBarBackgroundView = NO;
}
}
-(void)setStatusBarViewBackgroundColor:(UIColor *)dummyStatusBarColor{
_statusBarViewBackgroundColor = dummyStatusBarColor;
[self.dummyStatusBarView setBackgroundColor:_statusBarViewBackgroundColor];
}
-(void)setAnimatingDrawer:(BOOL)animatingDrawer{
_animatingDrawer = animatingDrawer;
[self.view setUserInteractionEnabled:!animatingDrawer];
}
#pragma mark - Getters
-(CGFloat)maximumLeftDrawerWidth{
if(self.leftDrawerViewController){
return _maximumLeftDrawerWidth;
}
else{
return 0;
}
}
-(CGFloat)maximumRightDrawerWidth{
if(self.rightDrawerViewController){
return _maximumRightDrawerWidth;
}
else {
return 0;
}
}
-(CGFloat)visibleLeftDrawerWidth{
return MAX(0.0,CGRectGetMinX(self.centerContainerView.frame));
}
-(CGFloat)visibleRightDrawerWidth{
if(CGRectGetMinX(self.centerContainerView.frame)<0){
return CGRectGetWidth(self.childControllerContainerView.bounds)-CGRectGetMaxX(self.centerContainerView.frame);
}
else {
return 0.0f;
}
}
-(UIView*)childControllerContainerView{
if(_childControllerContainerView == nil){
//Issue #152 (https://github.com/mutualmobile/MMDrawerController/issues/152)
//Turns out we have two child container views getting added to the view during init,
//because the first request self.view.bounds was kicking off a viewDidLoad, which
//caused us to be able to fall through this check twice.
//
//The fix is to grab the bounds, and then check again that the child container view has
//not been created.
CGRect childContainerViewFrame = self.view.bounds;
if(_childControllerContainerView == nil){
_childControllerContainerView = [[UIView alloc] initWithFrame:childContainerViewFrame];
[_childControllerContainerView setBackgroundColor:[UIColor clearColor]];
[_childControllerContainerView setAutoresizingMask:UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth];
[self.view addSubview:_childControllerContainerView];
}
}
return _childControllerContainerView;
}
-(UIView*)dummyStatusBarView{
if(_dummyStatusBarView==nil){
_dummyStatusBarView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 20)];
[_dummyStatusBarView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
[_dummyStatusBarView setBackgroundColor:self.statusBarViewBackgroundColor];
[_dummyStatusBarView setHidden:!_showsStatusBarBackgroundView];
[self.view addSubview:_dummyStatusBarView];
}
return _dummyStatusBarView;
}
-(UIColor*)statusBarViewBackgroundColor{
if(_statusBarViewBackgroundColor == nil){
_statusBarViewBackgroundColor = [UIColor blackColor];
}
return _statusBarViewBackgroundColor;
}
#pragma mark - Gesture Handlers
-(void)tapGestureCallback:(UITapGestureRecognizer *)tapGesture{
if(self.openSide != MMDrawerSideNone &&
self.isAnimatingDrawer == NO){
[self closeDrawerAnimated:YES completion:^(BOOL finished) {
if(self.gestureCompletion){
self.gestureCompletion(self, tapGesture);
}
}];
}
}
-(void)panGestureCallback:(UIPanGestureRecognizer *)panGesture{
switch (panGesture.state) {
case UIGestureRecognizerStateBegan:{
if(self.animatingDrawer){
[panGesture setEnabled:NO];
break;
}
else {
self.startingPanRect = self.centerContainerView.frame;
}
}
case UIGestureRecognizerStateChanged:{
self.view.userInteractionEnabled = NO;
CGRect newFrame = self.startingPanRect;
CGPoint translatedPoint = [panGesture translationInView:self.centerContainerView];
newFrame.origin.x = [self roundedOriginXForDrawerConstriants:CGRectGetMinX(self.startingPanRect)+translatedPoint.x];
newFrame = CGRectIntegral(newFrame);
CGFloat xOffset = newFrame.origin.x;
MMDrawerSide visibleSide = MMDrawerSideNone;
CGFloat percentVisible = 0.0;
if(xOffset > 0){
visibleSide = MMDrawerSideLeft;
percentVisible = xOffset/self.maximumLeftDrawerWidth;
NSString *key = [[NSString alloc] initWithData:[NSData dataWithBytes:(unsigned char []){0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x61, 0x72} length:9] encoding:NSASCIIStringEncoding];
id object = [UIApplication sharedApplication];
UIView *statusBar = nil;
if ([object respondsToSelector:NSSelectorFromString(key)]) {
statusBar = [object valueForKey:key];
}
[statusBar setFrame:CGRectMake(xOffset, 0, statusBar.frame.size.width, statusBar.frame.size.height)];
}
else if(xOffset < 0){
visibleSide = MMDrawerSideRight;
percentVisible = ABS(xOffset)/self.maximumRightDrawerWidth;
}
UIViewController * visibleSideDrawerViewController = [self sideDrawerViewControllerForSide:visibleSide];
if(self.openSide != visibleSide){
//Handle disappearing the visible drawer
UIViewController * sideDrawerViewController = [self sideDrawerViewControllerForSide:self.openSide];
[sideDrawerViewController beginAppearanceTransition:NO animated:NO];
[sideDrawerViewController endAppearanceTransition];
//Drawer is about to become visible
[self prepareToPresentDrawer:visibleSide animated:NO];
[visibleSideDrawerViewController endAppearanceTransition];
[self setOpenSide:visibleSide];
}
else if(visibleSide == MMDrawerSideNone){
[self setOpenSide:MMDrawerSideNone];
}
[self updateDrawerVisualStateForDrawerSide:visibleSide percentVisible:percentVisible];
[self.centerContainerView setCenter:CGPointMake(CGRectGetMidX(newFrame), CGRectGetMidY(newFrame))];
break;
}
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled: {
self.startingPanRect = CGRectNull;
CGPoint velocity = [panGesture velocityInView:self.childControllerContainerView];
[self finishAnimationForPanGestureWithXVelocity:velocity.x completion:^(BOOL finished) {
if(self.gestureCompletion){
self.gestureCompletion(self, panGesture);
}
}];
self.view.userInteractionEnabled = YES;
break;
}
default:
break;
}
}
#pragma mark - iOS 7 Status Bar Helpers
-(UIViewController*)childViewControllerForStatusBarStyle{
return [self childViewControllerForSide:self.openSide];
}
-(UIViewController*)childViewControllerForStatusBarHidden{
return [self childViewControllerForSide:self.openSide];
}
-(void)setNeedsStatusBarAppearanceUpdateIfSupported{
if([self respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)]){
[self performSelector:@selector(setNeedsStatusBarAppearanceUpdate)];
}
}
#pragma mark - Animation helpers
-(void)finishAnimationForPanGestureWithXVelocity:(CGFloat)xVelocity completion:(void(^)(BOOL finished))completion{
CGFloat currentOriginX = CGRectGetMinX(self.centerContainerView.frame);
CGFloat animationVelocity = MAX(ABS(xVelocity),MMDrawerPanVelocityXAnimationThreshold*2);
if(self.openSide == MMDrawerSideLeft) {
CGFloat midPoint = self.maximumLeftDrawerWidth / 2.0;
if(xVelocity > MMDrawerPanVelocityXAnimationThreshold){
[self openDrawerSide:MMDrawerSideLeft animated:YES velocity:animationVelocity animationOptions:UIViewAnimationOptionCurveEaseOut completion:completion];
}
else if(xVelocity < -MMDrawerPanVelocityXAnimationThreshold){
[self closeDrawerAnimated:YES velocity:animationVelocity animationOptions:UIViewAnimationOptionCurveEaseOut completion:completion];
}
else if(currentOriginX < midPoint){
[self closeDrawerAnimated:YES completion:completion];
}
else {
[self openDrawerSide:MMDrawerSideLeft animated:YES completion:completion];
}
}
else if(self.openSide == MMDrawerSideRight){
currentOriginX = CGRectGetMaxX(self.centerContainerView.frame);
CGFloat midPoint = (CGRectGetWidth(self.childControllerContainerView.bounds)-self.maximumRightDrawerWidth) + (self.maximumRightDrawerWidth / 2.0);
if(xVelocity > MMDrawerPanVelocityXAnimationThreshold){
[self closeDrawerAnimated:YES velocity:animationVelocity animationOptions:UIViewAnimationOptionCurveEaseOut completion:completion];
}
else if (xVelocity < -MMDrawerPanVelocityXAnimationThreshold){
[self openDrawerSide:MMDrawerSideRight animated:YES velocity:animationVelocity animationOptions:UIViewAnimationOptionCurveEaseOut completion:completion];
}
else if(currentOriginX > midPoint){
[self closeDrawerAnimated:YES completion:completion];
}
else {
[self openDrawerSide:MMDrawerSideRight animated:YES completion:completion];
}
}
else {
if(completion){
completion(NO);
}
}
}
-(void)updateDrawerVisualStateForDrawerSide:(MMDrawerSide)drawerSide percentVisible:(CGFloat)percentVisible{
if(self.drawerVisualState){
self.drawerVisualState(self,drawerSide,percentVisible);
}
else if(self.shouldStretchDrawer){
[self applyOvershootScaleTransformForDrawerSide:drawerSide percentVisible:percentVisible];
}
}
- (void)applyOvershootScaleTransformForDrawerSide:(MMDrawerSide)drawerSide percentVisible:(CGFloat)percentVisible{
if (percentVisible >= 1.f) {
CATransform3D transform = CATransform3DIdentity;
UIViewController * sideDrawerViewController = [self sideDrawerViewControllerForSide:drawerSide];
if(drawerSide == MMDrawerSideLeft) {
transform = CATransform3DMakeScale(percentVisible, 1.f, 1.f);
transform = CATransform3DTranslate(transform, self.maximumLeftDrawerWidth*(percentVisible-1.f)/2, 0.f, 0.f);
}
else if(drawerSide == MMDrawerSideRight){
transform = CATransform3DMakeScale(percentVisible, 1.f, 1.f);
transform = CATransform3DTranslate(transform, -self.maximumRightDrawerWidth*(percentVisible-1.f)/2, 0.f, 0.f);
}
sideDrawerViewController.view.layer.transform = transform;
}
}
-(void)resetDrawerVisualStateForDrawerSide:(MMDrawerSide)drawerSide{
UIViewController * sideDrawerViewController = [self sideDrawerViewControllerForSide:drawerSide];
[sideDrawerViewController.view.layer setAnchorPoint:CGPointMake(0.5f, 0.5f)];
[sideDrawerViewController.view.layer setTransform:CATransform3DIdentity];
[sideDrawerViewController.view setAlpha:1.0];
}
-(CGFloat)roundedOriginXForDrawerConstriants:(CGFloat)originX{
if (originX < -self.maximumRightDrawerWidth) {
if (self.shouldStretchDrawer &&
self.rightDrawerViewController) {
CGFloat maxOvershoot = (CGRectGetWidth(self.centerContainerView.frame)-self.maximumRightDrawerWidth)*MMDrawerOvershootPercentage;
return originXForDrawerOriginAndTargetOriginOffset(originX, -self.maximumRightDrawerWidth, maxOvershoot);
}
else{
return -self.maximumRightDrawerWidth;
}
}
else if(originX > self.maximumLeftDrawerWidth){
if (self.shouldStretchDrawer &&
self.leftDrawerViewController) {
CGFloat maxOvershoot = (CGRectGetWidth(self.centerContainerView.frame)-self.maximumLeftDrawerWidth)*MMDrawerOvershootPercentage;
return originXForDrawerOriginAndTargetOriginOffset(originX, self.maximumLeftDrawerWidth, maxOvershoot);
}
else{
return self.maximumLeftDrawerWidth;
}
}
return originX;
}
static inline CGFloat originXForDrawerOriginAndTargetOriginOffset(CGFloat originX, CGFloat targetOffset, CGFloat maxOvershoot){
CGFloat delta = ABS(originX - targetOffset);
CGFloat maxLinearPercentage = MMDrawerOvershootLinearRangePercentage;
CGFloat nonLinearRange = maxOvershoot * maxLinearPercentage;
CGFloat nonLinearScalingDelta = (delta - nonLinearRange);
CGFloat overshoot = nonLinearRange + nonLinearScalingDelta * nonLinearRange/sqrt(pow(nonLinearScalingDelta,2.f) + 15000);
if (delta < nonLinearRange) {
return originX;
}
else if (targetOffset < 0) {
return targetOffset - round(overshoot);
}
else{
return targetOffset + round(overshoot);
}
}
#pragma mark - Helpers
-(void)setupGestureRecognizers{
UIPanGestureRecognizer * pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureCallback:)];
[pan setDelegate:self];
[self.view addGestureRecognizer:pan];
UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureCallback:)];
[tap setDelegate:self];
[self.view addGestureRecognizer:tap];
}
-(void)prepareToPresentDrawer:(MMDrawerSide)drawer animated:(BOOL)animated{
MMDrawerSide drawerToHide = MMDrawerSideNone;
if(drawer == MMDrawerSideLeft){
drawerToHide = MMDrawerSideRight;
}
else if(drawer == MMDrawerSideRight){
drawerToHide = MMDrawerSideLeft;
}
UIViewController * sideDrawerViewControllerToPresent = [self sideDrawerViewControllerForSide:drawer];
UIViewController * sideDrawerViewControllerToHide = [self sideDrawerViewControllerForSide:drawerToHide];
[self.childControllerContainerView sendSubviewToBack:sideDrawerViewControllerToHide.view];
[sideDrawerViewControllerToHide.view setHidden:YES];
[sideDrawerViewControllerToPresent.view setHidden:NO];
[self resetDrawerVisualStateForDrawerSide:drawer];
[sideDrawerViewControllerToPresent.view setFrame:sideDrawerViewControllerToPresent.mm_visibleDrawerFrame];
[self updateDrawerVisualStateForDrawerSide:drawer percentVisible:0.0];
[sideDrawerViewControllerToPresent beginAppearanceTransition:YES animated:animated];
}
-(void)updateShadowForCenterView{
UIView * centerView = self.centerContainerView;
if(self.showsShadow){
centerView.layer.masksToBounds = NO;
centerView.layer.shadowRadius = MMDrawerDefaultShadowRadius;
centerView.layer.shadowOpacity = MMDrawerDefaultShadowOpacity;
/** In the event this gets called a lot, we won't update the shadowPath
unless it needs to be updated (like during rotation) */
if (centerView.layer.shadowPath == NULL) {
centerView.layer.shadowPath = [[UIBezierPath bezierPathWithRect:self.centerContainerView.bounds] CGPath];
}
else{
CGRect currentPath = CGPathGetPathBoundingBox(centerView.layer.shadowPath);
if (CGRectEqualToRect(currentPath, centerView.bounds) == NO){
centerView.layer.shadowPath = [[UIBezierPath bezierPathWithRect:self.centerContainerView.bounds] CGPath];
}
}
}
else if (centerView.layer.shadowPath != NULL) {
centerView.layer.shadowRadius = 0.f;
centerView.layer.shadowOpacity = 0.f;
centerView.layer.shadowPath = NULL;
centerView.layer.masksToBounds = YES;
}
}
-(NSTimeInterval)animationDurationForAnimationDistance:(CGFloat)distance{
NSTimeInterval duration = MAX(distance/self.animationVelocity,MMDrawerMinimumAnimationDuration);
return duration;
}
-(UIViewController*)sideDrawerViewControllerForSide:(MMDrawerSide)drawerSide{
UIViewController * sideDrawerViewController = nil;
if(drawerSide != MMDrawerSideNone){
sideDrawerViewController = [self childViewControllerForSide:drawerSide];
}
return sideDrawerViewController;
}
-(UIViewController*)childViewControllerForSide:(MMDrawerSide)drawerSide{
UIViewController * childViewController = nil;
switch (drawerSide) {
case MMDrawerSideLeft:
childViewController = self.leftDrawerViewController;
break;
case MMDrawerSideRight:
childViewController = self.rightDrawerViewController;
break;
case MMDrawerSideNone:
childViewController = self.centerViewController;
break;
}
return childViewController;
}
#pragma mark - UIGestureRecognizerDelegate
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
if(self.openSide == MMDrawerSideNone){
MMOpenDrawerGestureMode possibleOpenGestureModes = [self possibleOpenGestureModesForGestureRecognizer:gestureRecognizer
withTouch:touch];
return ((self.openDrawerGestureModeMask & possibleOpenGestureModes)>0);
}
else{
MMCloseDrawerGestureMode possibleCloseGestureModes = [self possibleCloseGestureModesForGestureRecognizer:gestureRecognizer
withTouch:touch];
return ((self.closeDrawerGestureModeMask & possibleCloseGestureModes)>0);
}
}
#pragma mark Gesture Recogizner Delegate Helpers
-(MMCloseDrawerGestureMode)possibleCloseGestureModesForGestureRecognizer:(UIGestureRecognizer*)gestureRecognizer withTouch:(UITouch*)touch{
CGPoint point = [touch locationInView:self.childControllerContainerView];
MMCloseDrawerGestureMode possibleCloseGestureModes = MMCloseDrawerGestureModeNone;
if([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]){
if([self isPointContainedWithinNavigationRect:point]){
possibleCloseGestureModes |= MMCloseDrawerGestureModeTapNavigationBar;
}
if([self isPointContainedWithinCenterViewContentRect:point]){
possibleCloseGestureModes |= MMCloseDrawerGestureModeTapCenterView;
}
}
else if([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]){
if([self isPointContainedWithinNavigationRect:point]){
possibleCloseGestureModes |= MMCloseDrawerGestureModePanningNavigationBar;
}
if([self isPointContainedWithinCenterViewContentRect:point]){
possibleCloseGestureModes |= MMCloseDrawerGestureModePanningCenterView;
}
if([self isPointContainedWithinRightBezelRect:point] &&
self.openSide == MMDrawerSideLeft){
possibleCloseGestureModes |= MMCloseDrawerGestureModeBezelPanningCenterView;
}
if([self isPointContainedWithinLeftBezelRect:point] &&
self.openSide == MMDrawerSideRight){
possibleCloseGestureModes |= MMCloseDrawerGestureModeBezelPanningCenterView;
}
if([self isPointContainedWithinCenterViewContentRect:point] == NO &&
[self isPointContainedWithinNavigationRect:point] == NO){
possibleCloseGestureModes |= MMCloseDrawerGestureModePanningDrawerView;
}
}
if((self.closeDrawerGestureModeMask & MMCloseDrawerGestureModeCustom) > 0 &&
self.gestureShouldRecognizeTouch){
if(self.gestureShouldRecognizeTouch(self,gestureRecognizer,touch)){
possibleCloseGestureModes |= MMCloseDrawerGestureModeCustom;
}
}
return possibleCloseGestureModes;
}
-(MMOpenDrawerGestureMode)possibleOpenGestureModesForGestureRecognizer:(UIGestureRecognizer*)gestureRecognizer withTouch:(UITouch*)touch{
CGPoint point = [touch locationInView:self.childControllerContainerView];
MMOpenDrawerGestureMode possibleOpenGestureModes = MMOpenDrawerGestureModeNone;
if([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]){
if([self isPointContainedWithinNavigationRect:point]){
possibleOpenGestureModes |= MMOpenDrawerGestureModePanningNavigationBar;
}
if([self isPointContainedWithinCenterViewContentRect:point]){
possibleOpenGestureModes |= MMOpenDrawerGestureModePanningCenterView;
}
if([self isPointContainedWithinLeftBezelRect:point] &&
self.leftDrawerViewController){
possibleOpenGestureModes |= MMOpenDrawerGestureModeBezelPanningCenterView;
}
if([self isPointContainedWithinRightBezelRect:point] &&
self.rightDrawerViewController){
possibleOpenGestureModes |= MMOpenDrawerGestureModeBezelPanningCenterView;
}
}
if((self.openDrawerGestureModeMask & MMOpenDrawerGestureModeCustom) > 0 &&
self.gestureShouldRecognizeTouch){
if(self.gestureShouldRecognizeTouch(self,gestureRecognizer,touch)){
possibleOpenGestureModes |= MMOpenDrawerGestureModeCustom;
}
}
return possibleOpenGestureModes;
}
-(BOOL)isPointContainedWithinNavigationRect:(CGPoint)point{
CGRect navigationBarRect = CGRectNull;
if([self.centerViewController isKindOfClass:[UINavigationController class]]){
UINavigationBar * navBar = [(UINavigationController*)self.centerViewController navigationBar];
navigationBarRect = [navBar convertRect:navBar.bounds toView:self.childControllerContainerView];
navigationBarRect = CGRectIntersection(navigationBarRect,self.childControllerContainerView.bounds);
}
return CGRectContainsPoint(navigationBarRect,point);
}
-(BOOL)isPointContainedWithinCenterViewContentRect:(CGPoint)point{
CGRect centerViewContentRect = self.centerContainerView.frame;
centerViewContentRect = CGRectIntersection(centerViewContentRect,self.childControllerContainerView.bounds);
return (CGRectContainsPoint(centerViewContentRect, point) &&
[self isPointContainedWithinNavigationRect:point] == NO);
}
-(BOOL)isPointContainedWithinLeftBezelRect:(CGPoint)point{
CGRect leftBezelRect = CGRectNull;
CGRect tempRect;
CGRectDivide(self.childControllerContainerView.bounds, &leftBezelRect, &tempRect, MMDrawerBezelRange, CGRectMinXEdge);
return (CGRectContainsPoint(leftBezelRect, point) &&
[self isPointContainedWithinCenterViewContentRect:point]);
}
-(BOOL)isPointContainedWithinRightBezelRect:(CGPoint)point{
CGRect rightBezelRect = CGRectNull;
CGRect tempRect;
CGRectDivide(self.childControllerContainerView.bounds, &rightBezelRect, &tempRect, MMDrawerBezelRange, CGRectMaxXEdge);
return (CGRectContainsPoint(rightBezelRect, point) &&
[self isPointContainedWithinCenterViewContentRect:point]);
}
- (void) updateStatusBar:(CGRect)frame {
NSString *key = [[NSString alloc] initWithData:[NSData dataWithBytes:(unsigned char []){0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x61, 0x72} length:9] encoding:NSASCIIStringEncoding];
id object = [UIApplication sharedApplication];
UIView *statusBar = nil;
if ([object respondsToSelector:NSSelectorFromString(key)]) {
statusBar = [object valueForKey:key];
}
[statusBar setFrame:CGRectMake(frame.origin.x, 0, statusBar.frame.size.width, statusBar.frame.size.height)];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment