diff --git a/Example/PSStackedViewExample.xcodeproj/project.pbxproj b/Example/PSStackedViewExample.xcodeproj/project.pbxproj index e59b73c..18a9d08 100644 --- a/Example/PSStackedViewExample.xcodeproj/project.pbxproj +++ b/Example/PSStackedViewExample.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 1F82478114EB76AD00773D9B /* popIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 1F82478014EB76AD00773D9B /* popIcon.png */; }; 1FEE7FF814C8E23200424F88 /* PSStackedViewSegue.m in Sources */ = {isa = PBXBuildFile; fileRef = 1FEE7FF714C8E23200424F88 /* PSStackedViewSegue.m */; }; 3798AD7B13E0B299004C1E33 /* error.png in Resources */ = {isa = PBXBuildFile; fileRef = 3798AD7413E0B299004C1E33 /* error.png */; }; 3798AD7D13E0B299004C1E33 /* error@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 3798AD7613E0B299004C1E33 /* error@2x.png */; }; @@ -39,6 +40,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 1F82478014EB76AD00773D9B /* popIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = popIcon.png; sourceTree = ""; }; 1FEE7FF614C8E23200424F88 /* PSStackedViewSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PSStackedViewSegue.h; path = ../../PSStackedView/PSStackedViewSegue.h; sourceTree = ""; }; 1FEE7FF714C8E23200424F88 /* PSStackedViewSegue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PSStackedViewSegue.m; path = ../../PSStackedView/PSStackedViewSegue.m; sourceTree = ""; }; 3798AD7413E0B299004C1E33 /* error.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = error.png; sourceTree = ""; }; @@ -209,6 +211,7 @@ 7884393E13CEF82D00D18A56 /* Images */ = { isa = PBXGroup; children = ( + 1F82478014EB76AD00773D9B /* popIcon.png */, 3798AD7313E0B299004C1E33 /* error */, 3798AD7713E0B299004C1E33 /* NewGlow */, 78BA6A1313CF05C200DDA16E /* 08-chat.png */, @@ -283,6 +286,7 @@ 3798AD7D13E0B299004C1E33 /* error@2x.png in Resources */, 3798AD7E13E0B299004C1E33 /* NewGlow.png in Resources */, 3798AD8013E0B299004C1E33 /* NewGlow@2x.png in Resources */, + 1F82478114EB76AD00773D9B /* popIcon.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Example/StackedViewKitExample/AppDelegate.m b/Example/StackedViewKitExample/AppDelegate.m index 62cefd9..5ff626e 100644 --- a/Example/StackedViewKitExample/AppDelegate.m +++ b/Example/StackedViewKitExample/AppDelegate.m @@ -26,6 +26,9 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( // set root controller as stack controller ExampleMenuRootController *menuController = [[ExampleMenuRootController alloc] init]; self.stackController = [[PSStackedViewController alloc] initWithRootViewController:menuController]; + self.stackController.enablePopOffOnDragRight = YES; + self.stackController.popOffType = SVPopOptionAllButFirst; + self.stackController.delegate = menuController; self.window.rootViewController = self.stackController; [self.window makeKeyAndVisible]; diff --git a/Example/StackedViewKitExample/ExampleMenuRootController.h b/Example/StackedViewKitExample/ExampleMenuRootController.h index beb5095..46b37a1 100644 --- a/Example/StackedViewKitExample/ExampleMenuRootController.h +++ b/Example/StackedViewKitExample/ExampleMenuRootController.h @@ -8,8 +8,10 @@ #import -@interface ExampleMenuRootController : UIViewController { +@interface ExampleMenuRootController : UIViewController { UITableView *menuTable_; + UIImageView *popIconLeft_; + UIImageView *popIconRight_; NSArray *cellContents_; } diff --git a/Example/StackedViewKitExample/ExampleMenuRootController.m b/Example/StackedViewKitExample/ExampleMenuRootController.m index 4c4687c..38ae9d3 100644 --- a/Example/StackedViewKitExample/ExampleMenuRootController.m +++ b/Example/StackedViewKitExample/ExampleMenuRootController.m @@ -23,12 +23,16 @@ @interface ExampleMenuRootController() @property (nonatomic, strong) UITableView *menuTable; @property (nonatomic, strong) NSArray *cellContents; +@property (nonatomic, strong) UIImageView *popIconLeft; +@property (nonatomic, strong) UIImageView *popIconRight; @end @implementation ExampleMenuRootController @synthesize menuTable = menuTable_; @synthesize cellContents = cellContents_; +@synthesize popIconLeft = popIconLeft_; +@synthesize popIconRight = popIconRight_; /////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - NSObject @@ -69,6 +73,16 @@ - (void)viewDidLoad { self.menuTable.dataSource = self; [self.view addSubview:self.menuTable]; [self.menuTable reloadData]; + + self.popIconLeft = [[UIImageView alloc] initWithFrame:CGRectMake(225, 482, 50, 70)]; + self.popIconLeft.image = [UIImage imageNamed:@"popIcon.png"]; + self.popIconLeft.alpha = 0.0; + [self.view addSubview:self.popIconLeft]; + + self.popIconRight = [[UIImageView alloc] initWithFrame:CGRectMake(245, 502, 50, 70)]; + self.popIconRight.image = [UIImage imageNamed:@"popIcon.png"]; + self.popIconRight.alpha = 0.0; + [self.view addSubview:self.popIconRight]; } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -145,4 +159,43 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath } } +/// PSStackedViewDelegate methods + +- (void)stackedViewDidStartDragging:(PSStackedViewController *)stackedView { + if([stackedView.viewControllers count] > 1) { + [UIView animateWithDuration:0.2 animations:^(void) { + self.popIconLeft.alpha = 1.0; + self.popIconRight.alpha = 1.0; + }]; + } +} + +- (void)stackedViewDidStopDragging:(PSStackedViewController *)stackedView { + [UIView animateWithDuration:0.2 animations:^(void) { + self.popIconLeft.alpha = 0.0; + self.popIconRight.alpha = 0.0; + self.popIconRight.transform = CGAffineTransformIdentity; + }]; +} + +-(void)stackedView:(PSStackedViewController *)stackedView WillPopViewControllers:(NSArray *)controllers { + if([controllers count] > 0) { + [UIView animateWithDuration:0.2 animations:^(void) { + self.popIconRight.alpha = 0.5; + CGAffineTransform trans = CGAffineTransformMakeTranslation(40, 10); + trans = CGAffineTransformRotate(trans, M_PI/4); + self.popIconRight.transform = trans; + }]; + } +} + +- (void)stackedView:(PSStackedViewController *)stackedView WillNotPopViewControllers:(NSArray *)controllers { + if([controllers count] > 0) { + [UIView animateWithDuration:0.2 animations:^(void) { + self.popIconRight.alpha = 1.0; + self.popIconRight.transform = CGAffineTransformIdentity; + }]; + } +} + @end diff --git a/Example/StackedViewKitExample/Images/popIcon.png b/Example/StackedViewKitExample/Images/popIcon.png new file mode 100644 index 0000000..704f3c5 Binary files /dev/null and b/Example/StackedViewKitExample/Images/popIcon.png differ diff --git a/PSStackedView/PSStackedViewController.h b/PSStackedView/PSStackedViewController.h index 40d5756..3ea067c 100644 --- a/PSStackedView/PSStackedViewController.h +++ b/PSStackedView/PSStackedViewController.h @@ -14,9 +14,16 @@ enum { SVSnapOptionNearest, SVSnapOptionLeft, - SVSnapOptionRight + SVSnapOptionRight, + SVSnapOptionPopRight } typedef PSSVSnapOption; +enum { + SVPopOptionAllButFirst, + SVPopOptionAll, + SVPopOptionTop +} typedef PSSVPopOption; + /// StackController hosing a backside rootViewController and the stacked controllers @interface PSStackedViewController : UIViewController @@ -111,6 +118,15 @@ enum { /// Property to disable bounces @property(nonatomic, assign) BOOL enableBounces; +/// Property to enable poping off all of the stack views except the first when dragged past a specified amount +@property(nonatomic, assign) BOOL enablePopOffOnDragRight; + +/// Property to determine the type of pop off action that will be taken when entire stack is dragged to the right +@property(nonatomic, assign) PSSVPopOption popOffType; + +/// Property to determine the distance the stack has to be dragged to the right to trigger popOff +@property(nonatomic, assign) NSInteger popOffDragDistance; + /// left inset thats always visible. Defaults to 60. @property(nonatomic, assign) NSUInteger leftInset; /// animate setting of the left inset that is always visible diff --git a/PSStackedView/PSStackedViewController.m b/PSStackedView/PSStackedViewController.m index e9aba1d..cbafcc6 100644 --- a/PSStackedView/PSStackedViewController.m +++ b/PSStackedView/PSStackedViewController.m @@ -18,6 +18,7 @@ #define kPSSVStackAnimationPopDuration kPSSVStackAnimationSpeedModifier * 0.25f #define kPSSVMaxSnapOverOffset 20 #define kPSSVAssociatedBaseViewControllerKey @"kPSSVAssociatedBaseViewController" +#define kPSSVDefaultPopOffDragDistance 150 // reduces alpha over overlapped view controllers. 1.f would totally black-out on complete overlay #define kAlphaReductRatio 10.f @@ -35,11 +36,18 @@ @interface PSStackedViewController() { BOOL lastDragDividedOne_; NSInteger lastVisibleIndexBeforeRotation_; BOOL enableBounces_; + BOOL enablePopOffOnDragRight_; + PSSVPopOption popOffType_; + NSInteger popOffDragDistance_; struct { unsigned int delegateWillInsertViewController:1; unsigned int delegateDidInsertViewController:1; unsigned int delegateWillRemoveViewController:1; - unsigned int delegateDidRemoveViewController:1; + unsigned int delegateDidRemoveViewController:1; + unsigned int delegateDidStartDragging:1; + unsigned int delegateDidStopDragging:1; + unsigned int delegateWillPopViewControllers:1; + unsigned int delegateWillNotPopViewControllers:1; }delegateFlags_; } @property(nonatomic, strong) UIViewController *rootViewController; @@ -60,6 +68,9 @@ @implementation PSStackedViewController @synthesize delegate = delegate_; @synthesize reduceAnimations = reduceAnimations_; @synthesize enableBounces = enableBounces_; +@synthesize enablePopOffOnDragRight = enablePopOffOnDragRight_; +@synthesize popOffType = popOffType_; +@synthesize popOffDragDistance = popOffDragDistance_; @dynamic firstVisibleIndex; #ifdef ALLOW_SWIZZLING_NAVIGATIONCONTROLLER @@ -79,6 +90,7 @@ - (id)initWithRootViewController:(UIViewController *)rootViewController; { // set some reasonble defaults leftInset_ = 60; largeLeftInset_ = 200; + popOffDragDistance_ = kPSSVDefaultPopOffDragDistance; // add a gesture recognizer to detect dragging to the guest controllers UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanFrom:)]; @@ -123,6 +135,10 @@ - (void)setDelegate:(id)delegate { delegateFlags_.delegateDidInsertViewController = [delegate respondsToSelector:@selector(stackedView:didInsertViewController:)]; delegateFlags_.delegateWillRemoveViewController = [delegate respondsToSelector:@selector(stackedView:willRemoveViewController:)]; delegateFlags_.delegateDidRemoveViewController = [delegate respondsToSelector:@selector(stackedView:didRemoveViewController:)]; + delegateFlags_.delegateDidStartDragging = [delegate respondsToSelector:@selector(stackedViewDidStartDragging:)]; + delegateFlags_.delegateDidStopDragging = [delegate respondsToSelector:@selector(stackedViewDidStopDragging:)]; + delegateFlags_.delegateWillPopViewControllers = [delegate respondsToSelector:@selector(stackedView:WillPopViewControllers:)]; + delegateFlags_.delegateWillNotPopViewControllers = [delegate respondsToSelector:@selector(stackedView:WillNotPopViewControllers:)]; } } @@ -775,6 +791,9 @@ - (void)handlePanFrom:(UIPanGestureRecognizer *)recognizer { // set up designated drag destination if (state == UIGestureRecognizerStateBegan) { + if (delegateFlags_.delegateDidStartDragging) { + [delegate_ stackedViewDidStartDragging:self]; + } if (offset > 0) { lastDragOption_ = SVSnapOptionRight; }else { @@ -792,6 +811,44 @@ - (void)handlePanFrom:(UIPanGestureRecognizer *)recognizer { lastDragOffset_ = translatedPoint.x; } + if(self.enablePopOffOnDragRight && [viewControllers_ count] > 0) { + UIViewController* fvc = (UIViewController*)[viewControllers_ objectAtIndex:0]; + NSInteger currentDragDistance = (fvc.containerView.left - largeLeftInset_); + + if(currentDragDistance > popOffDragDistance_ && lastDragOption_ != SVSnapOptionPopRight) { + lastDragOption_ = SVSnapOptionPopRight; + if(delegateFlags_.delegateWillPopViewControllers) { + NSArray* toPop; + if(self.popOffType == SVPopOptionAll) { + toPop = [self.viewControllers copy]; + } + else if(self.popOffType == SVPopOptionAllButFirst) { + toPop = [self.viewControllers subarrayWithRange:NSMakeRange(1, [self.viewControllers count] - 1)]; + } + else if(self.popOffType == SVPopOptionTop) { + toPop = [self.viewControllers subarrayWithRange:NSMakeRange([self.viewControllers count] - 1, 1)]; + } + [delegate_ stackedView:self WillPopViewControllers:toPop]; + } + } + else if(currentDragDistance < popOffDragDistance_ && lastDragOption_ == SVSnapOptionPopRight) { + lastDragOption_ = SVSnapOptionNearest; + if(delegateFlags_.delegateWillNotPopViewControllers) { + NSArray* toPop; + if(self.popOffType == SVPopOptionAll) { + toPop = [self.viewControllers copy]; + } + else if(self.popOffType == SVPopOptionAllButFirst) { + toPop = [self.viewControllers subarrayWithRange:NSMakeRange(1, [self.viewControllers count] - 1)]; + } + else if(self.popOffType == SVPopOptionTop) { + toPop = [self.viewControllers subarrayWithRange:NSMakeRange([self.viewControllers count] - 1, 1)]; + } + [delegate_ stackedView:self WillNotPopViewControllers:toPop]; + } + } + } + // perform snapping after gesture ended BOOL gestureEnded = state == UIGestureRecognizerStateEnded; if (gestureEnded) { @@ -800,11 +857,30 @@ - (void)handlePanFrom:(UIPanGestureRecognizer *)recognizer { self.floatIndex = [self nearestValidFloatIndex:self.floatIndex round:PSSVRoundDown]; }else if(lastDragOption_ == SVSnapOptionLeft) { self.floatIndex = [self nearestValidFloatIndex:self.floatIndex round:PSSVRoundUp]; - }else { + }else if(lastDragOption_ == SVSnapOptionPopRight) { + self.floatIndex = 0.0; + if(self.popOffType == SVPopOptionAll) { + [self popToRootViewControllerAnimated:YES]; + } + else if(self.popOffType == SVPopOptionAllButFirst) { + [self popToViewController:[self.viewControllers objectAtIndex:0] animated:YES]; + } + else if(self.popOffType == SVPopOptionTop) { + if([self.viewControllers count] == 1) + [self popToRootViewControllerAnimated:YES]; + else + [self popToViewController:[self.viewControllers objectAtIndex:[self.viewControllers count]-2] animated:YES]; + } + } + else { self.floatIndex = [self nearestValidFloatIndex:self.floatIndex round:PSSVRoundNearest]; } [self alignStackAnimated:YES]; + + if(delegateFlags_.delegateDidStopDragging) { + [delegate_ stackedViewDidStopDragging:self]; + } } } diff --git a/PSStackedView/PSStackedViewDelegate.h b/PSStackedView/PSStackedViewDelegate.h index 556e02d..cf277f9 100644 --- a/PSStackedView/PSStackedViewDelegate.h +++ b/PSStackedView/PSStackedViewDelegate.h @@ -26,4 +26,16 @@ /// viewController has been removed - (void)stackedView:(PSStackedViewController *)stackedView didRemoveViewController:(UIViewController *)viewController; +/// stackcontroller will pop off view controllers because of drag to right +- (void)stackedView:(PSStackedViewController*)stackedView WillPopViewControllers:(NSArray*)controllers; + +/// stackcontroller will no longer pop off view controllers because of drag to right +- (void)stackedView:(PSStackedViewController *)stackedView WillNotPopViewControllers:(NSArray*)controllers; + +/// stackcontroller did start dragging stack +- (void)stackedViewDidStartDragging:(PSStackedViewController*)stackedView; + +/// stackcontroller did stop dragging stack +- (void)stackedViewDidStopDragging:(PSStackedViewController*)stackedView; + @end diff --git a/README.md b/README.md index 32594ef..76874a0 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,12 @@ window.rootViewController = self.stackController; PSStackedViewRootController's rootViewController is in the background and its left part is always visible. Adjust the size with leftInset and largeLeftInset. +PS: Added "Remove top view controller if user slides stack to the right" + +Code by: [mball-crrc](https://github.com/mball-crrc/PSStackedView/tree/feature/pop_off_on_drag_right) + +Pull merge by: [fabiosoft](https://github.com/fabiosoft) - [website](http://www.fabiosoft.com) + ## Roadmap - Add (conditional) support for the new child view controller system in iOS5 - Appledoc