├── InteractiveCustomTransitionsDemo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── rryan.xcuserdatad │ └── xcschemes │ ├── InteractiveCustomTransitionsDemo.xcscheme │ └── xcschememanagement.plist ├── InteractiveCustomTransitionsDemo ├── AViewController.swift ├── AppDelegate.swift ├── BViewController.swift ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── CViewController.swift ├── CustomNavigationController.swift ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist └── RootViewController.swift └── README.md /InteractiveCustomTransitionsDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 83395CC61A047A0E00BEA60F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83395CC51A047A0E00BEA60F /* AppDelegate.swift */; }; 11 | 83395CCB1A047A0E00BEA60F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 83395CC91A047A0E00BEA60F /* Main.storyboard */; }; 12 | 83395CCD1A047A0E00BEA60F /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 83395CCC1A047A0E00BEA60F /* Images.xcassets */; }; 13 | 83395CD01A047A0E00BEA60F /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 83395CCE1A047A0E00BEA60F /* LaunchScreen.xib */; }; 14 | 83395CE61A047A2400BEA60F /* CustomNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83395CE51A047A2400BEA60F /* CustomNavigationController.swift */; }; 15 | 83395CE81A047A3E00BEA60F /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83395CE71A047A3E00BEA60F /* RootViewController.swift */; }; 16 | 83395CEA1A047A4F00BEA60F /* AViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83395CE91A047A4F00BEA60F /* AViewController.swift */; }; 17 | 83395CEC1A047A5900BEA60F /* BViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83395CEB1A047A5900BEA60F /* BViewController.swift */; }; 18 | 83395CEE1A047A6300BEA60F /* CViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83395CED1A047A6300BEA60F /* CViewController.swift */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | 83395CC01A047A0E00BEA60F /* InteractiveCustomTransitionsDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = InteractiveCustomTransitionsDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | 83395CC41A047A0E00BEA60F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 24 | 83395CC51A047A0E00BEA60F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 25 | 83395CCA1A047A0E00BEA60F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 26 | 83395CCC1A047A0E00BEA60F /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 27 | 83395CCF1A047A0E00BEA60F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 28 | 83395CE51A047A2400BEA60F /* CustomNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomNavigationController.swift; sourceTree = ""; }; 29 | 83395CE71A047A3E00BEA60F /* RootViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootViewController.swift; sourceTree = ""; }; 30 | 83395CE91A047A4F00BEA60F /* AViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AViewController.swift; sourceTree = ""; }; 31 | 83395CEB1A047A5900BEA60F /* BViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BViewController.swift; sourceTree = ""; }; 32 | 83395CED1A047A6300BEA60F /* CViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CViewController.swift; sourceTree = ""; }; 33 | 83395CEF1A047DD500BEA60F /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 34 | /* End PBXFileReference section */ 35 | 36 | /* Begin PBXFrameworksBuildPhase section */ 37 | 83395CBD1A047A0E00BEA60F /* Frameworks */ = { 38 | isa = PBXFrameworksBuildPhase; 39 | buildActionMask = 2147483647; 40 | files = ( 41 | ); 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXFrameworksBuildPhase section */ 45 | 46 | /* Begin PBXGroup section */ 47 | 83395CB71A047A0E00BEA60F = { 48 | isa = PBXGroup; 49 | children = ( 50 | 83395CEF1A047DD500BEA60F /* README.md */, 51 | 83395CC21A047A0E00BEA60F /* InteractiveCustomTransitionsDemo */, 52 | 83395CC11A047A0E00BEA60F /* Products */, 53 | ); 54 | sourceTree = ""; 55 | }; 56 | 83395CC11A047A0E00BEA60F /* Products */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | 83395CC01A047A0E00BEA60F /* InteractiveCustomTransitionsDemo.app */, 60 | ); 61 | name = Products; 62 | sourceTree = ""; 63 | }; 64 | 83395CC21A047A0E00BEA60F /* InteractiveCustomTransitionsDemo */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 83395CC51A047A0E00BEA60F /* AppDelegate.swift */, 68 | 83395CE71A047A3E00BEA60F /* RootViewController.swift */, 69 | 83395CE51A047A2400BEA60F /* CustomNavigationController.swift */, 70 | 83395CE91A047A4F00BEA60F /* AViewController.swift */, 71 | 83395CEB1A047A5900BEA60F /* BViewController.swift */, 72 | 83395CED1A047A6300BEA60F /* CViewController.swift */, 73 | 83395CC91A047A0E00BEA60F /* Main.storyboard */, 74 | 83395CCC1A047A0E00BEA60F /* Images.xcassets */, 75 | 83395CCE1A047A0E00BEA60F /* LaunchScreen.xib */, 76 | 83395CC31A047A0E00BEA60F /* Supporting Files */, 77 | ); 78 | path = InteractiveCustomTransitionsDemo; 79 | sourceTree = ""; 80 | }; 81 | 83395CC31A047A0E00BEA60F /* Supporting Files */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | 83395CC41A047A0E00BEA60F /* Info.plist */, 85 | ); 86 | name = "Supporting Files"; 87 | sourceTree = ""; 88 | }; 89 | /* End PBXGroup section */ 90 | 91 | /* Begin PBXNativeTarget section */ 92 | 83395CBF1A047A0E00BEA60F /* InteractiveCustomTransitionsDemo */ = { 93 | isa = PBXNativeTarget; 94 | buildConfigurationList = 83395CDF1A047A0E00BEA60F /* Build configuration list for PBXNativeTarget "InteractiveCustomTransitionsDemo" */; 95 | buildPhases = ( 96 | 83395CBC1A047A0E00BEA60F /* Sources */, 97 | 83395CBD1A047A0E00BEA60F /* Frameworks */, 98 | 83395CBE1A047A0E00BEA60F /* Resources */, 99 | ); 100 | buildRules = ( 101 | ); 102 | dependencies = ( 103 | ); 104 | name = InteractiveCustomTransitionsDemo; 105 | productName = InteractiveCustomTransitionsDemo; 106 | productReference = 83395CC01A047A0E00BEA60F /* InteractiveCustomTransitionsDemo.app */; 107 | productType = "com.apple.product-type.application"; 108 | }; 109 | /* End PBXNativeTarget section */ 110 | 111 | /* Begin PBXProject section */ 112 | 83395CB81A047A0E00BEA60F /* Project object */ = { 113 | isa = PBXProject; 114 | attributes = { 115 | LastSwiftMigration = 0710; 116 | LastSwiftUpdateCheck = 0710; 117 | LastUpgradeCheck = 0830; 118 | ORGANIZATIONNAME = "Robert Ryan"; 119 | TargetAttributes = { 120 | 83395CBF1A047A0E00BEA60F = { 121 | CreatedOnToolsVersion = 6.1; 122 | LastSwiftMigration = 0830; 123 | }; 124 | }; 125 | }; 126 | buildConfigurationList = 83395CBB1A047A0E00BEA60F /* Build configuration list for PBXProject "InteractiveCustomTransitionsDemo" */; 127 | compatibilityVersion = "Xcode 3.2"; 128 | developmentRegion = English; 129 | hasScannedForEncodings = 0; 130 | knownRegions = ( 131 | en, 132 | Base, 133 | ); 134 | mainGroup = 83395CB71A047A0E00BEA60F; 135 | productRefGroup = 83395CC11A047A0E00BEA60F /* Products */; 136 | projectDirPath = ""; 137 | projectRoot = ""; 138 | targets = ( 139 | 83395CBF1A047A0E00BEA60F /* InteractiveCustomTransitionsDemo */, 140 | ); 141 | }; 142 | /* End PBXProject section */ 143 | 144 | /* Begin PBXResourcesBuildPhase section */ 145 | 83395CBE1A047A0E00BEA60F /* Resources */ = { 146 | isa = PBXResourcesBuildPhase; 147 | buildActionMask = 2147483647; 148 | files = ( 149 | 83395CCB1A047A0E00BEA60F /* Main.storyboard in Resources */, 150 | 83395CD01A047A0E00BEA60F /* LaunchScreen.xib in Resources */, 151 | 83395CCD1A047A0E00BEA60F /* Images.xcassets in Resources */, 152 | ); 153 | runOnlyForDeploymentPostprocessing = 0; 154 | }; 155 | /* End PBXResourcesBuildPhase section */ 156 | 157 | /* Begin PBXSourcesBuildPhase section */ 158 | 83395CBC1A047A0E00BEA60F /* Sources */ = { 159 | isa = PBXSourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | 83395CE61A047A2400BEA60F /* CustomNavigationController.swift in Sources */, 163 | 83395CEC1A047A5900BEA60F /* BViewController.swift in Sources */, 164 | 83395CEE1A047A6300BEA60F /* CViewController.swift in Sources */, 165 | 83395CE81A047A3E00BEA60F /* RootViewController.swift in Sources */, 166 | 83395CC61A047A0E00BEA60F /* AppDelegate.swift in Sources */, 167 | 83395CEA1A047A4F00BEA60F /* AViewController.swift in Sources */, 168 | ); 169 | runOnlyForDeploymentPostprocessing = 0; 170 | }; 171 | /* End PBXSourcesBuildPhase section */ 172 | 173 | /* Begin PBXVariantGroup section */ 174 | 83395CC91A047A0E00BEA60F /* Main.storyboard */ = { 175 | isa = PBXVariantGroup; 176 | children = ( 177 | 83395CCA1A047A0E00BEA60F /* Base */, 178 | ); 179 | name = Main.storyboard; 180 | sourceTree = ""; 181 | }; 182 | 83395CCE1A047A0E00BEA60F /* LaunchScreen.xib */ = { 183 | isa = PBXVariantGroup; 184 | children = ( 185 | 83395CCF1A047A0E00BEA60F /* Base */, 186 | ); 187 | name = LaunchScreen.xib; 188 | sourceTree = ""; 189 | }; 190 | /* End PBXVariantGroup section */ 191 | 192 | /* Begin XCBuildConfiguration section */ 193 | 83395CDD1A047A0E00BEA60F /* Debug */ = { 194 | isa = XCBuildConfiguration; 195 | buildSettings = { 196 | ALWAYS_SEARCH_USER_PATHS = NO; 197 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 198 | CLANG_CXX_LIBRARY = "libc++"; 199 | CLANG_ENABLE_MODULES = YES; 200 | CLANG_ENABLE_OBJC_ARC = YES; 201 | CLANG_WARN_BOOL_CONVERSION = YES; 202 | CLANG_WARN_CONSTANT_CONVERSION = YES; 203 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 204 | CLANG_WARN_EMPTY_BODY = YES; 205 | CLANG_WARN_ENUM_CONVERSION = YES; 206 | CLANG_WARN_INFINITE_RECURSION = YES; 207 | CLANG_WARN_INT_CONVERSION = YES; 208 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 209 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 210 | CLANG_WARN_UNREACHABLE_CODE = YES; 211 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 212 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 213 | COPY_PHASE_STRIP = NO; 214 | ENABLE_STRICT_OBJC_MSGSEND = YES; 215 | ENABLE_TESTABILITY = YES; 216 | GCC_C_LANGUAGE_STANDARD = gnu99; 217 | GCC_DYNAMIC_NO_PIC = NO; 218 | GCC_NO_COMMON_BLOCKS = YES; 219 | GCC_OPTIMIZATION_LEVEL = 0; 220 | GCC_PREPROCESSOR_DEFINITIONS = ( 221 | "DEBUG=1", 222 | "$(inherited)", 223 | ); 224 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 225 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 226 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 227 | GCC_WARN_UNDECLARED_SELECTOR = YES; 228 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 229 | GCC_WARN_UNUSED_FUNCTION = YES; 230 | GCC_WARN_UNUSED_VARIABLE = YES; 231 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 232 | MTL_ENABLE_DEBUG_INFO = YES; 233 | ONLY_ACTIVE_ARCH = YES; 234 | SDKROOT = iphoneos; 235 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 236 | }; 237 | name = Debug; 238 | }; 239 | 83395CDE1A047A0E00BEA60F /* Release */ = { 240 | isa = XCBuildConfiguration; 241 | buildSettings = { 242 | ALWAYS_SEARCH_USER_PATHS = NO; 243 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 244 | CLANG_CXX_LIBRARY = "libc++"; 245 | CLANG_ENABLE_MODULES = YES; 246 | CLANG_ENABLE_OBJC_ARC = YES; 247 | CLANG_WARN_BOOL_CONVERSION = YES; 248 | CLANG_WARN_CONSTANT_CONVERSION = YES; 249 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 250 | CLANG_WARN_EMPTY_BODY = YES; 251 | CLANG_WARN_ENUM_CONVERSION = YES; 252 | CLANG_WARN_INFINITE_RECURSION = YES; 253 | CLANG_WARN_INT_CONVERSION = YES; 254 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 255 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 256 | CLANG_WARN_UNREACHABLE_CODE = YES; 257 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 258 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 259 | COPY_PHASE_STRIP = YES; 260 | ENABLE_NS_ASSERTIONS = NO; 261 | ENABLE_STRICT_OBJC_MSGSEND = YES; 262 | GCC_C_LANGUAGE_STANDARD = gnu99; 263 | GCC_NO_COMMON_BLOCKS = YES; 264 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 265 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 266 | GCC_WARN_UNDECLARED_SELECTOR = YES; 267 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 268 | GCC_WARN_UNUSED_FUNCTION = YES; 269 | GCC_WARN_UNUSED_VARIABLE = YES; 270 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 271 | MTL_ENABLE_DEBUG_INFO = NO; 272 | SDKROOT = iphoneos; 273 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 274 | VALIDATE_PRODUCT = YES; 275 | }; 276 | name = Release; 277 | }; 278 | 83395CE01A047A0E00BEA60F /* Debug */ = { 279 | isa = XCBuildConfiguration; 280 | buildSettings = { 281 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 282 | INFOPLIST_FILE = InteractiveCustomTransitionsDemo/Info.plist; 283 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 284 | PRODUCT_BUNDLE_IDENTIFIER = "com.robertmryan.$(PRODUCT_NAME:rfc1034identifier)"; 285 | PRODUCT_NAME = "$(TARGET_NAME)"; 286 | SWIFT_VERSION = 3.0; 287 | }; 288 | name = Debug; 289 | }; 290 | 83395CE11A047A0E00BEA60F /* Release */ = { 291 | isa = XCBuildConfiguration; 292 | buildSettings = { 293 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 294 | INFOPLIST_FILE = InteractiveCustomTransitionsDemo/Info.plist; 295 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 296 | PRODUCT_BUNDLE_IDENTIFIER = "com.robertmryan.$(PRODUCT_NAME:rfc1034identifier)"; 297 | PRODUCT_NAME = "$(TARGET_NAME)"; 298 | SWIFT_VERSION = 3.0; 299 | }; 300 | name = Release; 301 | }; 302 | /* End XCBuildConfiguration section */ 303 | 304 | /* Begin XCConfigurationList section */ 305 | 83395CBB1A047A0E00BEA60F /* Build configuration list for PBXProject "InteractiveCustomTransitionsDemo" */ = { 306 | isa = XCConfigurationList; 307 | buildConfigurations = ( 308 | 83395CDD1A047A0E00BEA60F /* Debug */, 309 | 83395CDE1A047A0E00BEA60F /* Release */, 310 | ); 311 | defaultConfigurationIsVisible = 0; 312 | defaultConfigurationName = Release; 313 | }; 314 | 83395CDF1A047A0E00BEA60F /* Build configuration list for PBXNativeTarget "InteractiveCustomTransitionsDemo" */ = { 315 | isa = XCConfigurationList; 316 | buildConfigurations = ( 317 | 83395CE01A047A0E00BEA60F /* Debug */, 318 | 83395CE11A047A0E00BEA60F /* Release */, 319 | ); 320 | defaultConfigurationIsVisible = 0; 321 | defaultConfigurationName = Release; 322 | }; 323 | /* End XCConfigurationList section */ 324 | }; 325 | rootObject = 83395CB81A047A0E00BEA60F /* Project object */; 326 | } 327 | -------------------------------------------------------------------------------- /InteractiveCustomTransitionsDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /InteractiveCustomTransitionsDemo.xcodeproj/xcuserdata/rryan.xcuserdatad/xcschemes/InteractiveCustomTransitionsDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /InteractiveCustomTransitionsDemo.xcodeproj/xcuserdata/rryan.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | InteractiveCustomTransitionsDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 83395CBF1A047A0E00BEA60F 16 | 17 | primary 18 | 19 | 20 | 83395CD41A047A0E00BEA60F 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /InteractiveCustomTransitionsDemo/AViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AViewController.swift 3 | // InteractiveCustomTransitionsDemo 4 | // 5 | // Created by Robert Ryan on 10/31/14. 6 | // Copyright (c) 2014 Robert Ryan. All rights reserved. 7 | // 8 | // This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. 9 | // http://creativecommons.org/licenses/by-sa/4.0/ 10 | 11 | import UIKit 12 | 13 | // If you want to allow navigation controller to automatically detect whether you can swipe right to left 14 | // to push to the "next" scene, you should conform to `CustomNavigationControllerDelegate` and then 15 | // implement `pushToNextScene`. 16 | 17 | class AViewController: UIViewController, CustomNavigationControllerDelegate { 18 | 19 | func pushToNextScene() { 20 | performSegue(withIdentifier: "PushToB", sender: self) 21 | } 22 | 23 | @IBAction func pressedCancelButton(_ sender: AnyObject) { 24 | navigationController?.dismiss(animated: true) 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /InteractiveCustomTransitionsDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // InteractiveCustomTransitionsDemo 4 | // 5 | // Created by Robert Ryan on 10/31/14. 6 | // Copyright (c) 2014 Robert Ryan. All rights reserved. 7 | // 8 | // This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. 9 | // http://creativecommons.org/licenses/by-sa/4.0/ 10 | 11 | import UIKit 12 | 13 | @UIApplicationMain 14 | class AppDelegate: UIResponder, UIApplicationDelegate { 15 | 16 | var window: UIWindow? 17 | 18 | 19 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 20 | // Override point for customization after application launch. 21 | return true 22 | } 23 | 24 | func applicationWillResignActive(_ application: UIApplication) { 25 | // 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. 26 | // 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. 27 | } 28 | 29 | func applicationDidEnterBackground(_ application: UIApplication) { 30 | // 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. 31 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 32 | } 33 | 34 | func applicationWillEnterForeground(_ application: UIApplication) { 35 | // 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. 36 | } 37 | 38 | func applicationDidBecomeActive(_ application: UIApplication) { 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 | func applicationWillTerminate(_ application: UIApplication) { 43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 44 | } 45 | 46 | 47 | } 48 | 49 | -------------------------------------------------------------------------------- /InteractiveCustomTransitionsDemo/BViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BViewController.swift 3 | // InteractiveCustomTransitionsDemo 4 | // 5 | // Created by Robert Ryan on 10/31/14. 6 | // Copyright (c) 2014 Robert Ryan. All rights reserved. 7 | // 8 | // This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. 9 | // http://creativecommons.org/licenses/by-sa/4.0/ 10 | 11 | import UIKit 12 | 13 | // If you want to allow navigation controller to automatically detect whether you can swipe right to left 14 | // to push to the "next" scene, you should conform to `CustomNavigationControllerDelegate` and then 15 | // implement `pushToNextScene`. 16 | 17 | class BViewController: UIViewController, CustomNavigationControllerDelegate { 18 | 19 | func pushToNextScene() { 20 | performSegue(withIdentifier: "PushToC", sender: self) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /InteractiveCustomTransitionsDemo/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /InteractiveCustomTransitionsDemo/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 | 34 | 41 | 42 | 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 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 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 | 153 | 154 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /InteractiveCustomTransitionsDemo/CViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CViewController.swift 3 | // InteractiveCustomTransitionsDemo 4 | // 5 | // Created by Robert Ryan on 10/31/14. 6 | // Copyright (c) 2014 Robert Ryan. All rights reserved. 7 | // 8 | // This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. 9 | // http://creativecommons.org/licenses/by-sa/4.0/ 10 | 11 | import UIKit 12 | 13 | // Note, since this is the last scene in the demo, we did not conform to 14 | // `CustomNavigationControllerDelegate`, nor did we implement `pushToNextScene`. 15 | 16 | class CViewController: UIViewController { 17 | 18 | // this is intentionally blank 19 | 20 | } 21 | -------------------------------------------------------------------------------- /InteractiveCustomTransitionsDemo/CustomNavigationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomNavigationController.swift 3 | // deleteme10 4 | // 5 | // Created by Robert Ryan on 10/31/14. 6 | // Copyright (c) 2014 Robert Ryan. All rights reserved. 7 | // 8 | // This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. 9 | // http://creativecommons.org/licenses/by-sa/4.0/ 10 | 11 | import UIKit 12 | import UIKit.UIGestureRecognizerSubclass 13 | 14 | /// Custom Navigation Controller Protocol 15 | 16 | @objc protocol CustomNavigationControllerDelegate { 17 | 18 | /// Push to next scene 19 | /// 20 | /// If the view controller wants to enjoy swipe from right edge to push to the next scene, 21 | /// it has to implement `pushToNextScene` that initiates that segue (or programmatic 22 | /// `show` or `push`). 23 | 24 | func pushToNextScene() 25 | } 26 | 27 | class CustomNavigationController: UINavigationController { 28 | 29 | // If you are not doing custom transition to the initial navigation controller, you can 30 | // comment out the following three routines, as well as the `UIViewControllerTransitioningDelegate` 31 | // extension and `UIPresentationController` subclass below. 32 | 33 | public required init?(coder aDecoder: NSCoder) { 34 | super.init(coder: aDecoder) 35 | configure() 36 | } 37 | 38 | override init(rootViewController: UIViewController) { 39 | super.init(rootViewController: rootViewController) 40 | configure() 41 | } 42 | 43 | private func configure() { 44 | transitioningDelegate = self // for presenting the original navigation controller 45 | } 46 | 47 | override func viewDidLoad() { 48 | super.viewDidLoad() 49 | 50 | delegate = self // for navigation controller custom transitions 51 | 52 | let left = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleSwipeFromLeft(_:))) 53 | left.edges = .left 54 | view.addGestureRecognizer(left) 55 | 56 | let right = UIScreenEdgePanGestureRecognizer(target: self, action: #selector(handleSwipeFromRight(_:))) 57 | right.edges = .right 58 | view.addGestureRecognizer(right) 59 | } 60 | 61 | fileprivate var interactionController: UIPercentDrivenInteractiveTransition? 62 | 63 | func handleSwipeFromLeft(_ gesture: UIScreenEdgePanGestureRecognizer) { 64 | let percent = gesture.translation(in: gesture.view!).x / gesture.view!.bounds.size.width 65 | 66 | if gesture.state == .began { 67 | // if the navigation controller wasn't, itself, presented with a custom transition, 68 | // you insert this `guard` statement to prevent interactive dismissal performed below. 69 | // 70 | // guard viewControllers.count > 1 else { 71 | // gesture.state = .cancelled 72 | // return 73 | // } 74 | 75 | interactionController = UIPercentDrivenInteractiveTransition() 76 | if viewControllers.count > 1 { 77 | popViewController(animated: true) 78 | } else { 79 | dismiss(animated: true) 80 | } 81 | } else if gesture.state == .changed { 82 | interactionController?.update(percent) 83 | } else if gesture.state == .ended { 84 | if percent > 0.5 && gesture.state != .cancelled { 85 | interactionController?.finish() 86 | } else { 87 | interactionController?.cancel() 88 | } 89 | interactionController = nil 90 | } 91 | } 92 | 93 | func handleSwipeFromRight(_ gesture: UIScreenEdgePanGestureRecognizer) { 94 | let percent = -gesture.translation(in: gesture.view!).x / gesture.view!.bounds.size.width 95 | 96 | if gesture.state == .began { 97 | guard let currentViewController = viewControllers.last as? CustomNavigationControllerDelegate else { 98 | gesture.state = .cancelled 99 | return 100 | } 101 | interactionController = UIPercentDrivenInteractiveTransition() 102 | currentViewController.pushToNextScene() 103 | } else if gesture.state == .changed { 104 | interactionController?.update(percent) 105 | } else if gesture.state == .ended { 106 | if percent > 0.5 { 107 | interactionController?.finish() 108 | } else { 109 | interactionController?.cancel() 110 | } 111 | interactionController = nil 112 | } 113 | } 114 | 115 | } 116 | 117 | // MARK: - UINavigationControllerDelegate 118 | // 119 | // Use this for custom transitions as you push/pop between the various child view controllers 120 | // of the navigation controller. If you don't need a custom animation there, you can comment this 121 | // out. 122 | 123 | extension CustomNavigationController: UINavigationControllerDelegate { 124 | 125 | func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { 126 | 127 | if operation == .push { 128 | return ForwardAnimator() 129 | } else if operation == .pop { 130 | return BackAnimator() 131 | } 132 | return nil 133 | } 134 | 135 | func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { 136 | return interactionController 137 | } 138 | 139 | } 140 | 141 | // MARK: - UIViewControllerTransitioningDelegate 142 | // 143 | // This is needed for the animation when we initially present the navigation controller. 144 | // If you're only looking for custom animations as you push/pop between the child view 145 | // controllers of the navigation controller, this is not needed. This is only for the 146 | // custom transition of the initial `present` and `dismiss` of the navigation controller 147 | // itself. 148 | 149 | extension CustomNavigationController: UIViewControllerTransitioningDelegate { 150 | 151 | func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { 152 | return ForwardAnimator() 153 | } 154 | 155 | func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { 156 | return BackAnimator() 157 | } 158 | 159 | func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { 160 | return interactionController 161 | } 162 | 163 | func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { 164 | return interactionController 165 | } 166 | 167 | func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { 168 | return PresentationController(presentedViewController: presented, presenting: presenting) 169 | } 170 | 171 | } 172 | 173 | class PresentationController: UIPresentationController { 174 | override var shouldRemovePresentersView: Bool { return true } 175 | } 176 | 177 | class ForwardAnimator : NSObject, UIViewControllerAnimatedTransitioning { 178 | 179 | func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 180 | return 0.5 181 | } 182 | 183 | func animateTransition(using context: UIViewControllerContextTransitioning) { 184 | let toView = context.viewController(forKey: .to)!.view! 185 | 186 | context.containerView.addSubview(toView) 187 | 188 | toView.alpha = 0.0 189 | 190 | UIView.animate(withDuration: transitionDuration(using: context), animations: { 191 | toView.alpha = 1.0 192 | }, completion: { finished in 193 | context.completeTransition(!context.transitionWasCancelled) 194 | }) 195 | } 196 | 197 | } 198 | 199 | class BackAnimator : NSObject, UIViewControllerAnimatedTransitioning { 200 | 201 | func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 202 | return 0.5 203 | } 204 | 205 | func animateTransition(using context: UIViewControllerContextTransitioning) { 206 | let toView = context.viewController(forKey: .to)!.view! 207 | let fromView = context.viewController(forKey: .from)!.view! 208 | 209 | context.containerView.insertSubview(toView, belowSubview: fromView) 210 | 211 | UIView.animate(withDuration: transitionDuration(using: context), animations: { 212 | fromView.alpha = 0.0 213 | }, completion: { finished in 214 | context.completeTransition(!context.transitionWasCancelled) 215 | }) 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /InteractiveCustomTransitionsDemo/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" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /InteractiveCustomTransitionsDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /InteractiveCustomTransitionsDemo/RootViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootViewController.swift 3 | // InteractiveCustomTransitionsDemo 4 | // 5 | // Created by Robert Ryan on 10/31/14. 6 | // Copyright (c) 2014 Robert Ryan. All rights reserved. 7 | // 8 | // This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. 9 | // http://creativecommons.org/licenses/by-sa/4.0/ 10 | 11 | import UIKit 12 | 13 | // This is the "root" scene's view controller. The presentation of the navigation controller 14 | // and it's children is done entirely in Interface Builder, though you obviously _could_ 15 | // implement it programmatically if you wanted to. 16 | 17 | class RootViewController: UIViewController { 18 | 19 | // this is intentionally blank 20 | 21 | } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Interactive Custom Transitions Demo in Swift 2 | 3 | This is an demonstration of a Custom Transitions and Interactive Custom Transitions. 4 | 5 | The idea was to illustrate a custom "presentation" transition between the scenes in a navigation controller. The goal was to allow interactive gestures to swipe between three different scenes, A, B, and C underneath the navigation controller to illustrate custom "push" and "pop" transitions between the navigation controller's scenes. 6 | 7 | There's also code if you wanted to have a custom transition as the navigation controller, itself. I've placed this in a separate `UIViewControllerTransitioningDelegate` extension, separate from the `UINavigationControllerDelegate` extension that does the custom transitions between the navigation controller's child scenes. Clearly, if you don't want a custom transition as you initially present the navigation controller, you can remove the `UIViewControllerTransitioningDelegate` extension. Likewise, if you don't need custom transitions between the navigation controller's children, you can comment out the `UINavigationControllerDelegate` extension. 8 | 9 | To implement this, the suggestion was to implement a custom subclass of the navigation controller that would instantiate the custom animation controllers and interaction controllers as necessary: 10 | 11 | ![schematic](http://i.stack.imgur.com/TCYGC.png) 12 | 13 | The heart of this is entirely in the custom navigation controller which: 14 | 15 | - Adds gesture recognizers to its view 16 | 17 | - If the gesture is right to left (i.e. push), this looks to see if the source controller conforms to `CustomNavigationControllerDelegate` protocol, namely a method `pushToNextScene` that will initiate the transition to the next scene. 18 | 19 | - If the gesture is left to right, this looks to see if there is more than one view controller in the stack, and if so, initiates a pop. If not, we're at the top level and it initiates a dismiss back to the root. 20 | 21 | - This sets the navigation controller's `delegate` (so that you can customize transition as you push/pop between the navigation controller's own child view controllers). 22 | 23 | - This sets the navigation controller's `transitionDelegate` (so that you can customize the transition as you present and dismiss the navigation controller itself). 24 | 25 | See http://stackoverflow.com/questions/26680311/interactive-delegate-methods-never-called/26683223. 26 | 27 | This is for illustrative purposes only. 28 | 29 | Developed in Swift 3 on Xcode 8.3 for iOS 8.1. Theoretically it should work fine in iOS 7.0+. Also, the basic ideas are equally applicable for both Swift and Objective-C. 30 | 31 | ## License 32 | 33 | Copyright © 2014-2017. Robert Ryan. All rights reserved. 34 | 35 | Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. 36 | 37 | -- 38 | 39 | 31 October 2014 40 | --------------------------------------------------------------------------------