Instantly share code, notes, and snippets.
Forked from siqin/SwipeNavigationController.h
Last active
January 1, 2016 14:39
-
Star
1
(1)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save hikui/8159256 to your computer and use it in GitHub Desktop.
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
/* | |
##################################################################### | |
# File : MHGSwipeNavigationController.h | |
# Project : StockBar | |
# Created : 13-12-30 | |
# DevTeam : Thomas | |
# Author : 缪 和光 | |
# Notes : | |
##################################################################### | |
### Change Logs ################################################### | |
##################################################################### | |
--------------------------------------------------------------------- | |
# Date : | |
# Author: | |
# Notes : | |
# | |
##################################################################### | |
*/ | |
#import <UIKit/UIKit.h> | |
@interface EMSwipeNavigationController : UINavigationController<UINavigationControllerDelegate> | |
/** | |
* You can disable this gesture where you do not want the "swipe to pop" feature. | |
* But you should NEVER set the delegate of this gesture recognizer to your own class, | |
* since I have to use this delegate to ignore the vertical pan. | |
*/ | |
@property (nonatomic, strong, readonly) UIPanGestureRecognizer *mhg_popGestureRecognizer; | |
/** | |
* You should ALWAYS use this property as the delegate instead of self.navigationController.delegate. | |
*/ | |
@property (nonatomic, unsafe_unretained) id<UINavigationControllerDelegate> secondLevelDelegate; | |
@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
/* | |
##################################################################### | |
# File : MHGSwipeNavigationController.m | |
# Project : StockBar | |
# Created : 13-12-30 | |
# DevTeam : Thomas | |
# Author : 缪 和光 | |
# Notes : | |
##################################################################### | |
### Change Logs ################################################### | |
##################################################################### | |
--------------------------------------------------------------------- | |
# Date : | |
# Author: | |
# Notes : | |
# | |
##################################################################### | |
*/ | |
#if !__has_feature(objc_arc) | |
#error "This file should be compiled with ARC!" | |
#endif | |
#import "EMSwipeNavigationController.h" | |
#define KEY_WINDOW [[UIApplication sharedApplication] keyWindow] | |
#define TOP_VIEW (KEY_WINDOW.rootViewController.view) | |
@interface EMSwipeNavigationController () <UIGestureRecognizerDelegate> | |
@property (nonatomic, strong) NSMutableArray *snapshotStack; | |
@property (nonatomic, strong) UIImageView *snapshotImageView; | |
@property (nonatomic, assign) CGFloat lastTouchX; | |
@property (nonatomic, assign) dispatch_queue_t imageCompressQueue; | |
@end | |
@implementation EMSwipeNavigationController | |
- (id)initWithCoder:(NSCoder *)aDecoder { | |
self = [super initWithCoder:aDecoder]; | |
if (self) { | |
[self commonInit]; | |
} | |
return self; | |
} | |
- (id)initWithRootViewController:(UIViewController *)rootViewController { | |
self = [super initWithRootViewController:rootViewController]; | |
if (self) { | |
[self commonInit]; | |
} | |
return self; | |
} | |
- (id)init { | |
self = [super init]; | |
if (self) { | |
[self commonInit]; | |
} | |
return self; | |
} | |
- (void)commonInit { | |
if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) { | |
self.interactivePopGestureRecognizer.enabled = NO; | |
} | |
self.delegate = self; | |
_snapshotImageView = [[UIImageView alloc]init]; | |
_imageCompressQueue = dispatch_queue_create("EMSwipeNavigationControllerImageCompressQueue", NULL); | |
} | |
- (void)dealloc | |
{ | |
if (_imageCompressQueue) { | |
dispatch_release(_imageCompressQueue); | |
_imageCompressQueue = NULL; | |
} | |
} | |
- (void)viewDidLoad | |
{ | |
[super viewDidLoad]; | |
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)]; | |
[self.view addGestureRecognizer:panGestureRecognizer]; | |
_mhg_popGestureRecognizer = panGestureRecognizer; | |
_mhg_popGestureRecognizer.delegate = self; | |
} | |
- (void)viewWillAppear:(BOOL)animated | |
{ | |
[super viewWillAppear:animated]; | |
// [self addShadowForView]; | |
} | |
- (void)viewWillDisappear:(BOOL)animated { | |
[super viewWillDisappear:animated]; | |
[self.snapshotImageView removeFromSuperview]; | |
} | |
#pragma mark - capture last view's snapshot | |
- (UIImage *)takeSnapshot | |
{ | |
UIGraphicsBeginImageContextWithOptions(TOP_VIEW.bounds.size, YES, 0.0); | |
[TOP_VIEW.layer renderInContext:UIGraphicsGetCurrentContext()]; | |
UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext(); | |
UIGraphicsEndImageContext(); | |
return snapshot; | |
} | |
#pragma mark - override push | |
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated | |
{ | |
if (self.viewControllers.count != 0 || self.view.superview != nil) { | |
// if self.view is in the view hierarchy or it has more than 1 viewController, then the snapshot should be taken. | |
if (!self.snapshotStack) { | |
self.snapshotStack = [[NSMutableArray alloc] initWithCapacity:5]; | |
} | |
UIImage *snapshot = [self takeSnapshot]; | |
self.snapshotImageView.image = snapshot; | |
dispatch_async(self.imageCompressQueue, ^{ | |
NSData *snapshotData = UIImagePNGRepresentation(snapshot); | |
if (snapshotData) [self.snapshotStack addObject:snapshotData]; | |
}); | |
} | |
[super pushViewController:viewController animated:animated]; | |
} | |
- (UIViewController *)popViewControllerAnimated:(BOOL)animated | |
{ | |
if (self.viewControllers.count != 0 || self.view.superview != nil) { | |
dispatch_async(self.imageCompressQueue, ^{ | |
[self.snapshotStack removeLastObject]; | |
NSData *snapshotData = [self.snapshotStack lastObject]; | |
if (snapshotData) { | |
UIImage *snapshot = [UIImage imageWithData:snapshotData]; | |
dispatch_async(dispatch_get_main_queue(), ^{ | |
self.snapshotImageView.image = snapshot; | |
}); | |
} | |
}); | |
} | |
return [super popViewControllerAnimated:animated]; | |
} | |
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated { | |
NSArray *popedStack = [super popToViewController:viewController animated:animated]; | |
dispatch_async(self.imageCompressQueue, ^{ | |
//出图片栈 | |
[self.snapshotStack removeObjectsInRange:NSMakeRange(self.snapshotStack.count - popedStack.count, popedStack.count)]; | |
NSData *snapshotData = [self.snapshotStack lastObject]; | |
if (snapshotData) { | |
UIImage *snapshot = [UIImage imageWithData:snapshotData]; | |
dispatch_async(dispatch_get_main_queue(), ^{ | |
self.snapshotImageView.image = snapshot; | |
}); | |
} | |
}); | |
return popedStack; | |
} | |
- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated { | |
dispatch_sync(self.imageCompressQueue, ^{ | |
[self.snapshotStack removeAllObjects]; | |
}); | |
return [super popToRootViewControllerAnimated:animated]; | |
} | |
#pragma mark - handlePanGesture | |
- (void)handlePanGesture:(UIPanGestureRecognizer *)panGestureRecognizer | |
{ | |
if (self.viewControllers.count <= 1) return ; | |
CGPoint point = [panGestureRecognizer locationInView:KEY_WINDOW]; | |
if (panGestureRecognizer.state == UIGestureRecognizerStateBegan) { | |
[self ajustSnapshotFrame]; | |
[self addShadowForView]; | |
self.snapshotImageView.hidden = NO; | |
[TOP_VIEW.superview insertSubview:self.snapshotImageView belowSubview:TOP_VIEW]; | |
self.lastTouchX = point.x; | |
} else if (panGestureRecognizer.state == UIGestureRecognizerStateChanged) { | |
CGRect frame = TOP_VIEW.frame; | |
CGFloat newX = (point.x - self.lastTouchX) + frame.origin.x; | |
if (newX < 0) { | |
return; | |
} | |
frame.origin.x = newX; | |
TOP_VIEW.frame = frame; | |
self.lastTouchX = point.x; | |
[self offsetImageViewForX:newX]; | |
} else { | |
[self judgeToPushOrPop]; | |
} | |
} | |
- (void)ajustSnapshotFrame | |
{ | |
CGRect imageViewFrame = TOP_VIEW.bounds; | |
imageViewFrame.origin.x = -TOP_VIEW.bounds.size.width / 3; | |
self.snapshotImageView.frame = imageViewFrame; | |
} | |
#pragma mark - judgeToPushOrPop | |
- (void)judgeToPushOrPop | |
{ | |
__block CGRect frame = TOP_VIEW.frame; | |
if (frame.origin.x > (frame.size.width / 3)) { | |
[UIView animateWithDuration:0.3 animations:^{ | |
frame.origin.x = frame.size.width; | |
TOP_VIEW.frame = frame; | |
[self offsetImageViewForX:frame.origin.x]; | |
} completion:^(BOOL finished) { | |
[self popViewControllerAnimated:NO]; | |
self.snapshotImageView.hidden = YES; | |
[self removeShadowForView]; | |
frame.origin.x = 0; | |
TOP_VIEW.frame = frame; | |
}]; | |
} else { | |
[UIView animateWithDuration:0.3 animations:^{ | |
frame.origin.x = 0; | |
TOP_VIEW.frame = frame; | |
[self offsetImageViewForX:frame.origin.x]; | |
} completion:^(BOOL finished) { | |
self.snapshotImageView.hidden = YES; | |
[self removeShadowForView]; | |
}]; | |
} | |
} | |
- (void)offsetImageViewForX:(CGFloat)x { | |
CGFloat imageViewX = (x - TOP_VIEW.bounds.size.width) /3 ; | |
CGRect imageViewFrame = self.snapshotImageView.frame; | |
imageViewFrame.origin.x = imageViewX; | |
self.snapshotImageView.frame = imageViewFrame; | |
} | |
- (void)addShadowForView { | |
UIView *shadowedView = TOP_VIEW; | |
UIBezierPath* newShadowPath = [UIBezierPath bezierPathWithRect:shadowedView.bounds]; | |
shadowedView.layer.masksToBounds = NO; | |
shadowedView.layer.shadowRadius = 10; | |
shadowedView.layer.shadowOpacity = 1; | |
shadowedView.layer.shadowColor = [[UIColor blackColor] CGColor]; | |
shadowedView.layer.shadowOffset = CGSizeZero; | |
shadowedView.layer.shadowPath = [newShadowPath CGPath]; | |
} | |
- (void)removeShadowForView { | |
UIView *shadowedView = TOP_VIEW; | |
shadowedView.layer.masksToBounds = YES; | |
shadowedView.layer.shadowRadius = 0; | |
shadowedView.layer.shadowPath = NULL; | |
} | |
#pragma mark - gesture recognizer delegate | |
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { | |
if (gestureRecognizer == self.mhg_popGestureRecognizer) { | |
CGPoint velocity = [self.mhg_popGestureRecognizer velocityInView:self.view]; | |
return ABS(velocity.x) > ABS(velocity.y); // Horizontal panning | |
}else{ | |
return YES; | |
} | |
} | |
#pragma mark UINavigationControllerDelegate | |
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated | |
{ | |
self.mhg_popGestureRecognizer.enabled = NO; | |
if ([self.secondLevelDelegate respondsToSelector:@selector(navigationController:willShowViewController:animated:)]) { | |
[self.secondLevelDelegate navigationController:navigationController willShowViewController:viewController animated:animated]; | |
} | |
} | |
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated | |
{ | |
self.mhg_popGestureRecognizer.enabled = YES; | |
if ([self.secondLevelDelegate respondsToSelector:@selector(navigationController:didShowViewController:animated:)]) { | |
[self.secondLevelDelegate navigationController:navigationController didShowViewController:viewController animated:animated]; | |
} | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment