Last active
December 16, 2015 07:49
-
-
Save ifournight/5401409 to your computer and use it in GitHub Desktop.
SRevealSideController and company class.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <Foundation/Foundation.h> | |
#import <UIKit/UIGestureRecognizerSubclass.h> | |
typedef NS_ENUM(NSUInteger, SDirectionPanGestureRecognizerDirection) { | |
SDirectionPanGestureRecognizerDirectionHorizontal, | |
SDirectionPanGestureRecognizerDirectionVertical | |
}; | |
// Subclass of UIPanGestureRecognizer that only enable one direction pan gesture. | |
// The intended direction should be set by the property direction. | |
@interface SDirectionPanGestureRecognizer : UIPanGestureRecognize | |
// Gesture's movement along the x axis, only valid when gesture's direction is horizontal. | |
@property (nonatomic, assign) NSUInteger moveX; | |
// Gesture's movement along the y axis, only valid when gesture's direction is vertical. | |
@property (nonatomic, assign) NSUInteger moveY; | |
// The intended direction should be set by this property. | |
@property (nonatomic, assign) SDirectionPanGestureRecognizerDirection direction; | |
// Not a designated initalizer, but a convenient one. | |
- (id)initWithTarget:(id)target action:(SEL)action direction:(SDirectionPanGestureRecognizerDirection)direction; | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import "SDirectionPanGestureRecognizer.h" | |
const static NSUInteger kDirectionPanDistanceThreshold = 5; | |
@interface SDirectionPanGestureRecognizer() | |
@property (nonatomic, assign) BOOL directionPan; | |
@end | |
@implementation SDirectionPanGestureRecognizer | |
// Designated initializer. | |
- (id)initWithTarget:(id)target action:(SEL)action | |
{ | |
self = [super initWithTarget:target action:action]; | |
if (self) { | |
[self reset]; | |
} | |
return self; | |
} | |
- (id)initWithTarget:(id)target action:(SEL)action direction:(SDirectionPanGestureRecognizerDirection)direction | |
{ | |
self = [self initWithTarget:target action:action]; | |
if (self) { | |
self.direction = direction; | |
} | |
} | |
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event | |
{ | |
[super touchesMoved:touches withEvent:event]; | |
if (self.state == UIGestureRecognizerStateFailed) return; | |
CGPoint nowPoint = [[touches anyObject] locationInView:self.view]; | |
CGPoint prePoint = [[touches anyObject] locationInView:self.view]; | |
self.moveX += nowPoint.x - prePoint.x; | |
self.moveY += nowPoint.y - prePoint.y; | |
if (!directionPan) { | |
if (self.direction == SDirectionPanGestureRecognizerDirectionHorizontal) { | |
if (abs(self.moveX) > kDirectionPanDistanceThreshold) { | |
self.directionPan = YES; | |
} else { | |
self.state = UIGestureRecognizerStateFailed; | |
} | |
} else if (self.direction == SDirectionPanGestureRecognizerDirectionVertical) { | |
if (abs(self.moveY) > kDirectionPanDistanceThreshold) { | |
self.directionPan = YES; | |
} else { | |
self.state = UIGestureRecognizerStateFailed; | |
} | |
} | |
} | |
} | |
- (void)reset | |
{ | |
[super reset]; | |
self.directionPan = NO; | |
self.moveX = 0; | |
self.moveY = 0; | |
} | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <UIKit/UIKit.h> | |
UIKIT_EXTERN NSString* SRevealSideControllerWillRevealSideControllerNotification; | |
UIKIT_EXTERN NSString* SRevealSideControllerDidRevealSideControllerNotification; | |
UIKIT_EXTERN NSString* SRevealSideControllerWillHideSideControllerNotification; | |
UIKIT_EXTERN NSString* SRevealSideControllerDidHideSideControllerNotification; | |
// Custom container controller class contain two children view controllers: | |
// Side controller, central controller. | |
// Central controller will be fixed to fill the entire application frame.(SRevealSideController will normally be a root view controller) | |
// Side controller will be hidden on the left side defaulty. And can be revealed and hided by a horizontal pan gesture or programatically. | |
// That is to say: side controller will follow a horizontal pan's movement to traverse between its hidden position to fully revealed position. | |
@interface SRevealSideController : UIViewController<UIGestureRecognizerDelegate> | |
// Side Controller. | |
@property (nonatomic, strong) UIViewController *sideController; | |
// Central Controller. | |
@property (nonatomic, strong) UIViewController *centralController; | |
// Designated Initializer. | |
- (id)initWithSideController:(UIViewController *)sideController centralController:(UIViewController *)centralController; | |
// Reveal side controller with an animation. | |
// Only valid when side controller being hidden. | |
- (void)revealSideController; | |
// Hide side controller with an animation. | |
// Only valid when side controller being fully revealed. | |
- (void)hideSideController; | |
@end | |
@interface UIViewController(SRevealSideController) | |
// If a view controller has an ancestor as a SRevealSideController, return one, otherwise return nil. | |
- (id)revealSideController; | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "SRevealSideController.h" | |
#import "SDirectionPanGestureRecognizer.h" | |
const static NSUInteger kSRevealSideControllerSideControllerWidth = 320; | |
const static NSUInteger kSRevealSideControllerSideControllerShadowWidth = 10; | |
const static NSUInteger kSRevealSideControllerSideControllerDefiniteHideXPosition = -280; | |
const static CGFloat kSRevealSideControllerRevealHideAnimationDuration = 0.5; | |
const static CGFloat kSRevealSideControllerDimViewMaximumAlpha = 0.5; | |
const static NSString *kSRevealSideControllerSideControllerShadowImageName = @"SideController-Shadow.png"; | |
const static NSString *kSRevealSideControllerSideControllerShadowImageEdgeInsets = CGEdgeInsetsZero; // Placeholder value; | |
const NSString *SRevealSideControllerWillRevealSideControllerNotification = @"SRevealSideControllerWillRevealSideControllerNotification"; | |
const NSString *SRevealSideControllerDidRevealSideControllerNotification = @"SRevealSideControllerDidRevealSideControllerNotification"; | |
const NSString *SRevealSideControllerWillHideSideControllerNotification = @"SRevealSideControllerWillHideSideControllerNotification"; | |
const NSString *SRevealSideControllerDidHideSideControllerNotification = @"SRevealSideControllerDidHideSideControllerNotification"; | |
typedef NS_ENUM(NSUInteger, SRevealSideControllerSideControllerState) { | |
SRevealSideControllerSideControllerStateHidden, | |
SRevealSideControllerSideControllerStateRevealed, | |
SRevealSideControllerSideControllerStateMovingFromRevealed, | |
SRevealSideControllerSideControllerStateMovingFromHidden | |
}; | |
@interface SRevealSideController() | |
// Controller's root view. | |
@property (nonatomic, strong) UIView *containerView; | |
// Wrapper superview for sideController's view and sideControllerShadowView; | |
@property (nonatomic, strong) UIView *sideControllerContainerView; | |
// Custom view object to fake as a shadow of side controller. | |
@property (nonatomic, strong) UIView *sideControllerShadowView; | |
// Custom view with black color background and alpha 0.0. | |
// Positioned below sideControllerContainerView and above central controller's view. | |
// Its size is application frame size, same as central controller. | |
// And its alpha value will increase along with side controller revealing from left to right. | |
@property (nonatomic, strong) UIView *centralControllerDimView; | |
// Horizontal pan gesture recognizer on containerView. | |
@property (nonatomic, strong) SDirectionPanGestureRecognizer *horizontalPan; | |
// Side controller state | |
@property (nonatomic, assign) SRevealSideControllerSideControllerState *sideControllerState; | |
@end | |
@implementation SRevealSideController | |
#pragma mark - Life Cycle | |
- (id)initWithSideController:(UIViewController *)sideController centralController:(UIViewController *)centralController | |
{ | |
self = [super init]; | |
if (self) { | |
_sideController = sideController; | |
_centralController = centralController; | |
} | |
return self; | |
} | |
- (void)loadView | |
{ | |
CGSize applicationFrame = [UIApplication mainScreen] frame]; | |
// Alloc and create all views. | |
_containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, applicationFrame.width, applicationFrame.height); | |
_sideControllerContainerView = [[UIView alloc] initWithFrame:CGRectMake(-kSRevealSideControllerSideControllerWidth, 0, kSRevealSideControllerSideControllerWidth, applicationFrame.height)]; | |
// Shadow view'frame is outside of side controller contrainer view's. | |
_sideControllerShadowView = [[UIView alloc] initWithFrame:CGRectMake(kSRevealSideControllerSideControllerWidth, 0, kSRevealSideControllerSideControllerShadowWidth, applicationFrame.height)]; | |
_centralControllerDimView = [[UIView alloc] initWithFrame:_containerView.frame]; | |
// Setup view's attributes and contents.(Gesture recognizer, autoresizingMask... ) | |
_containerView.multipleTouchEnabled = YES; | |
UIImage *shadowImage = [[UIImage imageNamed:kSRevealSideControllerSideControllerShadowImageName] resizableImageWithEdgeInsets:kSRevealSideControllerSideControllerShadowImageEdgeInsets]; | |
_sideControllerShadowView.layer.contents = [shadowImage CGIImage]; | |
_centralControllerDimView.backgroundColor = [UIColor blackColor]; | |
_centralControllerDimView.alpha = 0.0; | |
// AutoresizingMask. | |
_containerView.autoresizingMask = UIViewAutoresizingMaskFlexibleWidth | UIViewAutoresizingMaskFlexibleHeight | UIViewAutoresizingMaskFlexibleRightMargin | UIViewAutoresizingMaskFlexibleBottomMargin; | |
_sideControllerContainerView = UIViewAutoresizingMaskFlexibleRightMargin | UIViewAutoresizingMaskFlexibleBottomMargin; | |
_sideController.view = UIViewAutoresizingMaskFlexibleRightMargin | UIViewAutoresizingMaskFlexibleBottomMargin; | |
_sideControllerShadowView = UIViewAutoresizingMaskFlexibleRightMargin | UIViewAutoresizingMaskFlexibleBottomMargin; | |
_centralControllerDimView = UIViewAutoresizingMaskFlexibleWidth | UIViewAutoresizingMaskFlexibleHeight | UIViewAutoresizingMaskFlexibleRightMargin | UIViewAutoresizingMaskFlexibleBottomMargin; | |
_centralController.view = UIViewAutoresizingMaskFlexibleWidth | UIViewAutoresizingMaskFlexibleHeight | UIViewAutoresizingMaskFlexibleRightMargin | UIViewAutoresizingMaskFlexibleBottomMargin; | |
// View Hierachy organization. | |
self.view = _containerView; | |
[sideControllerContainerView addSubview:_sideControllerShadowView]; | |
[sideControllerContainerView addSubview:_sideController.view]; | |
[_containerView addSubview:_centralController.view]; | |
[_containerView addSubview:_centralControllerDimView]; | |
[_containerView addSubview:_sideControllerContainerView]; | |
} | |
- (void)viewDidLoad | |
{ | |
[super viewDidLoad]; | |
// Horizontal pan gesture attachment. | |
self.horizontalPan = [SDirectionPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleHorizontalPan:) direction:SDirectionPanGestureRecognizerDirectionHorizontal]; | |
[self.containerView addGestureRecognizer:self.horizontalPan]; | |
// Default side controller state. | |
self.sideControllerState = SRevealSideControllerSideControllerStateHidden; | |
} | |
#pragma mark - Side Controller, Central Controller Management and Reveal/ Hide Side Controller. | |
- (void)handleHorizontalPan:(SDirectionPanGestureRecognizer *)horizontalPan | |
{ | |
if (horizontalPan.state == UIGestureRecognizerStateChanged) { | |
if (self.sideControllerState == SRevealSideControllerSideControllerStateHidden || self.sideControllerState == SRevealSideControllerSideControllerStateRevealed) { | |
if (self.sideControllerState == SRevealSideControllerSideControllerStateHidden && horizontalPan.moveX > 0) { | |
self.sideControllerState = SRevealSideControllerSideControllerStateMovingFromHidden; | |
[[NSNotificationCenter defaultCenter] postNotificationName:SRevealSideControllerWillRevealSideControllerNotification object:self]; | |
} else if (self.sideControllerState == SRevealSideControllerSideControllerStateRevealed && horizontalPan.moveX < 0) { | |
self.sideControllerState = SRevealSideControllerSideControllerStateMovingFromRevealed; | |
[[NSNotificationCenter defaultCenter] postNotificationName:SRevealSideControllerWillHideSideControllerNotification object:self]; | |
} else { | |
return; | |
} | |
} | |
// FIXME: math calculation may be buggy. | |
NSUInteger moveX = horizontalPan.moveX; | |
NSUInteger originX = self.sideControllerState == SRevealSideControllerSideControllerStateMovingFromHidden ? -kSRevealSideControllerSideControllerWidth : 0; | |
CGFloat movePercentage = 0.0; | |
if (moveX > 0) moveX = 0; | |
if (moveX < -kSRevealSideControllerSideControllerWidth) moveX = -kSRevealSideControllerSideControllerWidth; | |
self.sideControllerContainerView.origin.x = originX + moveX; | |
movePercentage = fabs((kSRevealSideControllerSideControllerWidth + originX + moveX) / kSRevealSideControllerSideControllerWidth); | |
self.sideControllerShadowView.alpha = round(movePercentage * 100.0) / 100.0; | |
self.centralControllerDimView.alpha = round(movePercentage * 100.0) / 100.0 * kSRevealSideControllerDimViewMaximumAlpha; | |
} else if (horizontalPan.state == UIGestureRecognizerStateEnded) { | |
NSUInteger originX = self.sideControllerContainerView.origin.x; | |
if (originX == -kSRevealSideControllerSideControllerWidth) { | |
self.sideControllerState = SRevealSideControllerSideControllerStateHidden; | |
[[NSNotificationCenter defaultCenter] postNotificationName:SRevealSideControllerDidHideSideControllerNotification object:self]; | |
return; | |
} else if (originX == 0) { | |
self.sideControllerState = SRevealSideControllerSideControllerStateRevealed; | |
[[NSNotificationCenter defaultCenter] postNotificationName:SRevealSideControllerDidRevealSideControllerNotification object:self]; | |
return; | |
} | |
if (horizontalPan.moveX < 0 || originX < kSRevealSideControllerSideControllerDefiniteHideXPosition) { | |
[self hideSideController]; | |
} else { | |
[self revealSideController]; | |
} | |
} | |
} | |
- (void)revealSideController | |
{ | |
if (self.sideControllerState == SRevealSideControllerSideControllerStateHidden) { | |
self.sideControllerState == SRevealSideControllerSideControllerStateMovingFromHidden; | |
[[NSNotificationCenter defaultCenter] postNotificationName:SRevealSideControllerWillRevealSideControllerNotification object:self]; | |
} else if (self.sideControllerState == SRevealSideControllerSideControllerStateRevealed) { | |
return; | |
} | |
[UIView animateWithDuration:kSRevealSideControllerRevealHideAnimationDuration | |
delay:0.0 | |
options:UIViewAnimationOptionCurveEaseOut | |
animations:^{ | |
self.sideControllerContainerView.origin.x = 0; | |
self.sideControllerShadowView.alpha = 1.0; | |
self.centralControllerDimView.alpha = kSRevealSideControllerDimViewMaximumAlpha; | |
} | |
completion:^(BOOL finished){ | |
self.sideControllerState == SRevealSideControllerSideControllerStateRevealed; | |
[[NSNotificationCenter defaultCenter] postNotificationName:SRevealSideControllerDidRevealSideControllerNotification object:self]; | |
}]; | |
} | |
- (void)hideSideController | |
{ | |
if (self.sideControllerState == SRevealSideControllerSideControllerStateRevealed) { | |
self.sideControllerState == SRevealSideControllerSideControllerStateMovingFromRevealed; | |
[[NSNotificationCenter defaultCenter] postNotificationName:SRevealSideControllerWillHideSideControllerNotification object:self]; | |
} else if (self.sideControllerState == SRevealSideControllerSideControllerStateHidden) { | |
return; | |
} | |
[UIView animateWithDuration:kSRevealSideControllerRevealHideAnimationDuration | |
delay:0.0 | |
options:UIViewAnimationOptionCurveEaseOut | |
animations:^{ | |
self.sideControllerContainerView.origin.x = -kSRevealSideControllerSideControllerWidth; | |
self.sideControllerShadowView.alpha = 0.0; | |
self.centralControllerDimView.alpha = 0.0; | |
} completion:^(BOOL finished){ | |
self.sideControllerState == SRevealSideControllerSideControllerStateHidden; | |
[[NSNotificationCenter defaultCenter] postNotificationName:SRevealSideControllerDidHideSideControllerNotification object:self]; | |
}]; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment