├── .gitignore ├── CWStackController.podspec ├── CWStackController ├── CWStackController.h ├── CWStackController.m ├── CWStackPanGestureRecognizer.h ├── CWStackPanGestureRecognizer.m └── CWStackProtocol.h ├── CWStackControllerDemo.xcodeproj └── project.pbxproj ├── CWStackControllerDemo ├── AppDelegate.h ├── AppDelegate.m ├── CWStackControllerDemo-Info.plist ├── CWStackControllerDemo-Prefix.pch ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json ├── ScrollViewController.h ├── ScrollViewController.m ├── ScrollViewController.xib ├── TableViewController.h ├── TableViewController.m ├── en.lproj │ └── InfoPlist.strings └── main.m ├── CWStackControllerDemoTests ├── CWStackControllerDemoTests-Info.plist ├── CWStackControllerDemoTests.m └── en.lproj │ └── InfoPlist.strings ├── LICENSE ├── README.md └── demo.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | *.xcworkspace 20 | .DS_Store 21 | 22 | # CocoaPods 23 | # 24 | # We recommend against adding the Pods directory to your .gitignore. However 25 | # you should judge for yourself, the pros and cons are mentioned at: 26 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 27 | # 28 | Pods/ 29 | Podfile.lock 30 | -------------------------------------------------------------------------------- /CWStackController.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod spec lint CWFoundation.podspec' to ensure this is a 3 | # valid spec and to remove all comments including this before submitting the spec. 4 | # 5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html 6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | 11 | s.name = "CWStackController" 12 | s.version = "0.0.3" 13 | s.summary = "A UINavigationController like custom container view controller which provides fullscreen pan gesture support to POP and PUSH." 14 | s.homepage = "https://github.com/guojiubo/CWStackController" 15 | s.license = "MIT" 16 | s.author = { "guojiubo" => "guojiubo@gmail.com" } 17 | s.platform = :ios 18 | s.platform = :ios, "5.0" 19 | s.requires_arc = true 20 | s.source = { :git => "https://github.com/guojiubo/CWStackController.git", :tag => "0.0.3" } 21 | s.source_files = "CWStackController/*.{h,m}" 22 | s.frameworks = 'Foundation', 'UIKit' 23 | 24 | end 25 | -------------------------------------------------------------------------------- /CWStackController/CWStackController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CWStackController.h 3 | // CWStackControllerDemo 4 | // 5 | // Created by Guojiubo on 14-9-14. 6 | // Copyright (c) 2014年 CocoaWind. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "CWStackProtocol.h" 11 | #import "CWStackPanGestureRecognizer.h" 12 | 13 | @interface CWStackController : UIViewController 14 | 15 | // Convenience method pushes the root view controller without animation. 16 | - (instancetype)initWithRootViewController:(UIViewController *)rootViewController; 17 | 18 | // Uses a horizontal slide transition. Has no effect if the view controller is already in the stack. 19 | - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated; 20 | 21 | // Returns the popped controller. 22 | - (UIViewController *)popViewControllerAnimated:(BOOL)animated; 23 | // Pops view controllers until the one specified is on top. Returns the popped controllers. 24 | - (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated; 25 | // Pops until there's only a single view controller left on the stack. Returns the popped controllers. 26 | - (NSArray *)popToRootViewControllerAnimated:(BOOL)animated; 27 | 28 | // The current view controller stack. 29 | @property (nonatomic) NSArray *viewControllers; 30 | // If animated is YES, then simulate a push or pop depending on whether the new top view controller was previously in the stack. 31 | - (void)setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated; 32 | 33 | // The top view controller on the stack. 34 | @property (nonatomic, readonly) UIViewController *topViewController; 35 | 36 | // The build-in gesture recognizer, for interacting with other gesture recognizers. 37 | // Do not change the gestures' delegates or override the getters for these properties. 38 | @property (nonatomic, strong, readonly) CWStackPanGestureRecognizer *panGestureRecognizer; 39 | 40 | // Customizations 41 | // 42 | // The threshold to trigger a push or pop through pan gesture. 43 | // Default is 0.15, valid range: 0.0 to 1.0. 44 | @property (nonatomic, assign) CGFloat threshold; 45 | // Horizontal slide transition duration, default is 0.2 46 | @property (nonatomic, assign) NSTimeInterval durationForAnimations; 47 | // Shadow 48 | @property (nonatomic, strong) UIColor *shadowColor; 49 | @property (nonatomic, assign) CGSize shadowOffset; 50 | @property (nonatomic, assign) CGFloat shadowOpacity; 51 | 52 | - (void)setContentScrollView:(UIScrollView *)scrollView; 53 | 54 | @end 55 | 56 | @interface UIViewController (CWStackController) 57 | 58 | // If this view controller has been pushed onto a stack controller, return it. 59 | @property (nonatomic, readonly) CWStackController *stackController; 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /CWStackController/CWStackController.m: -------------------------------------------------------------------------------- 1 | // 2 | // CWStackController.m 3 | // CWStackControllerDemo 4 | // 5 | // Created by Guojiubo on 14-9-14. 6 | // Copyright (c) 2014年 CocoaWind. All rights reserved. 7 | // 8 | 9 | #import "CWStackController.h" 10 | 11 | @interface CWStackController () 12 | 13 | @property (nonatomic, strong) UIViewController *rootViewController; 14 | @property (nonatomic, strong) UIViewController *previousViewController; 15 | @property (nonatomic, strong) UIViewController *nextViewController; 16 | @property (nonatomic, strong) UIViewController *tabBarHolder; 17 | @property (nonatomic, strong) CWStackPanGestureRecognizer *panGestureRecognizer; 18 | 19 | @end 20 | 21 | @implementation CWStackController 22 | 23 | #pragma mark - Internal 24 | 25 | - (id)init 26 | { 27 | self = [super init]; 28 | if (self) { 29 | // Default customization values 30 | _threshold = 0.15f; 31 | _durationForAnimations = 0.2f; 32 | _shadowColor = [UIColor lightGrayColor]; 33 | _shadowOffset = CGSizeMake(-2.0f, 0.0f); 34 | _shadowOpacity = 0.5f; 35 | } 36 | return self; 37 | } 38 | 39 | - (void)viewDidLoad 40 | { 41 | [super viewDidLoad]; 42 | 43 | CWStackPanGestureRecognizer *recognizer = [[CWStackPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)]; 44 | recognizer.delegate = self; 45 | [self.view addGestureRecognizer:recognizer]; 46 | self.panGestureRecognizer = recognizer; 47 | 48 | [self pushViewController:self.rootViewController animated:NO]; 49 | self.rootViewController = nil; 50 | } 51 | 52 | - (CGRect)contentBounds 53 | { 54 | return [self.view bounds]; 55 | } 56 | 57 | #pragma mark - Public 58 | 59 | - (instancetype)initWithRootViewController:(UIViewController *)rootViewController 60 | { 61 | self = [self init]; 62 | if (self) { 63 | _rootViewController = rootViewController; 64 | } 65 | return self; 66 | } 67 | 68 | - (UIViewController *)topViewController 69 | { 70 | return [self.childViewControllers lastObject]; 71 | } 72 | 73 | - (NSArray *)viewControllers 74 | { 75 | return self.childViewControllers; 76 | } 77 | 78 | - (void)setViewControllers:(NSArray *)viewControllers 79 | { 80 | [self setViewControllers:viewControllers animated:NO]; 81 | } 82 | 83 | - (void)setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated 84 | { 85 | [self pushViewController:[viewControllers lastObject] animated:animated]; 86 | 87 | for (UIViewController *oldChild in self.childViewControllers) { 88 | [oldChild willMoveToParentViewController:nil]; 89 | [oldChild removeFromParentViewController]; 90 | } 91 | 92 | for (UIViewController *newChild in viewControllers) { 93 | [self addChildViewController:newChild]; 94 | [newChild didMoveToParentViewController:self]; 95 | } 96 | } 97 | 98 | - (void)pushViewController:(UIViewController *)to animated:(BOOL)animated 99 | { 100 | if ([self.viewControllers containsObject:to]) { 101 | NSLog(@"The view controller is already in the stack, nothing to be done"); 102 | return; 103 | } 104 | 105 | UIViewController *from = self.topViewController; 106 | 107 | [self addChildViewController:to]; 108 | 109 | CGRect originalFrame = [self contentBounds]; 110 | [from.view setFrame:originalFrame]; 111 | originalFrame.origin.x = originalFrame.size.width; 112 | [[to view] setFrame:originalFrame]; 113 | 114 | if (from) { 115 | [self.view insertSubview:to.view aboveSubview:[from view]]; 116 | } 117 | else { 118 | [self.view addSubview:to.view]; 119 | } 120 | 121 | // Move tabBar away with previous view 122 | if (self.tabBarController && ![from hidesBottomBarWhenPushed] && to.hidesBottomBarWhenPushed) { 123 | self.tabBarHolder = from; 124 | UITabBar *tabBar = [self.tabBarController tabBar]; 125 | [[self.tabBarHolder view] addSubview:tabBar]; 126 | } 127 | 128 | void (^animationBlock)(void) = ^(void){ 129 | CGRect finalFrame = [self contentBounds]; 130 | [[to view] setFrame:finalFrame]; 131 | 132 | finalFrame.origin.x = -finalFrame.size.width/3; 133 | [[from view] setFrame:finalFrame]; 134 | }; 135 | 136 | void (^completionBlock)(BOOL finished) = ^(BOOL finished){ 137 | [[from view] removeFromSuperview]; 138 | 139 | [to didMoveToParentViewController:self]; 140 | [to.view setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; 141 | 142 | if (to != self.rootViewController) { 143 | [self addShadowToView:to.view]; 144 | } 145 | }; 146 | 147 | if (animated) { 148 | [UIView animateWithDuration:self.durationForAnimations animations:animationBlock completion:completionBlock]; 149 | return; 150 | } 151 | 152 | animationBlock(); 153 | completionBlock(YES); 154 | } 155 | 156 | - (UIViewController *)popViewControllerAnimated:(BOOL)animated 157 | { 158 | UIViewController *popedViewController = self.topViewController; 159 | 160 | NSUInteger topIndex = [self.childViewControllers indexOfObject:self.topViewController]; 161 | if (topIndex == 0) { 162 | return nil; 163 | } 164 | 165 | UIViewController *previous = self.childViewControllers[topIndex - 1]; 166 | 167 | CGRect originalFrame = [self contentBounds]; 168 | originalFrame.origin.x = -originalFrame.size.width/3; 169 | [[previous view] setFrame:originalFrame]; 170 | 171 | [self.view insertSubview:[previous view] belowSubview:[self.topViewController view]]; 172 | 173 | void (^animationBlock)(void) = ^(void){ 174 | CGRect finalFrame = [self contentBounds]; 175 | finalFrame.origin.x = finalFrame.size.width; 176 | [[self.topViewController view] setFrame:finalFrame]; 177 | 178 | finalFrame.origin.x = 0; 179 | [[previous view] setFrame:finalFrame]; 180 | }; 181 | 182 | void (^completionBlock)(BOOL finished) = ^(BOOL finished){ 183 | // Put tabBar back 184 | if (self.tabBarController && [previous isEqual:self.tabBarHolder]) { 185 | UITabBar *tabBar = [self.tabBarController tabBar]; 186 | [[self.tabBarController view] addSubview:tabBar]; 187 | self.tabBarHolder = nil; 188 | } 189 | 190 | [self.topViewController willMoveToParentViewController:nil]; 191 | [[self.topViewController view] removeFromSuperview]; 192 | [self.topViewController removeFromParentViewController]; 193 | }; 194 | 195 | if (animated) { 196 | [UIView animateWithDuration:self.durationForAnimations animations:animationBlock completion:completionBlock]; 197 | return popedViewController; 198 | } 199 | 200 | animationBlock(); 201 | completionBlock(YES); 202 | 203 | return popedViewController; 204 | } 205 | 206 | - (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated 207 | { 208 | if (viewController == [self topViewController]) { 209 | return nil; 210 | } 211 | 212 | NSUInteger index = [self.viewControllers indexOfObject:viewController]; 213 | if (index == NSNotFound) { 214 | return nil; 215 | } 216 | 217 | NSUInteger count = [self.viewControllers count]; 218 | NSArray *popedViewControllers = [self.viewControllers subarrayWithRange:NSMakeRange(index + 1, count - index - 1)]; 219 | 220 | UIViewController *previous = viewController; 221 | 222 | CGRect originalFrame = [self contentBounds]; 223 | originalFrame.origin.x = -originalFrame.size.width/3; 224 | [[previous view] setFrame:originalFrame]; 225 | 226 | [self.view insertSubview:[previous view] belowSubview:[self.topViewController view]]; 227 | 228 | void (^animationBlock)(void) = ^(void){ 229 | CGRect finalFrame = [self contentBounds]; 230 | finalFrame.origin.x = finalFrame.size.width; 231 | [[self.topViewController view] setFrame:finalFrame]; 232 | 233 | finalFrame.origin.x = 0; 234 | [[previous view] setFrame:finalFrame]; 235 | }; 236 | 237 | void (^completionBlock)(BOOL finished) = ^(BOOL finished){ 238 | // Put tabBar back 239 | if (self.tabBarController && [previous isEqual:self.tabBarHolder]) { 240 | UITabBar *tabBar = [self.tabBarController tabBar]; 241 | [[self.tabBarController view] addSubview:tabBar]; 242 | self.tabBarHolder = nil; 243 | } 244 | 245 | [self.topViewController willMoveToParentViewController:nil]; 246 | [[self.topViewController view] removeFromSuperview]; 247 | [self.topViewController removeFromParentViewController]; 248 | 249 | for (UIViewController *popedViewController in popedViewControllers) { 250 | [popedViewController willMoveToParentViewController:nil]; 251 | [popedViewController removeFromParentViewController]; 252 | } 253 | }; 254 | 255 | if (animated) { 256 | [UIView animateWithDuration:self.durationForAnimations animations:animationBlock completion:completionBlock]; 257 | return popedViewControllers; 258 | } 259 | 260 | animationBlock(); 261 | completionBlock(YES); 262 | 263 | return popedViewControllers; 264 | } 265 | 266 | - (NSArray *)popToRootViewControllerAnimated:(BOOL)animated 267 | { 268 | return [self popToViewController:[self.viewControllers firstObject] animated:animated]; 269 | } 270 | 271 | - (void)setContentScrollView:(UIScrollView *)scrollView 272 | { 273 | [self.panGestureRecognizer setScrollView:scrollView]; 274 | } 275 | 276 | #pragma mark - Gesture 277 | 278 | - (void)resetPreviousAndNext 279 | { 280 | self.previousViewController = nil; 281 | self.nextViewController = nil; 282 | } 283 | 284 | - (void)handlePanGesture:(CWStackPanGestureRecognizer *)recognizer 285 | { 286 | if (recognizer.state == UIGestureRecognizerStateBegan) { 287 | [self resetPreviousAndNext]; 288 | 289 | if (recognizer.direction == CWStackPanGestureRecognizerDirectionPush) { 290 | if (![self.topViewController respondsToSelector:@selector(nextViewController)]) { 291 | return; 292 | } 293 | 294 | self.nextViewController = [self.topViewController performSelector:@selector(nextViewController) withObject:nil]; 295 | if (!self.nextViewController) { 296 | [self.panGestureRecognizer setState:UIGestureRecognizerStateFailed]; 297 | return; 298 | } 299 | 300 | self.previousViewController = self.topViewController; 301 | } 302 | else if (recognizer.direction == CWStackPanGestureRecognizerDirectionPop) { 303 | NSUInteger topIndex = [self.childViewControllers indexOfObject:self.topViewController]; 304 | if (topIndex == 0) { 305 | [self.panGestureRecognizer setState:UIGestureRecognizerStateFailed]; 306 | return; 307 | } 308 | 309 | self.previousViewController = self.childViewControllers[topIndex - 1]; 310 | } 311 | } 312 | 313 | if (recognizer.direction == CWStackPanGestureRecognizerDirectionPush) { 314 | if (!self.nextViewController) { 315 | [self.panGestureRecognizer setState:UIGestureRecognizerStateFailed]; 316 | return; 317 | } 318 | [self handlePushGesture:recognizer]; 319 | } 320 | 321 | if (recognizer.direction == CWStackPanGestureRecognizerDirectionPop) { 322 | if (!self.previousViewController) { 323 | [self.panGestureRecognizer setState:UIGestureRecognizerStateFailed]; 324 | return; 325 | } 326 | [self handlePopGesture:recognizer]; 327 | } 328 | } 329 | 330 | - (void)handlePushGesture:(UIPanGestureRecognizer *)recognizer 331 | { 332 | if (recognizer.state == UIGestureRecognizerStateBegan) { 333 | [self addChildViewController:self.nextViewController]; 334 | 335 | CGRect originalFrame = [self contentBounds]; 336 | originalFrame.origin.x = originalFrame.size.width; 337 | [[self.nextViewController view] setFrame:originalFrame]; 338 | [self addShadowToView:[self.nextViewController view]]; 339 | 340 | [self.view insertSubview:[self.nextViewController view] aboveSubview:[self.previousViewController view]]; 341 | 342 | if (!self.tabBarController || [self.previousViewController hidesBottomBarWhenPushed] || ![self.nextViewController hidesBottomBarWhenPushed]) { 343 | return; 344 | } 345 | 346 | self.tabBarHolder = self.previousViewController; 347 | UITabBar *tabBar = [self.tabBarController tabBar]; 348 | [[self.previousViewController view] addSubview:tabBar]; 349 | } 350 | else if (recognizer.state == UIGestureRecognizerStateChanged) { 351 | CGPoint translation = [recognizer translationInView:self.view]; 352 | 353 | CGRect frame = [[self.nextViewController view] frame]; 354 | frame.origin.x = fminf([self contentBounds].size.width, [self contentBounds].size.width + translation.x); 355 | [[self.nextViewController view] setFrame:frame]; 356 | 357 | frame = [[self.previousViewController view] frame]; 358 | frame.origin.x = fminf(0, [self contentBounds].size.width/3 * translation.x/[self contentBounds].size.width); 359 | [[self.previousViewController view] setFrame:frame]; 360 | } 361 | else if (recognizer.state == UIGestureRecognizerStateEnded || recognizer.state == UIGestureRecognizerStateCancelled) { 362 | CGRect nextFrame = [[self.nextViewController view] frame]; 363 | CGFloat percentage = (CGRectGetWidth([self contentBounds]) - nextFrame.origin.x)/CGRectGetWidth([self contentBounds]); 364 | 365 | // Push 366 | if (percentage >= self.threshold) { 367 | [UIView animateWithDuration:self.durationForAnimations animations:^{ 368 | CGRect finalFrame = [self contentBounds]; 369 | [[self.nextViewController view] setFrame:finalFrame]; 370 | 371 | finalFrame.origin.x = -finalFrame.size.width/3; 372 | [[self.previousViewController view] setFrame:finalFrame]; 373 | } completion:^(BOOL finished) { 374 | [[self.previousViewController view] removeFromSuperview]; 375 | 376 | [self.nextViewController didMoveToParentViewController:self]; 377 | [[self.nextViewController view] setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; 378 | 379 | [self resetPreviousAndNext]; 380 | }]; 381 | 382 | return; 383 | } 384 | 385 | // Cancel push 386 | [UIView animateWithDuration:self.durationForAnimations animations:^{ 387 | CGRect finalFrame = [self contentBounds]; 388 | [[self.previousViewController view] setFrame:finalFrame]; 389 | 390 | finalFrame.origin.x = finalFrame.size.width; 391 | [[self.nextViewController view] setFrame:finalFrame]; 392 | } completion:^(BOOL finished) { 393 | [self.previousViewController viewWillAppear:YES]; 394 | 395 | [self.nextViewController willMoveToParentViewController:nil]; 396 | [[self.nextViewController view] removeFromSuperview]; 397 | [self.nextViewController removeFromParentViewController]; 398 | 399 | [self.previousViewController viewDidAppear:YES]; 400 | 401 | 402 | if (self.tabBarController && [self.previousViewController isEqual:self.tabBarHolder] && ![self.previousViewController hidesBottomBarWhenPushed]) { 403 | // Put tabBar back 404 | UITabBar *tabBar = [self.tabBarController tabBar]; 405 | [[self.tabBarController view] addSubview:tabBar]; 406 | self.tabBarHolder = nil; 407 | } 408 | 409 | [self resetPreviousAndNext]; 410 | }]; 411 | } 412 | } 413 | 414 | - (void)handlePopGesture:(UIPanGestureRecognizer *)recognizer 415 | { 416 | if (recognizer.state == UIGestureRecognizerStateBegan) { 417 | CGRect originalFrame = [self contentBounds]; 418 | originalFrame.origin.x = -originalFrame.size.width/3; 419 | [[self.previousViewController view] setFrame:originalFrame]; 420 | 421 | [self.view insertSubview:[self.previousViewController view] belowSubview:[self.topViewController view]]; 422 | } 423 | else if (recognizer.state == UIGestureRecognizerStateChanged) { 424 | CGPoint translation = [recognizer translationInView:self.view]; 425 | 426 | CGRect frame = [[self.previousViewController view] frame]; 427 | frame.origin.x = -[self contentBounds].size.width/3 + [self contentBounds].size.width/3 * translation.x/[self contentBounds].size.width; 428 | [[self.previousViewController view] setFrame:frame]; 429 | 430 | frame = [[self.topViewController view] frame]; 431 | frame.origin.x = fmaxf(0, translation.x); 432 | [[self.topViewController view] setFrame:frame]; 433 | } 434 | else if (recognizer.state == UIGestureRecognizerStateEnded || recognizer.state == UIGestureRecognizerStateCancelled) { 435 | CGRect topFrame = [[self.topViewController view] frame]; 436 | CGFloat percentage = topFrame.origin.x/CGRectGetWidth([self contentBounds]); 437 | 438 | // Pop 439 | if (percentage >= self.threshold) { 440 | [UIView animateWithDuration:self.durationForAnimations animations:^{ 441 | CGRect finalFrame = [self contentBounds]; 442 | finalFrame.origin.x = finalFrame.size.width; 443 | [[self.topViewController view] setFrame:finalFrame]; 444 | 445 | finalFrame.origin.x = 0; 446 | [[self.previousViewController view] setFrame:finalFrame]; 447 | } completion:^(BOOL finished) { 448 | if (self.tabBarController && [self.previousViewController isEqual:self.tabBarHolder] && ![self.previousViewController hidesBottomBarWhenPushed]) { 449 | UITabBar *tabBar = [self.tabBarController tabBar]; 450 | [[self.tabBarController view] addSubview:tabBar]; 451 | self.tabBarHolder = nil; 452 | } 453 | 454 | [self.topViewController willMoveToParentViewController:nil]; 455 | [[self.topViewController view] removeFromSuperview]; 456 | [self.topViewController removeFromParentViewController]; 457 | 458 | [self resetPreviousAndNext]; 459 | }]; 460 | 461 | return; 462 | } 463 | 464 | // Cancel pop 465 | [UIView animateWithDuration:self.durationForAnimations animations:^{ 466 | CGRect finalFrame = [self contentBounds]; 467 | [[self.topViewController view] setFrame:finalFrame]; 468 | 469 | finalFrame.origin.x = -finalFrame.size.width/3; 470 | [[self.previousViewController view] setFrame:finalFrame]; 471 | } completion:^(BOOL finished) { 472 | [self.topViewController viewWillAppear:YES]; 473 | [self.topViewController viewDidAppear:YES]; 474 | 475 | [[self.previousViewController view] removeFromSuperview]; 476 | 477 | [self resetPreviousAndNext]; 478 | }]; 479 | } 480 | } 481 | 482 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 483 | { 484 | if ([self.panGestureRecognizer direction] != CWStackPanGestureRecognizerDirectionNone) { 485 | return YES; 486 | } 487 | 488 | return NO; 489 | } 490 | 491 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 492 | { 493 | if ([otherGestureRecognizer.view isKindOfClass:[UIScrollView class]]) { 494 | return YES; 495 | } 496 | 497 | return NO; 498 | } 499 | 500 | #pragma mark - Shadow 501 | 502 | - (void)addShadowToView:(UIView *)view 503 | { 504 | UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRect:view.bounds]; 505 | CALayer *layer = view.layer; 506 | layer.masksToBounds = NO; 507 | layer.shadowColor = [self.shadowColor CGColor]; 508 | layer.shadowOffset = self.shadowOffset; 509 | layer.shadowOpacity = self.shadowOpacity; 510 | layer.shadowPath = shadowPath.CGPath; 511 | } 512 | 513 | #pragma mark - Rotation 514 | 515 | - (BOOL)shouldAutorotate 516 | { 517 | return [self.topViewController shouldAutorotate]; 518 | } 519 | 520 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation 521 | { 522 | return [self.topViewController shouldAutorotateToInterfaceOrientation:toInterfaceOrientation]; 523 | } 524 | 525 | - (NSUInteger)supportedInterfaceOrientations 526 | { 527 | return [self.topViewController supportedInterfaceOrientations]; 528 | } 529 | 530 | @end 531 | 532 | @implementation UIViewController (CWStackController) 533 | 534 | - (CWStackController *)stackController 535 | { 536 | UIViewController *parent = self.parentViewController; 537 | while (parent) { 538 | if ([parent isKindOfClass:[CWStackController class]]) { 539 | return (CWStackController *)parent; 540 | } 541 | 542 | parent = parent.parentViewController; 543 | } 544 | return nil; 545 | } 546 | 547 | @end 548 | -------------------------------------------------------------------------------- /CWStackController/CWStackPanGestureRecognizer.h: -------------------------------------------------------------------------------- 1 | // 2 | // CWScrollViewEndDetectGestureRecognizer.h 3 | // CWStackControllerDemo 4 | // 5 | // Created by Guojiubo on 14-10-1. 6 | // Copyright (c) 2014年 CocoaWind. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | typedef NS_ENUM(NSInteger, CWStackPanGestureRecognizerDirection) { 13 | CWStackPanGestureRecognizerDirectionNone, 14 | CWStackPanGestureRecognizerDirectionPush, 15 | CWStackPanGestureRecognizerDirectionPop 16 | }; 17 | 18 | @interface CWStackPanGestureRecognizer : UIPanGestureRecognizer 19 | 20 | @property (nonatomic, weak) UIScrollView *scrollView; 21 | @property (nonatomic, readonly) CWStackPanGestureRecognizerDirection direction; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /CWStackController/CWStackPanGestureRecognizer.m: -------------------------------------------------------------------------------- 1 | // 2 | // CWScrollViewEndDetectGestureRecognizer.m 3 | // CWStackControllerDemo 4 | // 5 | // Created by Guojiubo on 14-10-1. 6 | // Copyright (c) 2014年 CocoaWind. All rights reserved. 7 | // 8 | 9 | #import "CWStackPanGestureRecognizer.h" 10 | 11 | @interface CWStackPanGestureRecognizer () 12 | 13 | @property (nonatomic, strong, getter=isFailed) NSNumber *failed; 14 | @property (nonatomic, readwrite) CWStackPanGestureRecognizerDirection direction; 15 | 16 | @end 17 | 18 | @implementation CWStackPanGestureRecognizer 19 | 20 | - (void)reset 21 | { 22 | [super reset]; 23 | self.failed = nil; 24 | self.direction = CWStackPanGestureRecognizerDirectionNone; 25 | } 26 | 27 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 28 | { 29 | [super touchesMoved:touches withEvent:event]; 30 | 31 | // 6S 3D Touch fix, if we're moving in/out of the screen discard this event 32 | for (UITouch *touch in touches) { 33 | if ([touch respondsToSelector:@selector(force)]) { 34 | CGPoint location = [touch locationInView:self.view]; 35 | CGPoint prevLocation = [touch previousLocationInView:self.view]; 36 | if (CGPointEqualToPoint(location, prevLocation)) { 37 | // Point has not moved - must be 3D Touch which is not handled by this recognizer 38 | return; 39 | } 40 | } 41 | else { 42 | break; 43 | } 44 | } 45 | 46 | if (self.state == UIGestureRecognizerStateFailed) { 47 | return; 48 | } 49 | 50 | if (self.isFailed) { 51 | if ([self.isFailed boolValue]) { 52 | self.state = UIGestureRecognizerStateFailed; 53 | } 54 | return; 55 | } 56 | 57 | CGPoint currentLocation = [touches.anyObject locationInView:self.view]; 58 | CGPoint previousLocation = [touches.anyObject previousLocationInView:self.view]; 59 | 60 | // Only analyze horizontal pan gesture 61 | CGPoint translation = CGPointMake(currentLocation.x - previousLocation.x, currentLocation.y - previousLocation.y); 62 | if (fabs(translation.y) > fabs(translation.x)) { 63 | self.state = UIGestureRecognizerStateFailed; 64 | self.failed = @YES; 65 | } 66 | else { 67 | self.failed = @NO; 68 | } 69 | 70 | // Determine direction 71 | if (currentLocation.x > previousLocation.x) { 72 | self.direction = CWStackPanGestureRecognizerDirectionPop; 73 | } 74 | else if (currentLocation.x < previousLocation.x) { 75 | self.direction = CWStackPanGestureRecognizerDirectionPush; 76 | } 77 | else { 78 | self.direction = CWStackPanGestureRecognizerDirectionNone; 79 | } 80 | 81 | // Deal with scroll view 82 | if (!self.scrollView) { 83 | return; 84 | } 85 | 86 | if (self.direction == CWStackPanGestureRecognizerDirectionPop) { 87 | CGFloat fixedOffsetX = [self.scrollView contentOffset].x + [self.scrollView contentInset].left; 88 | if (fixedOffsetX <= 0) { 89 | self.failed = @NO; 90 | } else { 91 | self.state = UIGestureRecognizerStateFailed; 92 | self.failed = @YES; 93 | } 94 | return; 95 | } 96 | 97 | if (self.direction == CWStackPanGestureRecognizerDirectionPush) { 98 | CGFloat fixedOffsetX = [self.scrollView contentOffset].x - [self.scrollView contentInset].right; 99 | 100 | if (fixedOffsetX + self.scrollView.bounds.size.width >= self.scrollView.contentSize.width) { 101 | self.failed = @NO; 102 | } else { 103 | self.state = UIGestureRecognizerStateFailed; 104 | self.failed = @YES; 105 | } 106 | } 107 | } 108 | 109 | @end 110 | -------------------------------------------------------------------------------- /CWStackController/CWStackProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // CWStackProtocol.h 3 | // CWStackControllerDemo 4 | // 5 | // Created by Guojiubo on 14-9-14. 6 | // Copyright (c) 2014年 CocoaWind. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @protocol CWStackProtocol 13 | 14 | @required 15 | - (UIViewController *)nextViewController; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /CWStackControllerDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 414A817719E54BD8000CBAC3 /* CWStackPanGestureRecognizer.m in Sources */ = {isa = PBXBuildFile; fileRef = 414A817619E54BD8000CBAC3 /* CWStackPanGestureRecognizer.m */; }; 11 | A090789B19DB2BB900085B6E /* ScrollViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A090789919DB2BB900085B6E /* ScrollViewController.m */; }; 12 | A090789C19DB2BB900085B6E /* ScrollViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = A090789A19DB2BB900085B6E /* ScrollViewController.xib */; }; 13 | A0F4578719C52CA90089A59C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A0F4578619C52CA90089A59C /* Foundation.framework */; }; 14 | A0F4578919C52CA90089A59C /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A0F4578819C52CA90089A59C /* CoreGraphics.framework */; }; 15 | A0F4578B19C52CA90089A59C /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A0F4578A19C52CA90089A59C /* UIKit.framework */; }; 16 | A0F4579119C52CA90089A59C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = A0F4578F19C52CA90089A59C /* InfoPlist.strings */; }; 17 | A0F4579319C52CA90089A59C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = A0F4579219C52CA90089A59C /* main.m */; }; 18 | A0F4579719C52CA90089A59C /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = A0F4579619C52CA90089A59C /* AppDelegate.m */; }; 19 | A0F4579919C52CA90089A59C /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A0F4579819C52CA90089A59C /* Images.xcassets */; }; 20 | A0F457A019C52CA90089A59C /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A0F4579F19C52CA90089A59C /* XCTest.framework */; }; 21 | A0F457A119C52CA90089A59C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A0F4578619C52CA90089A59C /* Foundation.framework */; }; 22 | A0F457A219C52CA90089A59C /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A0F4578A19C52CA90089A59C /* UIKit.framework */; }; 23 | A0F457AA19C52CA90089A59C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = A0F457A819C52CA90089A59C /* InfoPlist.strings */; }; 24 | A0F457AC19C52CA90089A59C /* CWStackControllerDemoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A0F457AB19C52CA90089A59C /* CWStackControllerDemoTests.m */; }; 25 | A0F457B819C52DA00089A59C /* CWStackController.m in Sources */ = {isa = PBXBuildFile; fileRef = A0F457B719C52DA00089A59C /* CWStackController.m */; }; 26 | A0F457BC19C52EBB0089A59C /* TableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A0F457BB19C52EBB0089A59C /* TableViewController.m */; }; 27 | /* End PBXBuildFile section */ 28 | 29 | /* Begin PBXContainerItemProxy section */ 30 | A0F457A319C52CA90089A59C /* PBXContainerItemProxy */ = { 31 | isa = PBXContainerItemProxy; 32 | containerPortal = A0F4577B19C52CA90089A59C /* Project object */; 33 | proxyType = 1; 34 | remoteGlobalIDString = A0F4578219C52CA90089A59C; 35 | remoteInfo = CWStackControllerDemo; 36 | }; 37 | /* End PBXContainerItemProxy section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | 414A817519E54BD8000CBAC3 /* CWStackPanGestureRecognizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CWStackPanGestureRecognizer.h; sourceTree = ""; }; 41 | 414A817619E54BD8000CBAC3 /* CWStackPanGestureRecognizer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CWStackPanGestureRecognizer.m; sourceTree = ""; }; 42 | A090789819DB2BB900085B6E /* ScrollViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScrollViewController.h; sourceTree = ""; }; 43 | A090789919DB2BB900085B6E /* ScrollViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ScrollViewController.m; sourceTree = ""; }; 44 | A090789A19DB2BB900085B6E /* ScrollViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ScrollViewController.xib; sourceTree = ""; }; 45 | A0F4578319C52CA90089A59C /* CWStackControllerDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CWStackControllerDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | A0F4578619C52CA90089A59C /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 47 | A0F4578819C52CA90089A59C /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 48 | A0F4578A19C52CA90089A59C /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 49 | A0F4578E19C52CA90089A59C /* CWStackControllerDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "CWStackControllerDemo-Info.plist"; sourceTree = ""; }; 50 | A0F4579019C52CA90089A59C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 51 | A0F4579219C52CA90089A59C /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 52 | A0F4579419C52CA90089A59C /* CWStackControllerDemo-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CWStackControllerDemo-Prefix.pch"; sourceTree = ""; }; 53 | A0F4579519C52CA90089A59C /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 54 | A0F4579619C52CA90089A59C /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 55 | A0F4579819C52CA90089A59C /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 56 | A0F4579E19C52CA90089A59C /* CWStackControllerDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CWStackControllerDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | A0F4579F19C52CA90089A59C /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 58 | A0F457A719C52CA90089A59C /* CWStackControllerDemoTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "CWStackControllerDemoTests-Info.plist"; sourceTree = ""; }; 59 | A0F457A919C52CA90089A59C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 60 | A0F457AB19C52CA90089A59C /* CWStackControllerDemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CWStackControllerDemoTests.m; sourceTree = ""; }; 61 | A0F457B619C52DA00089A59C /* CWStackController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CWStackController.h; sourceTree = ""; }; 62 | A0F457B719C52DA00089A59C /* CWStackController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CWStackController.m; sourceTree = ""; }; 63 | A0F457B919C52DDD0089A59C /* CWStackProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CWStackProtocol.h; sourceTree = ""; }; 64 | A0F457BA19C52EBB0089A59C /* TableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TableViewController.h; sourceTree = ""; }; 65 | A0F457BB19C52EBB0089A59C /* TableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TableViewController.m; sourceTree = ""; }; 66 | /* End PBXFileReference section */ 67 | 68 | /* Begin PBXFrameworksBuildPhase section */ 69 | A0F4578019C52CA90089A59C /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | A0F4578919C52CA90089A59C /* CoreGraphics.framework in Frameworks */, 74 | A0F4578B19C52CA90089A59C /* UIKit.framework in Frameworks */, 75 | A0F4578719C52CA90089A59C /* Foundation.framework in Frameworks */, 76 | ); 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | A0F4579B19C52CA90089A59C /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | A0F457A019C52CA90089A59C /* XCTest.framework in Frameworks */, 84 | A0F457A219C52CA90089A59C /* UIKit.framework in Frameworks */, 85 | A0F457A119C52CA90089A59C /* Foundation.framework in Frameworks */, 86 | ); 87 | runOnlyForDeploymentPostprocessing = 0; 88 | }; 89 | /* End PBXFrameworksBuildPhase section */ 90 | 91 | /* Begin PBXGroup section */ 92 | A0F4577A19C52CA90089A59C = { 93 | isa = PBXGroup; 94 | children = ( 95 | A0F457B519C52D820089A59C /* CWStackController */, 96 | A0F4578C19C52CA90089A59C /* CWStackControllerDemo */, 97 | A0F457A519C52CA90089A59C /* CWStackControllerDemoTests */, 98 | A0F4578519C52CA90089A59C /* Frameworks */, 99 | A0F4578419C52CA90089A59C /* Products */, 100 | ); 101 | sourceTree = ""; 102 | }; 103 | A0F4578419C52CA90089A59C /* Products */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | A0F4578319C52CA90089A59C /* CWStackControllerDemo.app */, 107 | A0F4579E19C52CA90089A59C /* CWStackControllerDemoTests.xctest */, 108 | ); 109 | name = Products; 110 | sourceTree = ""; 111 | }; 112 | A0F4578519C52CA90089A59C /* Frameworks */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | A0F4578619C52CA90089A59C /* Foundation.framework */, 116 | A0F4578819C52CA90089A59C /* CoreGraphics.framework */, 117 | A0F4578A19C52CA90089A59C /* UIKit.framework */, 118 | A0F4579F19C52CA90089A59C /* XCTest.framework */, 119 | ); 120 | name = Frameworks; 121 | sourceTree = ""; 122 | }; 123 | A0F4578C19C52CA90089A59C /* CWStackControllerDemo */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | A0F4579519C52CA90089A59C /* AppDelegate.h */, 127 | A0F4579619C52CA90089A59C /* AppDelegate.m */, 128 | A0F457BA19C52EBB0089A59C /* TableViewController.h */, 129 | A0F457BB19C52EBB0089A59C /* TableViewController.m */, 130 | A090789819DB2BB900085B6E /* ScrollViewController.h */, 131 | A090789919DB2BB900085B6E /* ScrollViewController.m */, 132 | A090789A19DB2BB900085B6E /* ScrollViewController.xib */, 133 | A0F4579819C52CA90089A59C /* Images.xcassets */, 134 | A0F4578D19C52CA90089A59C /* Supporting Files */, 135 | ); 136 | path = CWStackControllerDemo; 137 | sourceTree = ""; 138 | }; 139 | A0F4578D19C52CA90089A59C /* Supporting Files */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | A0F4578E19C52CA90089A59C /* CWStackControllerDemo-Info.plist */, 143 | A0F4578F19C52CA90089A59C /* InfoPlist.strings */, 144 | A0F4579219C52CA90089A59C /* main.m */, 145 | A0F4579419C52CA90089A59C /* CWStackControllerDemo-Prefix.pch */, 146 | ); 147 | name = "Supporting Files"; 148 | sourceTree = ""; 149 | }; 150 | A0F457A519C52CA90089A59C /* CWStackControllerDemoTests */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | A0F457AB19C52CA90089A59C /* CWStackControllerDemoTests.m */, 154 | A0F457A619C52CA90089A59C /* Supporting Files */, 155 | ); 156 | path = CWStackControllerDemoTests; 157 | sourceTree = ""; 158 | }; 159 | A0F457A619C52CA90089A59C /* Supporting Files */ = { 160 | isa = PBXGroup; 161 | children = ( 162 | A0F457A719C52CA90089A59C /* CWStackControllerDemoTests-Info.plist */, 163 | A0F457A819C52CA90089A59C /* InfoPlist.strings */, 164 | ); 165 | name = "Supporting Files"; 166 | sourceTree = ""; 167 | }; 168 | A0F457B519C52D820089A59C /* CWStackController */ = { 169 | isa = PBXGroup; 170 | children = ( 171 | A0F457B619C52DA00089A59C /* CWStackController.h */, 172 | A0F457B719C52DA00089A59C /* CWStackController.m */, 173 | A0F457B919C52DDD0089A59C /* CWStackProtocol.h */, 174 | 414A817519E54BD8000CBAC3 /* CWStackPanGestureRecognizer.h */, 175 | 414A817619E54BD8000CBAC3 /* CWStackPanGestureRecognizer.m */, 176 | ); 177 | path = CWStackController; 178 | sourceTree = ""; 179 | }; 180 | /* End PBXGroup section */ 181 | 182 | /* Begin PBXNativeTarget section */ 183 | A0F4578219C52CA90089A59C /* CWStackControllerDemo */ = { 184 | isa = PBXNativeTarget; 185 | buildConfigurationList = A0F457AF19C52CA90089A59C /* Build configuration list for PBXNativeTarget "CWStackControllerDemo" */; 186 | buildPhases = ( 187 | A0F4577F19C52CA90089A59C /* Sources */, 188 | A0F4578019C52CA90089A59C /* Frameworks */, 189 | A0F4578119C52CA90089A59C /* Resources */, 190 | ); 191 | buildRules = ( 192 | ); 193 | dependencies = ( 194 | ); 195 | name = CWStackControllerDemo; 196 | productName = CWStackControllerDemo; 197 | productReference = A0F4578319C52CA90089A59C /* CWStackControllerDemo.app */; 198 | productType = "com.apple.product-type.application"; 199 | }; 200 | A0F4579D19C52CA90089A59C /* CWStackControllerDemoTests */ = { 201 | isa = PBXNativeTarget; 202 | buildConfigurationList = A0F457B219C52CA90089A59C /* Build configuration list for PBXNativeTarget "CWStackControllerDemoTests" */; 203 | buildPhases = ( 204 | A0F4579A19C52CA90089A59C /* Sources */, 205 | A0F4579B19C52CA90089A59C /* Frameworks */, 206 | A0F4579C19C52CA90089A59C /* Resources */, 207 | ); 208 | buildRules = ( 209 | ); 210 | dependencies = ( 211 | A0F457A419C52CA90089A59C /* PBXTargetDependency */, 212 | ); 213 | name = CWStackControllerDemoTests; 214 | productName = CWStackControllerDemoTests; 215 | productReference = A0F4579E19C52CA90089A59C /* CWStackControllerDemoTests.xctest */; 216 | productType = "com.apple.product-type.bundle.unit-test"; 217 | }; 218 | /* End PBXNativeTarget section */ 219 | 220 | /* Begin PBXProject section */ 221 | A0F4577B19C52CA90089A59C /* Project object */ = { 222 | isa = PBXProject; 223 | attributes = { 224 | CLASSPREFIX = CW; 225 | LastUpgradeCheck = 0510; 226 | ORGANIZATIONNAME = CocoaWind; 227 | TargetAttributes = { 228 | A0F4579D19C52CA90089A59C = { 229 | TestTargetID = A0F4578219C52CA90089A59C; 230 | }; 231 | }; 232 | }; 233 | buildConfigurationList = A0F4577E19C52CA90089A59C /* Build configuration list for PBXProject "CWStackControllerDemo" */; 234 | compatibilityVersion = "Xcode 3.2"; 235 | developmentRegion = English; 236 | hasScannedForEncodings = 0; 237 | knownRegions = ( 238 | en, 239 | ); 240 | mainGroup = A0F4577A19C52CA90089A59C; 241 | productRefGroup = A0F4578419C52CA90089A59C /* Products */; 242 | projectDirPath = ""; 243 | projectRoot = ""; 244 | targets = ( 245 | A0F4578219C52CA90089A59C /* CWStackControllerDemo */, 246 | A0F4579D19C52CA90089A59C /* CWStackControllerDemoTests */, 247 | ); 248 | }; 249 | /* End PBXProject section */ 250 | 251 | /* Begin PBXResourcesBuildPhase section */ 252 | A0F4578119C52CA90089A59C /* Resources */ = { 253 | isa = PBXResourcesBuildPhase; 254 | buildActionMask = 2147483647; 255 | files = ( 256 | A0F4579119C52CA90089A59C /* InfoPlist.strings in Resources */, 257 | A0F4579919C52CA90089A59C /* Images.xcassets in Resources */, 258 | A090789C19DB2BB900085B6E /* ScrollViewController.xib in Resources */, 259 | ); 260 | runOnlyForDeploymentPostprocessing = 0; 261 | }; 262 | A0F4579C19C52CA90089A59C /* Resources */ = { 263 | isa = PBXResourcesBuildPhase; 264 | buildActionMask = 2147483647; 265 | files = ( 266 | A0F457AA19C52CA90089A59C /* InfoPlist.strings in Resources */, 267 | ); 268 | runOnlyForDeploymentPostprocessing = 0; 269 | }; 270 | /* End PBXResourcesBuildPhase section */ 271 | 272 | /* Begin PBXSourcesBuildPhase section */ 273 | A0F4577F19C52CA90089A59C /* Sources */ = { 274 | isa = PBXSourcesBuildPhase; 275 | buildActionMask = 2147483647; 276 | files = ( 277 | A090789B19DB2BB900085B6E /* ScrollViewController.m in Sources */, 278 | A0F457B819C52DA00089A59C /* CWStackController.m in Sources */, 279 | A0F4579719C52CA90089A59C /* AppDelegate.m in Sources */, 280 | A0F4579319C52CA90089A59C /* main.m in Sources */, 281 | 414A817719E54BD8000CBAC3 /* CWStackPanGestureRecognizer.m in Sources */, 282 | A0F457BC19C52EBB0089A59C /* TableViewController.m in Sources */, 283 | ); 284 | runOnlyForDeploymentPostprocessing = 0; 285 | }; 286 | A0F4579A19C52CA90089A59C /* Sources */ = { 287 | isa = PBXSourcesBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | A0F457AC19C52CA90089A59C /* CWStackControllerDemoTests.m in Sources */, 291 | ); 292 | runOnlyForDeploymentPostprocessing = 0; 293 | }; 294 | /* End PBXSourcesBuildPhase section */ 295 | 296 | /* Begin PBXTargetDependency section */ 297 | A0F457A419C52CA90089A59C /* PBXTargetDependency */ = { 298 | isa = PBXTargetDependency; 299 | target = A0F4578219C52CA90089A59C /* CWStackControllerDemo */; 300 | targetProxy = A0F457A319C52CA90089A59C /* PBXContainerItemProxy */; 301 | }; 302 | /* End PBXTargetDependency section */ 303 | 304 | /* Begin PBXVariantGroup section */ 305 | A0F4578F19C52CA90089A59C /* InfoPlist.strings */ = { 306 | isa = PBXVariantGroup; 307 | children = ( 308 | A0F4579019C52CA90089A59C /* en */, 309 | ); 310 | name = InfoPlist.strings; 311 | sourceTree = ""; 312 | }; 313 | A0F457A819C52CA90089A59C /* InfoPlist.strings */ = { 314 | isa = PBXVariantGroup; 315 | children = ( 316 | A0F457A919C52CA90089A59C /* en */, 317 | ); 318 | name = InfoPlist.strings; 319 | sourceTree = ""; 320 | }; 321 | /* End PBXVariantGroup section */ 322 | 323 | /* Begin XCBuildConfiguration section */ 324 | A0F457AD19C52CA90089A59C /* Debug */ = { 325 | isa = XCBuildConfiguration; 326 | buildSettings = { 327 | ALWAYS_SEARCH_USER_PATHS = NO; 328 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 329 | CLANG_CXX_LIBRARY = "libc++"; 330 | CLANG_ENABLE_MODULES = YES; 331 | CLANG_ENABLE_OBJC_ARC = YES; 332 | CLANG_WARN_BOOL_CONVERSION = YES; 333 | CLANG_WARN_CONSTANT_CONVERSION = YES; 334 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 335 | CLANG_WARN_EMPTY_BODY = YES; 336 | CLANG_WARN_ENUM_CONVERSION = YES; 337 | CLANG_WARN_INT_CONVERSION = YES; 338 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 339 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 340 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 341 | COPY_PHASE_STRIP = NO; 342 | GCC_C_LANGUAGE_STANDARD = gnu99; 343 | GCC_DYNAMIC_NO_PIC = NO; 344 | GCC_OPTIMIZATION_LEVEL = 0; 345 | GCC_PREPROCESSOR_DEFINITIONS = ( 346 | "DEBUG=1", 347 | "$(inherited)", 348 | ); 349 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 350 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 351 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 352 | GCC_WARN_UNDECLARED_SELECTOR = YES; 353 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 354 | GCC_WARN_UNUSED_FUNCTION = YES; 355 | GCC_WARN_UNUSED_VARIABLE = YES; 356 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 357 | ONLY_ACTIVE_ARCH = YES; 358 | SDKROOT = iphoneos; 359 | }; 360 | name = Debug; 361 | }; 362 | A0F457AE19C52CA90089A59C /* Release */ = { 363 | isa = XCBuildConfiguration; 364 | buildSettings = { 365 | ALWAYS_SEARCH_USER_PATHS = NO; 366 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 367 | CLANG_CXX_LIBRARY = "libc++"; 368 | CLANG_ENABLE_MODULES = YES; 369 | CLANG_ENABLE_OBJC_ARC = YES; 370 | CLANG_WARN_BOOL_CONVERSION = YES; 371 | CLANG_WARN_CONSTANT_CONVERSION = YES; 372 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 373 | CLANG_WARN_EMPTY_BODY = YES; 374 | CLANG_WARN_ENUM_CONVERSION = YES; 375 | CLANG_WARN_INT_CONVERSION = YES; 376 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 377 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 378 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 379 | COPY_PHASE_STRIP = YES; 380 | ENABLE_NS_ASSERTIONS = NO; 381 | GCC_C_LANGUAGE_STANDARD = gnu99; 382 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 383 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 384 | GCC_WARN_UNDECLARED_SELECTOR = YES; 385 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 386 | GCC_WARN_UNUSED_FUNCTION = YES; 387 | GCC_WARN_UNUSED_VARIABLE = YES; 388 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 389 | SDKROOT = iphoneos; 390 | VALIDATE_PRODUCT = YES; 391 | }; 392 | name = Release; 393 | }; 394 | A0F457B019C52CA90089A59C /* Debug */ = { 395 | isa = XCBuildConfiguration; 396 | buildSettings = { 397 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 398 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 399 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 400 | GCC_PREFIX_HEADER = "CWStackControllerDemo/CWStackControllerDemo-Prefix.pch"; 401 | INFOPLIST_FILE = "CWStackControllerDemo/CWStackControllerDemo-Info.plist"; 402 | PRODUCT_NAME = "$(TARGET_NAME)"; 403 | WRAPPER_EXTENSION = app; 404 | }; 405 | name = Debug; 406 | }; 407 | A0F457B119C52CA90089A59C /* Release */ = { 408 | isa = XCBuildConfiguration; 409 | buildSettings = { 410 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 411 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 412 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 413 | GCC_PREFIX_HEADER = "CWStackControllerDemo/CWStackControllerDemo-Prefix.pch"; 414 | INFOPLIST_FILE = "CWStackControllerDemo/CWStackControllerDemo-Info.plist"; 415 | PRODUCT_NAME = "$(TARGET_NAME)"; 416 | WRAPPER_EXTENSION = app; 417 | }; 418 | name = Release; 419 | }; 420 | A0F457B319C52CA90089A59C /* Debug */ = { 421 | isa = XCBuildConfiguration; 422 | buildSettings = { 423 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/CWStackControllerDemo.app/CWStackControllerDemo"; 424 | FRAMEWORK_SEARCH_PATHS = ( 425 | "$(SDKROOT)/Developer/Library/Frameworks", 426 | "$(inherited)", 427 | "$(DEVELOPER_FRAMEWORKS_DIR)", 428 | ); 429 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 430 | GCC_PREFIX_HEADER = "CWStackControllerDemo/CWStackControllerDemo-Prefix.pch"; 431 | GCC_PREPROCESSOR_DEFINITIONS = ( 432 | "DEBUG=1", 433 | "$(inherited)", 434 | ); 435 | INFOPLIST_FILE = "CWStackControllerDemoTests/CWStackControllerDemoTests-Info.plist"; 436 | PRODUCT_NAME = "$(TARGET_NAME)"; 437 | TEST_HOST = "$(BUNDLE_LOADER)"; 438 | WRAPPER_EXTENSION = xctest; 439 | }; 440 | name = Debug; 441 | }; 442 | A0F457B419C52CA90089A59C /* Release */ = { 443 | isa = XCBuildConfiguration; 444 | buildSettings = { 445 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/CWStackControllerDemo.app/CWStackControllerDemo"; 446 | FRAMEWORK_SEARCH_PATHS = ( 447 | "$(SDKROOT)/Developer/Library/Frameworks", 448 | "$(inherited)", 449 | "$(DEVELOPER_FRAMEWORKS_DIR)", 450 | ); 451 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 452 | GCC_PREFIX_HEADER = "CWStackControllerDemo/CWStackControllerDemo-Prefix.pch"; 453 | INFOPLIST_FILE = "CWStackControllerDemoTests/CWStackControllerDemoTests-Info.plist"; 454 | PRODUCT_NAME = "$(TARGET_NAME)"; 455 | TEST_HOST = "$(BUNDLE_LOADER)"; 456 | WRAPPER_EXTENSION = xctest; 457 | }; 458 | name = Release; 459 | }; 460 | /* End XCBuildConfiguration section */ 461 | 462 | /* Begin XCConfigurationList section */ 463 | A0F4577E19C52CA90089A59C /* Build configuration list for PBXProject "CWStackControllerDemo" */ = { 464 | isa = XCConfigurationList; 465 | buildConfigurations = ( 466 | A0F457AD19C52CA90089A59C /* Debug */, 467 | A0F457AE19C52CA90089A59C /* Release */, 468 | ); 469 | defaultConfigurationIsVisible = 0; 470 | defaultConfigurationName = Release; 471 | }; 472 | A0F457AF19C52CA90089A59C /* Build configuration list for PBXNativeTarget "CWStackControllerDemo" */ = { 473 | isa = XCConfigurationList; 474 | buildConfigurations = ( 475 | A0F457B019C52CA90089A59C /* Debug */, 476 | A0F457B119C52CA90089A59C /* Release */, 477 | ); 478 | defaultConfigurationIsVisible = 0; 479 | defaultConfigurationName = Release; 480 | }; 481 | A0F457B219C52CA90089A59C /* Build configuration list for PBXNativeTarget "CWStackControllerDemoTests" */ = { 482 | isa = XCConfigurationList; 483 | buildConfigurations = ( 484 | A0F457B319C52CA90089A59C /* Debug */, 485 | A0F457B419C52CA90089A59C /* Release */, 486 | ); 487 | defaultConfigurationIsVisible = 0; 488 | defaultConfigurationName = Release; 489 | }; 490 | /* End XCConfigurationList section */ 491 | }; 492 | rootObject = A0F4577B19C52CA90089A59C /* Project object */; 493 | } 494 | -------------------------------------------------------------------------------- /CWStackControllerDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // CWAppDelegate.h 3 | // CWStackControllerDemo 4 | // 5 | // Created by Guojiubo on 14-9-14. 6 | // Copyright (c) 2014年 CocoaWind. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /CWStackControllerDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // CWAppDelegate.m 3 | // CWStackControllerDemo 4 | // 5 | // Created by Guojiubo on 14-9-14. 6 | // Copyright (c) 2014年 CocoaWind. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "TableViewController.h" 11 | #import "ScrollViewController.h" 12 | 13 | @implementation AppDelegate 14 | 15 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 16 | { 17 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 18 | 19 | TableViewController *root = [[TableViewController alloc] initWithStyle:UITableViewStylePlain]; 20 | CWStackController *stackController = [[CWStackController alloc] initWithRootViewController:root]; 21 | [[self window] setRootViewController:stackController]; 22 | 23 | self.window.backgroundColor = [UIColor whiteColor]; 24 | [self.window makeKeyAndVisible]; 25 | return YES; 26 | } 27 | 28 | - (void)applicationWillResignActive:(UIApplication *)application 29 | { 30 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 31 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 32 | } 33 | 34 | - (void)applicationDidEnterBackground:(UIApplication *)application 35 | { 36 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 37 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 38 | } 39 | 40 | - (void)applicationWillEnterForeground:(UIApplication *)application 41 | { 42 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 43 | } 44 | 45 | - (void)applicationDidBecomeActive:(UIApplication *)application 46 | { 47 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 48 | } 49 | 50 | - (void)applicationWillTerminate:(UIApplication *)application 51 | { 52 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /CWStackControllerDemo/CWStackControllerDemo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.cocoawind.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /CWStackControllerDemo/CWStackControllerDemo-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_3_0 10 | #warning "This project uses features only available in iOS SDK 3.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #import "CWStackController.h" 17 | #endif 18 | -------------------------------------------------------------------------------- /CWStackControllerDemo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /CWStackControllerDemo/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /CWStackControllerDemo/ScrollViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollViewController.h 3 | // CWStackControllerDemo 4 | // 5 | // Created by Guojiubo on 14-10-1. 6 | // Copyright (c) 2014年 CocoaWind. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ScrollViewController : UIViewController 12 | 13 | @property (nonatomic, weak) IBOutlet UIScrollView *scrollView; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /CWStackControllerDemo/ScrollViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ScrollViewController.m 3 | // CWStackControllerDemo 4 | // 5 | // Created by Guojiubo on 14-10-1. 6 | // Copyright (c) 2014年 CocoaWind. All rights reserved. 7 | // 8 | 9 | #import "ScrollViewController.h" 10 | #import "CWStackProtocol.h" 11 | 12 | @interface ScrollViewController () 13 | 14 | @end 15 | 16 | @implementation ScrollViewController 17 | 18 | - (void)viewDidLoad 19 | { 20 | [super viewDidLoad]; 21 | 22 | [self.scrollView setContentSize:CGSizeMake(1000, 1000)]; 23 | } 24 | 25 | - (void)viewWillAppear:(BOOL)animated 26 | { 27 | [super viewWillAppear:animated]; 28 | 29 | [self.stackController setContentScrollView:self.scrollView]; 30 | } 31 | 32 | - (UIViewController *)nextViewController 33 | { 34 | return [[[self class] alloc] init]; 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /CWStackControllerDemo/ScrollViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /CWStackControllerDemo/TableViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewController.h 3 | // CWStackControllerDemo 4 | // 5 | // Created by Guojiubo on 14-9-14. 6 | // Copyright (c) 2014年 CocoaWind. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TableViewController : UITableViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /CWStackControllerDemo/TableViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewController.m 3 | // CWStackControllerDemo 4 | // 5 | // Created by Guojiubo on 14-9-14. 6 | // Copyright (c) 2014年 CocoaWind. All rights reserved. 7 | // 8 | 9 | #import "TableViewController.h" 10 | #import "ScrollViewController.h" 11 | 12 | @interface TableViewController () 13 | 14 | @property (nonatomic, strong) NSArray *menus; 15 | 16 | @end 17 | 18 | @implementation TableViewController 19 | 20 | static NSString *reuseIdentifier = @"Cell"; 21 | 22 | - (void)viewDidLoad 23 | { 24 | [super viewDidLoad]; 25 | 26 | UILabel *counterLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, [[self tableView] bounds].size.width, 64)]; 27 | [counterLabel setTextColor:[self randomColor]]; 28 | [counterLabel setFont:[UIFont boldSystemFontOfSize:28]]; 29 | [counterLabel setTextAlignment:NSTextAlignmentCenter]; 30 | [[self tableView] setTableHeaderView:counterLabel]; 31 | 32 | NSUInteger index = [[self.stackController viewControllers] indexOfObject:self]; 33 | [counterLabel setText:[@(index) stringValue]]; 34 | 35 | [self setMenus:@[@"Push", @"Pop", @"Pop to specific", @"Pop to root", @"Push scroll view"]]; 36 | 37 | [[self tableView] registerClass:[UITableViewCell class] forCellReuseIdentifier:reuseIdentifier]; 38 | } 39 | 40 | - (UIColor *)randomColor 41 | { 42 | NSUInteger r, g, b; 43 | r = arc4random_uniform(256); 44 | g = arc4random_uniform(256); 45 | b = arc4random_uniform(256); 46 | 47 | return [UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:1]; 48 | } 49 | 50 | #pragma mark - FSStack 51 | 52 | - (UIViewController *)nextViewController 53 | { 54 | return [[TableViewController alloc] init]; 55 | } 56 | 57 | #pragma mark - Table view data source 58 | 59 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 60 | { 61 | return [[self menus] count]; 62 | } 63 | 64 | 65 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 66 | { 67 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier forIndexPath:indexPath]; 68 | 69 | [[cell textLabel] setText:[self menus][[indexPath row]]]; 70 | 71 | return cell; 72 | } 73 | 74 | #pragma mark - Table view delegate 75 | 76 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath; 77 | { 78 | switch ([indexPath row]) { 79 | case 0: 80 | { 81 | UIViewController *next = [self nextViewController]; 82 | [self.stackController pushViewController:next animated:YES]; 83 | } 84 | break; 85 | case 1: 86 | { 87 | [self.stackController popViewControllerAnimated:YES]; 88 | } 89 | break; 90 | case 2: 91 | { 92 | NSUInteger count = [[[self stackController] viewControllers] count]; 93 | NSInteger randomIndex = arc4random_uniform((u_int32_t)count); 94 | UIViewController *randomViewController = [[self stackController] viewControllers][randomIndex]; 95 | [self.stackController popToViewController:randomViewController animated:YES]; 96 | } 97 | break; 98 | case 3: 99 | { 100 | [self.stackController popToRootViewControllerAnimated:YES]; 101 | } 102 | break; 103 | case 4: 104 | { 105 | ScrollViewController *vc = [[ScrollViewController alloc] init]; 106 | [self.stackController pushViewController:vc animated:YES]; 107 | } 108 | break; 109 | 110 | default: 111 | break; 112 | } 113 | } 114 | 115 | @end 116 | -------------------------------------------------------------------------------- /CWStackControllerDemo/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /CWStackControllerDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // CWStackControllerDemo 4 | // 5 | // Created by Guojiubo on 14-9-14. 6 | // Copyright (c) 2014年 CocoaWind. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /CWStackControllerDemoTests/CWStackControllerDemoTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.cocoawind.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /CWStackControllerDemoTests/CWStackControllerDemoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CWStackControllerDemoTests.m 3 | // CWStackControllerDemoTests 4 | // 5 | // Created by Guojiubo on 14-9-14. 6 | // Copyright (c) 2014年 CocoaWind. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CWStackControllerDemoTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation CWStackControllerDemoTests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown 24 | { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | [super tearDown]; 27 | } 28 | 29 | - (void)testExample 30 | { 31 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /CWStackControllerDemoTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 guojiubo (https://github.com/guojiubo). 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CWStackController 2 | 3 | CWStackController is a UINavigationController like custom container view controller which provides fullscreen pan gesture support to POP and PUSH , inspired by [Netease News](https://itunes.apple.com/cn/app/id425349261) and used in my recent app [cnBeta Reader](https://itunes.apple.com/cn/app/id885800972). 4 | 5 | ![demo gif](/demo.gif) 6 | 7 | ## Usage 8 | 9 | CWStackController's APIs are pretty much like UINavigationController's which make it very easy to use: 10 | 11 | // Init 12 | - (instancetype)initWithRootViewController:(UIViewController *)rootViewController; 13 | 14 | // Push 15 | - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated; 16 | 17 | // Pop 18 | - (UIViewController *)popViewControllerAnimated:(BOOL)animated; 19 | - (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated; 20 | - (NSArray *)popToRootViewControllerAnimated:(BOOL)animated; 21 | 22 | // Accessing 23 | @property (nonatomic) NSArray *viewControllers; 24 | - (void)setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated; 25 | @property (nonatomic, readonly) UIViewController *topViewController; 26 | 27 | **Enable to push new controller through pan gesture**: 28 | 29 | Assume you have a view controller A is in the CWStackController stack, you want push a new view controller B into stack through pan gesture, all you need is let A confirms to `CWStackProtocol` and implements `- (UIViewController *)nextViewController`: 30 | 31 | // A.m 32 | - (UIViewController *)nextViewController 33 | { 34 | return B; 35 | } 36 | 37 | You can customize the threshold to trigger a push or pop through pan gesture, the duration for animations and the shadow. 38 | 39 | **Work with scroll view:** 40 | 41 | CWStackController works well with scroll view now, if you want have scroll view in the child view controller and you want to PUSH or POP by drag it, you need to set scroll view to CWStackController instance using it's API: 42 | 43 | - (void)setContentScrollView:(UIScrollView *)scrollView; 44 | 45 | See *Demo* project for more details. 46 | 47 | ## Installation 48 | 49 | There are two options: 50 | 51 | 1. CWStackController is available as `CWStackController` in CocoaPods. 52 | 2. Drag *CWStackController* folder from demo project into your Xcode project. 53 | 54 | ## Requirement 55 | 56 | * iOS 5.0 or higher 57 | * ARC 58 | 59 | ## License 60 | 61 | CWStackController is available under the MIT license. See the LICENSE file for more info. 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiubo/CWStackController/1b5fcdfacf530321ea5863a47f6141283e4f92a5/demo.gif --------------------------------------------------------------------------------