├── .gitignore ├── CHANGELOG.md ├── CLHoppingViewController.podspec ├── CLHoppingViewController ├── CLHoppingViewController.h └── CLHoppingViewController.m ├── Example ├── .gitignore ├── CLHoppingViewControllerExample.xcodeproj │ └── project.pbxproj ├── CLHoppingViewControllerExample │ ├── Base.lproj │ │ └── Main.storyboard │ ├── CLAppDelegate.h │ ├── CLAppDelegate.m │ ├── CLHoppingViewControllerExample-Info.plist │ ├── CLHoppingViewControllerExample-Prefix.pch │ ├── CLLoginSignupViewController.h │ ├── CLLoginSignupViewController.m │ ├── CLOnboardingViewController.h │ ├── CLOnboardingViewController.m │ ├── CLSplashViewController.h │ ├── CLSplashViewController.m │ ├── CLStartupViewController.h │ ├── CLStartupViewController.m │ ├── en.lproj │ │ └── InfoPlist.strings │ └── main.m └── Podfile ├── LICENSE ├── README.md └── Rakefile /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 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 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | *.xccheckout 19 | *.xcworkspace/ 20 | 21 | #CocoaPods 22 | Pods 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CLHoppingViewController CHANGELOG 2 | 3 | ## 0.1.0 4 | 5 | Initial release. 6 | -------------------------------------------------------------------------------- /CLHoppingViewController.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "CLHoppingViewController" 3 | s.version = "0.1.5" 4 | s.summary = "A block-based navigational UIViewController designed for app startup, login and onboarding scenarios" 5 | s.homepage = "https://github.com/eladb/CLHoppingViewController" 6 | s.license = 'MIT' 7 | s.author = { "Elad Ben-Israel" => "elad.benisrael@gmail.com" } 8 | s.source = { :git => "https://github.com/eladb/CLHoppingViewController.git", :tag => s.version.to_s } 9 | s.social_media_url = 'https://twitter.com/emeshbi' 10 | s.requires_arc = true 11 | s.source_files = 'CLHoppingViewController' 12 | s.platform = :ios 13 | s.ios.deployment_target = "6.0" 14 | s.public_header_files = "CLHoppingViewController/*.h" 15 | end 16 | -------------------------------------------------------------------------------- /CLHoppingViewController/CLHoppingViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLHoppingViewController.h 3 | // 4 | // Created by Elad Ben-Israel on 2/20/14. 5 | // Copyright (c) 2014 Citylifeapps. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | typedef void(^CLHoppingViewControllerTransitionBlock)(UIViewController *fromViewController, UIViewController *toViewController, void(^completion)(BOOL finished)); 11 | 12 | @interface CLHoppingViewController : UIViewController 13 | 14 | @property (readonly, nonatomic) UIViewController *currentChildViewController; 15 | 16 | // immediately transitions to the specified view controller (cross-fade). When `unhop` will be called `block` will be invoked. If `block` is `nil`, nothing will happen. 17 | // the version that accepts a transition block can use it to customize any transitional behavior 18 | - (void)hopToViewController:(UIViewController *)newViewController then:(void(^)(void))block; 19 | - (void)hopToViewController:(UIViewController *)newViewController transition:(CLHoppingViewControllerTransitionBlock)transition then:(void(^)(void))block; 20 | 21 | // hopping with storyboard identifiers instead of view controllers 22 | - (void)hopTo:(NSString *)storyboardIdentifier then:(void(^)(void))block; 23 | - (void)hopTo:(NSString *)storyboardIdentifier thenTo:(NSString *)nextStoryboardIdentifier; 24 | - (void)hopTo:(NSString *)storyboardIdentifier transition:(CLHoppingViewControllerTransitionBlock)transition then:(void(^)(void))block; 25 | 26 | // causes the `then` block defined in the last `hopToViewController:then:` to be invoked. 27 | // usually, this is called from a child view controller by accessing the parent hopping view controller like this: 28 | // [self.hoppingViewController unhop]; 29 | - (void)unhop; 30 | 31 | // iverride to allow customizing the container. The container view is not inserted 32 | // into the view hierarchy (it's the responsibility of the user). 33 | - (UIView *)containerViewForChildViewController; // default is `[self view]` 34 | 35 | // default custom transitions (override to replace all transitions) 36 | - (void)animatedTransitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController completion:(void(^)(BOOL finished))completion; 37 | 38 | @end 39 | 40 | @interface UIViewController (Hopping) 41 | 42 | /** 43 | * Returns the parent CLHoppingViewController, if any. Otherwise, returns `nil`. 44 | */ 45 | - (CLHoppingViewController *)hoppingViewController; 46 | 47 | @end -------------------------------------------------------------------------------- /CLHoppingViewController/CLHoppingViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // CLHoppingViewController.m 3 | // 4 | // Created by Elad Ben-Israel on 2/20/14. 5 | // Copyright (c) 2014 Citylifeapps. All rights reserved. 6 | // 7 | 8 | #import "CLHoppingViewController.h" 9 | 10 | @interface CLHoppingViewController () 11 | 12 | @property (copy) void(^nextBlock)(void); 13 | 14 | @end 15 | 16 | @implementation CLHoppingViewController 17 | 18 | - (void)hopTo:(NSString *)storyboardIdentifier then:(void(^)(void))block 19 | { 20 | NSAssert(self.storyboard, @"Storyboard is required. Use `hopToViewController:then:` instead"); 21 | UIViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:storyboardIdentifier]; 22 | [self hopToViewController:vc then:block]; 23 | } 24 | 25 | - (void)hopTo:(NSString *)storyboardIdentifier thenTo:(NSString *)nextStoryboardIdentifier 26 | { 27 | [self hopTo:storyboardIdentifier then:^{ 28 | [self hopTo:nextStoryboardIdentifier then:nil]; 29 | }]; 30 | } 31 | 32 | - (void)hopToViewController:(UIViewController *)newViewController then:(void(^)(void))block 33 | { 34 | [self hopToViewController:newViewController transition:nil then:block]; 35 | } 36 | 37 | - (void)hopTo:(NSString *)storyboardIdentifier 38 | transition:(void(^)(UIViewController *fromViewController, UIViewController *toViewController, void(^completion)(BOOL finished)))transition 39 | then:(void(^)(void))block 40 | { 41 | [self hopToViewController:[self.storyboard instantiateViewControllerWithIdentifier:storyboardIdentifier] transition:transition then:block]; 42 | } 43 | 44 | - (void)hopToViewController:(UIViewController *)newViewController transition:(CLHoppingViewControllerTransitionBlock)transition then:(void(^)(void))block; 45 | { 46 | self.nextBlock = block; 47 | 48 | // use default transition if not specified 49 | if (!transition) { 50 | transition = ^(UIViewController *fromViewController, UIViewController *toViewController, void(^completion)(BOOL finished)) { 51 | [self animatedTransitionFromViewController:fromViewController toViewController:toViewController completion:completion]; 52 | }; 53 | } 54 | 55 | UIView *containerView = [self containerViewForChildViewController]; 56 | UIViewController *oldViewController = self.currentChildViewController; 57 | [oldViewController willMoveToParentViewController:nil]; 58 | [self addChildViewController:newViewController]; 59 | newViewController.view.frame = containerView.bounds; 60 | newViewController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 61 | [containerView addSubview:newViewController.view]; // this replades the animated transition 62 | transition(oldViewController, newViewController, ^(BOOL finished) { 63 | [oldViewController removeFromParentViewController]; 64 | [newViewController didMoveToParentViewController:self]; 65 | [containerView layoutIfNeeded]; 66 | [self setNeedsStatusBarAppearanceUpdate]; 67 | }); 68 | } 69 | 70 | - (void)unhop 71 | { 72 | NSAssert(self.nextBlock, @"cannot unhop if there's no next block"); 73 | self.nextBlock(); 74 | } 75 | 76 | - (UIViewController *)currentChildViewController 77 | { 78 | if (self.childViewControllers.count == 0) { 79 | return nil; 80 | } 81 | 82 | return self.childViewControllers[self.childViewControllers.count - 1]; 83 | } 84 | 85 | #pragma mark - Container 86 | 87 | - (UIView *)containerViewForChildViewController 88 | { 89 | return self.view; 90 | } 91 | 92 | #pragma mark - Transitions 93 | 94 | - (void)animatedTransitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController completion:(void(^)(BOOL finished))completion 95 | { 96 | toViewController.view.frame = self.view.bounds; 97 | toViewController.view.alpha = 0.0f; 98 | 99 | if (fromViewController) { 100 | [UIView animateWithDuration:0.25f animations:^{ 101 | fromViewController.view.alpha = 0.0f; 102 | toViewController.view.alpha = 1.0f; 103 | [self setNeedsStatusBarAppearanceUpdate]; 104 | } completion:completion]; 105 | } 106 | else { 107 | toViewController.view.alpha = 1.0f; 108 | completion(YES); 109 | } 110 | } 111 | 112 | #pragma mark - Propogate status bar events to topmost child view controller 113 | 114 | - (UIViewController *)childViewControllerForStatusBarStyle 115 | { 116 | return self.childViewControllers.lastObject; 117 | } 118 | 119 | - (UIViewController *)childViewControllerForStatusBarHidden 120 | { 121 | return self.childViewControllers.lastObject; 122 | } 123 | 124 | @end 125 | 126 | @implementation UIViewController (Hopping) 127 | 128 | - (CLHoppingViewController *)hoppingViewController 129 | { 130 | if ([self isKindOfClass:[CLHoppingViewController class]]) { 131 | return (CLHoppingViewController *)self; 132 | } 133 | 134 | // recursively look for the startup view controller in the view controller hierarchy 135 | // nil will terminate the recursion as well. 136 | return self.parentViewController.hoppingViewController; 137 | } 138 | 139 | @end -------------------------------------------------------------------------------- /Example/.gitignore: -------------------------------------------------------------------------------- 1 | Podfile.lock 2 | -------------------------------------------------------------------------------- /Example/CLHoppingViewControllerExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 474C7381B1134576A77F8A88 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F32A907EDC64F57BBAE2F7D /* libPods.a */; }; 11 | F4826CDF18B61C3F00B649D2 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F4826CDE18B61C3F00B649D2 /* Foundation.framework */; }; 12 | F4826CE118B61C3F00B649D2 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F4826CE018B61C3F00B649D2 /* CoreGraphics.framework */; }; 13 | F4826CE318B61C3F00B649D2 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F4826CE218B61C3F00B649D2 /* UIKit.framework */; }; 14 | F4826CE918B61C3F00B649D2 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = F4826CE718B61C3F00B649D2 /* InfoPlist.strings */; }; 15 | F4826CEB18B61C3F00B649D2 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = F4826CEA18B61C3F00B649D2 /* main.m */; }; 16 | F4826CEF18B61C3F00B649D2 /* CLAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = F4826CEE18B61C3F00B649D2 /* CLAppDelegate.m */; }; 17 | F4826CF218B61C3F00B649D2 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F4826CF018B61C3F00B649D2 /* Main.storyboard */; }; 18 | F4826D1D18B61CCF00B649D2 /* CLStartupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F4826D1618B61CCF00B649D2 /* CLStartupViewController.m */; }; 19 | F4826D1E18B61CCF00B649D2 /* CLSplashViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F4826D1818B61CCF00B649D2 /* CLSplashViewController.m */; }; 20 | F4826D1F18B61CCF00B649D2 /* CLOnboardingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F4826D1A18B61CCF00B649D2 /* CLOnboardingViewController.m */; }; 21 | F4826D2418B61D7E00B649D2 /* CLLoginSignupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F4826D2318B61D7E00B649D2 /* CLLoginSignupViewController.m */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXFileReference section */ 25 | 2F7BFF8B5B1347C3A97B19B3 /* Pods.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.xcconfig; path = Pods/Pods.xcconfig; sourceTree = ""; }; 26 | 4F32A907EDC64F57BBAE2F7D /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | F4826CDB18B61C3F00B649D2 /* CLHoppingViewControllerExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CLHoppingViewControllerExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | F4826CDE18B61C3F00B649D2 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 29 | F4826CE018B61C3F00B649D2 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 30 | F4826CE218B61C3F00B649D2 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 31 | F4826CE618B61C3F00B649D2 /* CLHoppingViewControllerExample-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "CLHoppingViewControllerExample-Info.plist"; sourceTree = ""; }; 32 | F4826CE818B61C3F00B649D2 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 33 | F4826CEA18B61C3F00B649D2 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 34 | F4826CEC18B61C3F00B649D2 /* CLHoppingViewControllerExample-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CLHoppingViewControllerExample-Prefix.pch"; sourceTree = ""; }; 35 | F4826CED18B61C3F00B649D2 /* CLAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CLAppDelegate.h; sourceTree = ""; }; 36 | F4826CEE18B61C3F00B649D2 /* CLAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CLAppDelegate.m; sourceTree = ""; }; 37 | F4826CF118B61C3F00B649D2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 38 | F4826CFD18B61C3F00B649D2 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 39 | F4826D1518B61CCF00B649D2 /* CLStartupViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CLStartupViewController.h; sourceTree = ""; }; 40 | F4826D1618B61CCF00B649D2 /* CLStartupViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CLStartupViewController.m; sourceTree = ""; }; 41 | F4826D1718B61CCF00B649D2 /* CLSplashViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CLSplashViewController.h; sourceTree = ""; }; 42 | F4826D1818B61CCF00B649D2 /* CLSplashViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CLSplashViewController.m; sourceTree = ""; }; 43 | F4826D1918B61CCF00B649D2 /* CLOnboardingViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CLOnboardingViewController.h; sourceTree = ""; }; 44 | F4826D1A18B61CCF00B649D2 /* CLOnboardingViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CLOnboardingViewController.m; sourceTree = ""; }; 45 | F4826D2218B61D7E00B649D2 /* CLLoginSignupViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CLLoginSignupViewController.h; sourceTree = ""; }; 46 | F4826D2318B61D7E00B649D2 /* CLLoginSignupViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CLLoginSignupViewController.m; sourceTree = ""; }; 47 | /* End PBXFileReference section */ 48 | 49 | /* Begin PBXFrameworksBuildPhase section */ 50 | F4826CD818B61C3F00B649D2 /* Frameworks */ = { 51 | isa = PBXFrameworksBuildPhase; 52 | buildActionMask = 2147483647; 53 | files = ( 54 | F4826CE118B61C3F00B649D2 /* CoreGraphics.framework in Frameworks */, 55 | F4826CE318B61C3F00B649D2 /* UIKit.framework in Frameworks */, 56 | F4826CDF18B61C3F00B649D2 /* Foundation.framework in Frameworks */, 57 | 474C7381B1134576A77F8A88 /* libPods.a in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | F4826CD218B61C3E00B649D2 = { 65 | isa = PBXGroup; 66 | children = ( 67 | F4826CE418B61C3F00B649D2 /* CLHoppingViewControllerExample */, 68 | F4826CDD18B61C3F00B649D2 /* Frameworks */, 69 | F4826CDC18B61C3F00B649D2 /* Products */, 70 | 2F7BFF8B5B1347C3A97B19B3 /* Pods.xcconfig */, 71 | ); 72 | sourceTree = ""; 73 | }; 74 | F4826CDC18B61C3F00B649D2 /* Products */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | F4826CDB18B61C3F00B649D2 /* CLHoppingViewControllerExample.app */, 78 | ); 79 | name = Products; 80 | sourceTree = ""; 81 | }; 82 | F4826CDD18B61C3F00B649D2 /* Frameworks */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | F4826CDE18B61C3F00B649D2 /* Foundation.framework */, 86 | F4826CE018B61C3F00B649D2 /* CoreGraphics.framework */, 87 | F4826CE218B61C3F00B649D2 /* UIKit.framework */, 88 | F4826CFD18B61C3F00B649D2 /* XCTest.framework */, 89 | 4F32A907EDC64F57BBAE2F7D /* libPods.a */, 90 | ); 91 | name = Frameworks; 92 | sourceTree = ""; 93 | }; 94 | F4826CE418B61C3F00B649D2 /* CLHoppingViewControllerExample */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | F4826CF018B61C3F00B649D2 /* Main.storyboard */, 98 | F4826D2118B61D1300B649D2 /* Application */, 99 | F4826D1418B61CC500B649D2 /* View Controllers */, 100 | F4826CE518B61C3F00B649D2 /* Supporting Files */, 101 | ); 102 | path = CLHoppingViewControllerExample; 103 | sourceTree = ""; 104 | }; 105 | F4826CE518B61C3F00B649D2 /* Supporting Files */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | F4826CE618B61C3F00B649D2 /* CLHoppingViewControllerExample-Info.plist */, 109 | F4826CE718B61C3F00B649D2 /* InfoPlist.strings */, 110 | F4826CEA18B61C3F00B649D2 /* main.m */, 111 | F4826CEC18B61C3F00B649D2 /* CLHoppingViewControllerExample-Prefix.pch */, 112 | ); 113 | name = "Supporting Files"; 114 | sourceTree = ""; 115 | }; 116 | F4826D1418B61CC500B649D2 /* View Controllers */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | F4826D1518B61CCF00B649D2 /* CLStartupViewController.h */, 120 | F4826D1618B61CCF00B649D2 /* CLStartupViewController.m */, 121 | F4826D1718B61CCF00B649D2 /* CLSplashViewController.h */, 122 | F4826D1818B61CCF00B649D2 /* CLSplashViewController.m */, 123 | F4826D1918B61CCF00B649D2 /* CLOnboardingViewController.h */, 124 | F4826D1A18B61CCF00B649D2 /* CLOnboardingViewController.m */, 125 | F4826D2218B61D7E00B649D2 /* CLLoginSignupViewController.h */, 126 | F4826D2318B61D7E00B649D2 /* CLLoginSignupViewController.m */, 127 | ); 128 | name = "View Controllers"; 129 | sourceTree = ""; 130 | }; 131 | F4826D2118B61D1300B649D2 /* Application */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | F4826CED18B61C3F00B649D2 /* CLAppDelegate.h */, 135 | F4826CEE18B61C3F00B649D2 /* CLAppDelegate.m */, 136 | ); 137 | name = Application; 138 | sourceTree = ""; 139 | }; 140 | /* End PBXGroup section */ 141 | 142 | /* Begin PBXNativeTarget section */ 143 | F4826CDA18B61C3F00B649D2 /* CLHoppingViewControllerExample */ = { 144 | isa = PBXNativeTarget; 145 | buildConfigurationList = F4826D0D18B61C3F00B649D2 /* Build configuration list for PBXNativeTarget "CLHoppingViewControllerExample" */; 146 | buildPhases = ( 147 | 8589504F42D445B295608CE4 /* Check Pods Manifest.lock */, 148 | F4826CD718B61C3F00B649D2 /* Sources */, 149 | F4826CD818B61C3F00B649D2 /* Frameworks */, 150 | F4826CD918B61C3F00B649D2 /* Resources */, 151 | C5312E7286D7414B82F2F4FF /* Copy Pods Resources */, 152 | ); 153 | buildRules = ( 154 | ); 155 | dependencies = ( 156 | ); 157 | name = CLHoppingViewControllerExample; 158 | productName = CLHoppingViewControllerExample; 159 | productReference = F4826CDB18B61C3F00B649D2 /* CLHoppingViewControllerExample.app */; 160 | productType = "com.apple.product-type.application"; 161 | }; 162 | /* End PBXNativeTarget section */ 163 | 164 | /* Begin PBXProject section */ 165 | F4826CD318B61C3E00B649D2 /* Project object */ = { 166 | isa = PBXProject; 167 | attributes = { 168 | CLASSPREFIX = CL; 169 | LastUpgradeCheck = 0510; 170 | ORGANIZATIONNAME = Citylifeapps; 171 | }; 172 | buildConfigurationList = F4826CD618B61C3E00B649D2 /* Build configuration list for PBXProject "CLHoppingViewControllerExample" */; 173 | compatibilityVersion = "Xcode 3.2"; 174 | developmentRegion = English; 175 | hasScannedForEncodings = 0; 176 | knownRegions = ( 177 | en, 178 | Base, 179 | ); 180 | mainGroup = F4826CD218B61C3E00B649D2; 181 | productRefGroup = F4826CDC18B61C3F00B649D2 /* Products */; 182 | projectDirPath = ""; 183 | projectRoot = ""; 184 | targets = ( 185 | F4826CDA18B61C3F00B649D2 /* CLHoppingViewControllerExample */, 186 | ); 187 | }; 188 | /* End PBXProject section */ 189 | 190 | /* Begin PBXResourcesBuildPhase section */ 191 | F4826CD918B61C3F00B649D2 /* Resources */ = { 192 | isa = PBXResourcesBuildPhase; 193 | buildActionMask = 2147483647; 194 | files = ( 195 | F4826CE918B61C3F00B649D2 /* InfoPlist.strings in Resources */, 196 | F4826CF218B61C3F00B649D2 /* Main.storyboard in Resources */, 197 | ); 198 | runOnlyForDeploymentPostprocessing = 0; 199 | }; 200 | /* End PBXResourcesBuildPhase section */ 201 | 202 | /* Begin PBXShellScriptBuildPhase section */ 203 | 8589504F42D445B295608CE4 /* Check Pods Manifest.lock */ = { 204 | isa = PBXShellScriptBuildPhase; 205 | buildActionMask = 2147483647; 206 | files = ( 207 | ); 208 | inputPaths = ( 209 | ); 210 | name = "Check Pods Manifest.lock"; 211 | outputPaths = ( 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | shellPath = /bin/sh; 215 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 216 | showEnvVarsInLog = 0; 217 | }; 218 | C5312E7286D7414B82F2F4FF /* Copy Pods Resources */ = { 219 | isa = PBXShellScriptBuildPhase; 220 | buildActionMask = 2147483647; 221 | files = ( 222 | ); 223 | inputPaths = ( 224 | ); 225 | name = "Copy Pods Resources"; 226 | outputPaths = ( 227 | ); 228 | runOnlyForDeploymentPostprocessing = 0; 229 | shellPath = /bin/sh; 230 | shellScript = "\"${SRCROOT}/Pods/Pods-resources.sh\"\n"; 231 | showEnvVarsInLog = 0; 232 | }; 233 | /* End PBXShellScriptBuildPhase section */ 234 | 235 | /* Begin PBXSourcesBuildPhase section */ 236 | F4826CD718B61C3F00B649D2 /* Sources */ = { 237 | isa = PBXSourcesBuildPhase; 238 | buildActionMask = 2147483647; 239 | files = ( 240 | F4826CEB18B61C3F00B649D2 /* main.m in Sources */, 241 | F4826D1E18B61CCF00B649D2 /* CLSplashViewController.m in Sources */, 242 | F4826D1F18B61CCF00B649D2 /* CLOnboardingViewController.m in Sources */, 243 | F4826D2418B61D7E00B649D2 /* CLLoginSignupViewController.m in Sources */, 244 | F4826D1D18B61CCF00B649D2 /* CLStartupViewController.m in Sources */, 245 | F4826CEF18B61C3F00B649D2 /* CLAppDelegate.m in Sources */, 246 | ); 247 | runOnlyForDeploymentPostprocessing = 0; 248 | }; 249 | /* End PBXSourcesBuildPhase section */ 250 | 251 | /* Begin PBXVariantGroup section */ 252 | F4826CE718B61C3F00B649D2 /* InfoPlist.strings */ = { 253 | isa = PBXVariantGroup; 254 | children = ( 255 | F4826CE818B61C3F00B649D2 /* en */, 256 | ); 257 | name = InfoPlist.strings; 258 | sourceTree = ""; 259 | }; 260 | F4826CF018B61C3F00B649D2 /* Main.storyboard */ = { 261 | isa = PBXVariantGroup; 262 | children = ( 263 | F4826CF118B61C3F00B649D2 /* Base */, 264 | ); 265 | name = Main.storyboard; 266 | sourceTree = ""; 267 | }; 268 | /* End PBXVariantGroup section */ 269 | 270 | /* Begin XCBuildConfiguration section */ 271 | F4826D0B18B61C3F00B649D2 /* Debug */ = { 272 | isa = XCBuildConfiguration; 273 | buildSettings = { 274 | ALWAYS_SEARCH_USER_PATHS = NO; 275 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 276 | CLANG_CXX_LIBRARY = "libc++"; 277 | CLANG_ENABLE_MODULES = YES; 278 | CLANG_ENABLE_OBJC_ARC = YES; 279 | CLANG_WARN_BOOL_CONVERSION = YES; 280 | CLANG_WARN_CONSTANT_CONVERSION = YES; 281 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 282 | CLANG_WARN_EMPTY_BODY = YES; 283 | CLANG_WARN_ENUM_CONVERSION = YES; 284 | CLANG_WARN_INT_CONVERSION = YES; 285 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 286 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 287 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 288 | COPY_PHASE_STRIP = NO; 289 | GCC_C_LANGUAGE_STANDARD = gnu99; 290 | GCC_DYNAMIC_NO_PIC = NO; 291 | GCC_OPTIMIZATION_LEVEL = 0; 292 | GCC_PREPROCESSOR_DEFINITIONS = ( 293 | "DEBUG=1", 294 | "$(inherited)", 295 | ); 296 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 297 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 298 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 299 | GCC_WARN_UNDECLARED_SELECTOR = YES; 300 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 301 | GCC_WARN_UNUSED_FUNCTION = YES; 302 | GCC_WARN_UNUSED_VARIABLE = YES; 303 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 304 | ONLY_ACTIVE_ARCH = YES; 305 | SDKROOT = iphoneos; 306 | }; 307 | name = Debug; 308 | }; 309 | F4826D0C18B61C3F00B649D2 /* Release */ = { 310 | isa = XCBuildConfiguration; 311 | buildSettings = { 312 | ALWAYS_SEARCH_USER_PATHS = NO; 313 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 314 | CLANG_CXX_LIBRARY = "libc++"; 315 | CLANG_ENABLE_MODULES = YES; 316 | CLANG_ENABLE_OBJC_ARC = YES; 317 | CLANG_WARN_BOOL_CONVERSION = YES; 318 | CLANG_WARN_CONSTANT_CONVERSION = YES; 319 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 320 | CLANG_WARN_EMPTY_BODY = YES; 321 | CLANG_WARN_ENUM_CONVERSION = YES; 322 | CLANG_WARN_INT_CONVERSION = YES; 323 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 324 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 325 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 326 | COPY_PHASE_STRIP = YES; 327 | ENABLE_NS_ASSERTIONS = NO; 328 | GCC_C_LANGUAGE_STANDARD = gnu99; 329 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 330 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 331 | GCC_WARN_UNDECLARED_SELECTOR = YES; 332 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 333 | GCC_WARN_UNUSED_FUNCTION = YES; 334 | GCC_WARN_UNUSED_VARIABLE = YES; 335 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 336 | SDKROOT = iphoneos; 337 | VALIDATE_PRODUCT = YES; 338 | }; 339 | name = Release; 340 | }; 341 | F4826D0E18B61C3F00B649D2 /* Debug */ = { 342 | isa = XCBuildConfiguration; 343 | baseConfigurationReference = 2F7BFF8B5B1347C3A97B19B3 /* Pods.xcconfig */; 344 | buildSettings = { 345 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 346 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 347 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 348 | GCC_PREFIX_HEADER = "CLHoppingViewControllerExample/CLHoppingViewControllerExample-Prefix.pch"; 349 | INFOPLIST_FILE = "CLHoppingViewControllerExample/CLHoppingViewControllerExample-Info.plist"; 350 | PRODUCT_NAME = "$(TARGET_NAME)"; 351 | TARGETED_DEVICE_FAMILY = 1; 352 | WRAPPER_EXTENSION = app; 353 | }; 354 | name = Debug; 355 | }; 356 | F4826D0F18B61C3F00B649D2 /* Release */ = { 357 | isa = XCBuildConfiguration; 358 | baseConfigurationReference = 2F7BFF8B5B1347C3A97B19B3 /* Pods.xcconfig */; 359 | buildSettings = { 360 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 361 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 362 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 363 | GCC_PREFIX_HEADER = "CLHoppingViewControllerExample/CLHoppingViewControllerExample-Prefix.pch"; 364 | INFOPLIST_FILE = "CLHoppingViewControllerExample/CLHoppingViewControllerExample-Info.plist"; 365 | PRODUCT_NAME = "$(TARGET_NAME)"; 366 | TARGETED_DEVICE_FAMILY = 1; 367 | WRAPPER_EXTENSION = app; 368 | }; 369 | name = Release; 370 | }; 371 | /* End XCBuildConfiguration section */ 372 | 373 | /* Begin XCConfigurationList section */ 374 | F4826CD618B61C3E00B649D2 /* Build configuration list for PBXProject "CLHoppingViewControllerExample" */ = { 375 | isa = XCConfigurationList; 376 | buildConfigurations = ( 377 | F4826D0B18B61C3F00B649D2 /* Debug */, 378 | F4826D0C18B61C3F00B649D2 /* Release */, 379 | ); 380 | defaultConfigurationIsVisible = 0; 381 | defaultConfigurationName = Release; 382 | }; 383 | F4826D0D18B61C3F00B649D2 /* Build configuration list for PBXNativeTarget "CLHoppingViewControllerExample" */ = { 384 | isa = XCConfigurationList; 385 | buildConfigurations = ( 386 | F4826D0E18B61C3F00B649D2 /* Debug */, 387 | F4826D0F18B61C3F00B649D2 /* Release */, 388 | ); 389 | defaultConfigurationIsVisible = 0; 390 | defaultConfigurationName = Release; 391 | }; 392 | /* End XCConfigurationList section */ 393 | }; 394 | rootObject = F4826CD318B61C3E00B649D2 /* Project object */; 395 | } 396 | -------------------------------------------------------------------------------- /Example/CLHoppingViewControllerExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 86 | 97 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 158 | 164 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 215 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 304 | 310 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 361 | 367 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | -------------------------------------------------------------------------------- /Example/CLHoppingViewControllerExample/CLAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLAppDelegate.h 3 | // CLHoppingViewControllerExample 4 | // 5 | // Created by Elad Ben-Israel on 2/20/14. 6 | // Copyright (c) 2014 Citylifeapps. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CLAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Example/CLHoppingViewControllerExample/CLAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // CLAppDelegate.m 3 | // CLHoppingViewControllerExample 4 | // 5 | // Created by Elad Ben-Israel on 2/20/14. 6 | // Copyright (c) 2014 Citylifeapps. All rights reserved. 7 | // 8 | 9 | #import "CLAppDelegate.h" 10 | #import 11 | 12 | @implementation CLAppDelegate 13 | 14 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 15 | { 16 | // Override point for customization after application launch. 17 | return YES; 18 | } 19 | 20 | - (void)applicationWillResignActive:(UIApplication *)application 21 | { 22 | // 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. 23 | // 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. 24 | } 25 | 26 | - (void)applicationDidEnterBackground:(UIApplication *)application 27 | { 28 | // 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. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | - (void)applicationWillEnterForeground:(UIApplication *)application 33 | { 34 | // 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. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application 38 | { 39 | // 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. 40 | } 41 | 42 | - (void)applicationWillTerminate:(UIApplication *)application 43 | { 44 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /Example/CLHoppingViewControllerExample/CLHoppingViewControllerExample-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.citylifeapps.${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 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarHidden 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeRight 39 | UIInterfaceOrientationLandscapeLeft 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Example/CLHoppingViewControllerExample/CLHoppingViewControllerExample-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_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /Example/CLHoppingViewControllerExample/CLLoginSignupViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLLoginSignupViewController.h 3 | // CLHoppingViewControllerExample 4 | // 5 | // Created by Elad Ben-Israel on 2/20/14. 6 | // Copyright (c) 2014 Citylifeapps. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CLLoginSignupViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/CLHoppingViewControllerExample/CLLoginSignupViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // CLLoginSignupViewController.m 3 | // CLHoppingViewControllerExample 4 | // 5 | // Created by Elad Ben-Israel on 2/20/14. 6 | // Copyright (c) 2014 Citylifeapps. All rights reserved. 7 | // 8 | 9 | #import "CLLoginSignupViewController.h" 10 | #import 11 | 12 | @implementation CLLoginSignupViewController 13 | 14 | - (IBAction)login:(id)sender 15 | { 16 | [self.hoppingViewController unhop]; 17 | } 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Example/CLHoppingViewControllerExample/CLOnboardingViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLOnboardingViewController.h 3 | // smvc 4 | // 5 | // Created by Elad Ben-Israel on 2/20/14. 6 | // Copyright (c) 2014 Citylifeapps. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CLOnboardingViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/CLHoppingViewControllerExample/CLOnboardingViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // CLOnboardingViewController.m 3 | // smvc 4 | // 5 | // Created by Elad Ben-Israel on 2/20/14. 6 | // Copyright (c) 2014 Citylifeapps. All rights reserved. 7 | // 8 | 9 | #import "CLOnboardingViewController.h" 10 | #import 11 | 12 | @implementation CLOnboardingViewController 13 | 14 | - (IBAction)next:(id)sender 15 | { 16 | @try { 17 | [self performSegueWithIdentifier:@"next" sender:nil]; 18 | } 19 | @catch (NSException *exception) { 20 | [self.hoppingViewController unhop]; 21 | } 22 | } 23 | 24 | - (BOOL)prefersStatusBarHidden 25 | { 26 | return YES; 27 | } 28 | 29 | - (UIStatusBarAnimation)preferredStatusBarUpdateAnimation 30 | { 31 | return UIStatusBarAnimationSlide; 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /Example/CLHoppingViewControllerExample/CLSplashViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLSplashViewController.h 3 | // smvc 4 | // 5 | // Created by Elad Ben-Israel on 2/20/14. 6 | // Copyright (c) 2014 Citylifeapps. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CLSplashViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Example/CLHoppingViewControllerExample/CLSplashViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // CLSplashViewController.m 3 | // smvc 4 | // 5 | // Created by Elad Ben-Israel on 2/20/14. 6 | // Copyright (c) 2014 Citylifeapps. All rights reserved. 7 | // 8 | 9 | #import "CLSplashViewController.h" 10 | #import 11 | 12 | @interface CLSplashViewController () 13 | 14 | @property (weak) IBOutlet UIProgressView *progressView; 15 | 16 | @property NSInteger currentItem; 17 | @property NSInteger totalItems; 18 | @property (strong) NSTimer *progressTimer; 19 | 20 | @end 21 | 22 | @implementation CLSplashViewController 23 | 24 | - (void)viewDidLoad 25 | { 26 | [super viewDidLoad]; 27 | self.totalItems = 10; 28 | self.currentItem = 0; 29 | self.progressTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(tick:) userInfo:nil repeats:YES]; 30 | } 31 | 32 | - (BOOL)prefersStatusBarHidden 33 | { 34 | return NO; 35 | } 36 | 37 | - (void)tick:(id)sender 38 | { 39 | if (self.currentItem > self.totalItems) { 40 | [self.progressTimer invalidate]; 41 | [self.hoppingViewController unhop]; 42 | } 43 | 44 | self.progressView.progress = ((CGFloat)self.currentItem / self.totalItems); 45 | self.currentItem++; 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /Example/CLHoppingViewControllerExample/CLStartupViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CLStartupViewController.h 3 | // smvc 4 | // 5 | // Created by Elad Ben-Israel on 2/20/14. 6 | // Copyright (c) 2014 Citylifeapps. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface CLStartupViewController : CLHoppingViewController 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /Example/CLHoppingViewControllerExample/CLStartupViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // CLStartupViewController.m 3 | // smvc 4 | // 5 | // Created by Elad Ben-Israel on 2/20/14. 6 | // Copyright (c) 2014 Citylifeapps. All rights reserved. 7 | // 8 | 9 | #import "CLStartupViewController.h" 10 | #import "CLLoginSignupViewController.h" 11 | 12 | @implementation CLStartupViewController 13 | 14 | - (void)viewDidLoad 15 | { 16 | [super viewDidLoad]; 17 | 18 | [self hopTo:@"splash" then:^{ 19 | [self hopTo:@"onboarding" then:^{ 20 | [self hopTo:@"signup_login" transition:^(UIViewController *fromViewController, UIViewController *toViewController, void (^completion)(BOOL finished)) { 21 | toViewController.view.center = CGPointMake(self.containerViewForChildViewController.bounds.size.width, self.containerViewForChildViewController.bounds.size.height); 22 | toViewController.view.transform = CGAffineTransformMakeScale(0.0, 0.0); 23 | [UIView animateWithDuration:0.25f animations:^{ 24 | fromViewController.view.center = CGPointMake(0, 0); 25 | fromViewController.view.transform = CGAffineTransformMakeScale(0.0, 0.0); 26 | toViewController.view.center = self.containerViewForChildViewController.center; 27 | toViewController.view.transform = CGAffineTransformMakeScale(1.0, 1.0); 28 | } completion:completion]; 29 | } then:^{ 30 | [self hopTo:@"main" then:nil]; 31 | }]; 32 | }]; 33 | }]; 34 | } 35 | 36 | @end -------------------------------------------------------------------------------- /Example/CLHoppingViewControllerExample/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Example/CLHoppingViewControllerExample/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // CLHoppingViewControllerExample 4 | // 5 | // Created by Elad Ben-Israel on 2/20/14. 6 | // Copyright (c) 2014 Citylifeapps. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "CLAppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([CLAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | pod "CLHoppingViewController", :path => "../CLHoppingViewController.podspec" 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Elad Ben-Israel 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. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CLHoppingViewController 2 | 3 | [![Version](http://cocoapod-badges.herokuapp.com/v/CLHoppingViewController/badge.png)](http://cocoadocs.org/docsets/CLHoppingViewController) 4 | [![Platform](http://cocoapod-badges.herokuapp.com/p/CLHoppingViewController/badge.png)](http://cocoadocs.org/docsets/CLHoppingViewController) 5 | 6 | A block-based [custom container view controller](https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/CreatingCustomContainerViewControllers/CreatingCustomContainerViewControllers.html) designed for app startup, login and onboarding scenarios. 7 | 8 | ## Motivation 9 | 10 | A common problem we keep running into is how to handle an app's start up flow. The standard container view controllers (such as `UINavigationController` and `UITabBarController`) do not provide a good solution for handling the conditional flows related to app start up. 11 | 12 | Here's a typical flow for an app start up sequence: 13 | 14 | 1. Show some beautiful splash screen while reloading cached state (or maybe refreshing from network). 15 | 2. If this is the user's first time, show the onboarding UX. 16 | 3. If there's no stored user session, show the sign-up/login UX. 17 | 4. Go to the main app flow (usually some `UINavigationController` within a storyboard). 18 | 19 | A common solution to this is to embed some conditional/one-off logic inside the various UIViewControllers involved in the flow and use a `UINavigationController` to push/pop the desired UX elements based on these conditionals. Another way to approach this is to use modal controllers, or even replace the `rootViewController` of the key window. All of these approaches result is a spaghetti solution with the actual logic of the startup sequence distributed across multiple source files, hard-to-control transitions and annoying bugs. 20 | 21 | ### Enters CLHoppingViewController ### 22 | 23 | The idea behind this [custom container]([custom container view controller](https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/CreatingCustomContainerViewControllers/CreatingCustomContainerViewControllers.html)) is that each UX element of the flow is self-contained and decoupled from the other parts of the flow. It only reports when it had finished using the `unhop` method and yields the flow control back to the parent view controller. 24 | 25 | So, for example. We can create a subclass `CLHoppingViewController` that "hops" to the splash screen. Then, when the splash screen calls `unhop`, it hops to the onboarding UX (if this is the first use) or to the log-in/sign-up UX if there's no saved session, etc, etc. 26 | 27 | ## Installation 28 | 29 | CLHoppingViewController is available through [CocoaPods](http://cocoapods.org), to install 30 | it simply add the following line to your Podfile: 31 | 32 | pod "CLHoppingViewController" 33 | 34 | ## API 35 | 36 | ### Hop to Another View Controller 37 | 38 | ```objc 39 | - (void)hopToViewController:(UIViewController *)newViewController then:(void(^)(void))block; 40 | ``` 41 | 42 | Immediately transitions to the specified view controller (cross-fade). When `unhop` will be called `block` will be invoked. If `block` is `nil`, nothing will happen. 43 | 44 | ### Unhop Back to the `then` Block 45 | 46 | ```objc 47 | - (void)unhop; 48 | ``` 49 | 50 | Causes the `then` block defined in the last `hopToViewController:then:` to be invoked. 51 | 52 | Usually, this is called from a child view controller by accessing the parent hopping view controller like this: 53 | 54 | ```objc 55 | #import 56 | 57 | - (void)readyToHopBack 58 | { 59 | [self.hoppingViewController unhop]; 60 | } 61 | ``` 62 | 63 | ### Custom Transitions 64 | 65 | CLHoppingViewController supports custom transition via a block that may be passed to the various hopping functions. 66 | 67 | ```objc 68 | - (void)hopToViewController:(UIViewController *)newViewController 69 | transition:(CLHoppingViewControllerTransitionBlock)transition 70 | then:(void(^)(void))block; 71 | ``` 72 | 73 | This will invoke the `transition` block during the hop. The transition block has the following signature: 74 | 75 | ```objc 76 | typedef void(^CLHoppingViewControllerTransitionBlock)(UIViewController *fromViewController, UIViewController *toViewController, void(^completion)(BOOL finished)); 77 | ``` 78 | 79 | - __fromViewController__: The source UIViewController 80 | - __toViewController__: The destination UIViewController 81 | - __completion__: A block that __must__ be called when the transition is finished 82 | 83 | NOTE: `toViewController.view` will be inserted to the view hierarchy of the container (`containerViewForChildViewController`) before the transition is started, so no need to add it manually. 84 | 85 | ### Custom Container View 86 | 87 | By default, `CLHoppingViewController` will add the destination view controller's view as a child of `[self view]`. If you wish to change this, override `[CLHoppingViewController containerViewForChildViewController]` and return any view you wish to use a container for the child view controllers. 88 | 89 | ## Author 90 | 91 | Elad Ben-Israel, elad.benisrael@gmail.com 92 | 93 | ## License 94 | 95 | CLHoppingViewController is available under the MIT license. See the LICENSE file for more info. 96 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | desc "Runs the specs [EMPTY]" 2 | task :spec do 3 | # Provide your own implementation 4 | end 5 | 6 | task :version do 7 | git_remotes = `git remote`.strip.split("\n") 8 | 9 | if git_remotes.count > 0 10 | puts "-- fetching version number from github" 11 | sh 'git fetch' 12 | 13 | remote_version = remote_spec_version 14 | end 15 | 16 | if remote_version.nil? 17 | puts "There is no current released version. You're about to release a new Pod." 18 | version = "0.0.1" 19 | else 20 | puts "The current released version of your pod is " + remote_spec_version.to_s() 21 | version = suggested_version_number 22 | end 23 | 24 | puts "Enter the version you want to release (" + version + ") " 25 | new_version_number = $stdin.gets.strip 26 | if new_version_number == "" 27 | new_version_number = version 28 | end 29 | 30 | replace_version_number(new_version_number) 31 | end 32 | 33 | desc "Release a new version of the Pod" 34 | task :release do 35 | 36 | puts "* Running version" 37 | sh "rake version" 38 | 39 | unless ENV['SKIP_CHECKS'] 40 | if `git symbolic-ref HEAD 2>/dev/null`.strip.split('/').last != 'master' 41 | $stderr.puts "[!] You need to be on the `master' branch in order to be able to do a release." 42 | exit 1 43 | end 44 | 45 | if `git tag`.strip.split("\n").include?(spec_version) 46 | $stderr.puts "[!] A tag for version `#{spec_version}' already exists. Change the version in the podspec" 47 | exit 1 48 | end 49 | 50 | puts "You are about to release `#{spec_version}`, is that correct? [y/n]" 51 | exit if $stdin.gets.strip.downcase != 'y' 52 | end 53 | 54 | puts "* Running specs" 55 | sh "rake spec" 56 | 57 | puts "* Linting the podspec" 58 | sh "pod lib lint" 59 | 60 | # Then release 61 | sh "git commit #{podspec_path} CHANGELOG.md -m 'Release #{spec_version}'" 62 | sh "git tag -a #{spec_version} -m 'Release #{spec_version}'" 63 | sh "git push origin master" 64 | sh "git push origin --tags" 65 | sh "pod push eladb #{podspec_path}" 66 | end 67 | 68 | # @return [Pod::Version] The version as reported by the Podspec. 69 | # 70 | def spec_version 71 | require 'cocoapods' 72 | spec = Pod::Specification.from_file(podspec_path) 73 | spec.version 74 | end 75 | 76 | # @return [Pod::Version] The version as reported by the Podspec from remote. 77 | # 78 | def remote_spec_version 79 | require 'cocoapods-core' 80 | 81 | if spec_file_exist_on_remote? 82 | remote_spec = eval(`git show origin/master:#{podspec_path}`) 83 | remote_spec.version 84 | else 85 | nil 86 | end 87 | end 88 | 89 | # @return [Bool] If the remote repository has a copy of the podpesc file or not. 90 | # 91 | def spec_file_exist_on_remote? 92 | test_condition = `if git rev-parse --verify --quiet origin/master:#{podspec_path} >/dev/null; 93 | then 94 | echo 'true' 95 | else 96 | echo 'false' 97 | fi` 98 | 99 | 'true' == test_condition.strip 100 | end 101 | 102 | # @return [String] The relative path of the Podspec. 103 | # 104 | def podspec_path 105 | podspecs = Dir.glob('*.podspec') 106 | if podspecs.count == 1 107 | podspecs.first 108 | else 109 | raise "Could not select a podspec" 110 | end 111 | end 112 | 113 | # @return [String] The suggested version number based on the local and remote version numbers. 114 | # 115 | def suggested_version_number 116 | if spec_version != remote_spec_version 117 | spec_version.to_s() 118 | else 119 | next_version(spec_version).to_s() 120 | end 121 | end 122 | 123 | # @param [Pod::Version] version 124 | # the version for which you need the next version 125 | # 126 | # @note It is computed by bumping the last component of the versino string by 1. 127 | # 128 | # @return [Pod::Version] The version that comes next after the version supplied. 129 | # 130 | def next_version(version) 131 | version_components = version.to_s().split("."); 132 | last = (version_components.last.to_i() + 1).to_s 133 | version_components[-1] = last 134 | Pod::Version.new(version_components.join(".")) 135 | end 136 | 137 | # @param [String] new_version_number 138 | # the new version number 139 | # 140 | # @note This methods replaces the version number in the podspec file with a new version number. 141 | # 142 | # @return void 143 | # 144 | def replace_version_number(new_version_number) 145 | text = File.read(podspec_path) 146 | text.gsub!(/(s.version( )*= ")#{spec_version}(")/, "\\1#{new_version_number}\\3") 147 | File.open(podspec_path, "w") { |file| file.puts text } 148 | end --------------------------------------------------------------------------------