├── Button Icons.sketch ├── Data ├── metadata └── version ├── Container Transitions.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── Container Transitions.xcscheme ├── Container Transitions ├── Animator.h ├── Animator.m ├── AppDelegate.h ├── AppDelegate.m ├── ChildViewController.h ├── ChildViewController.m ├── ContainerViewController.h ├── ContainerViewController.m ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── First Selected.imageset │ │ ├── Contents.json │ │ ├── First Selected.png │ │ └── First Selected@2x.png │ ├── First.imageset │ │ ├── Contents.json │ │ ├── First.png │ │ └── First@2x.png │ ├── LaunchImage.launchimage │ │ └── Contents.json │ ├── Second Selected.imageset │ │ ├── Contents.json │ │ ├── Second Selected.png │ │ └── Second Selected@2x.png │ ├── Second.imageset │ │ ├── Contents.json │ │ ├── Second.png │ │ └── Second@2x.png │ ├── Third Selected.imageset │ │ ├── Contents.json │ │ ├── Third Selected.png │ │ └── Third Selected@2x.png │ └── Third.imageset │ │ ├── Contents.json │ │ ├── Third.png │ │ └── Third@2x.png ├── Info.plist ├── Prefix.pch └── main.m ├── README.md └── stage-3.gif /Button Icons.sketch/Data: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osteslag/custom-container-transitions/fcf9dd6953ff62d5abbb593e421785caa6c48f17/Button Icons.sketch/Data -------------------------------------------------------------------------------- /Button Icons.sketch/metadata: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | app 6 | com.bohemiancoding.sketch3 7 | build 8 | 7597 9 | commit 10 | 463d76bf23dc890960e184011f80b3dacac572b9 11 | fonts 12 | 13 | length 14 | 65055 15 | version 16 | 36 17 | 18 | 19 | -------------------------------------------------------------------------------- /Button Icons.sketch/version: -------------------------------------------------------------------------------- 1 | 36 -------------------------------------------------------------------------------- /Container Transitions.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 29D3A6E3190C44CC00038CEC /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29D3A6E2190C44CC00038CEC /* Foundation.framework */; }; 11 | 29D3A6E5190C44CC00038CEC /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29D3A6E4190C44CC00038CEC /* CoreGraphics.framework */; }; 12 | 29D3A6E7190C44CC00038CEC /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29D3A6E6190C44CC00038CEC /* UIKit.framework */; }; 13 | 29D3A6EF190C44CC00038CEC /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D3A6EE190C44CC00038CEC /* main.m */; }; 14 | 29D3A6F3190C44CC00038CEC /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D3A6F2190C44CC00038CEC /* AppDelegate.m */; }; 15 | 29D3A6F5190C44CC00038CEC /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 29D3A6F4190C44CC00038CEC /* Images.xcassets */; }; 16 | 29D3A713190C4AA800038CEC /* ChildViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D3A712190C4AA800038CEC /* ChildViewController.m */; }; 17 | 29D3A716190C5EC100038CEC /* ContainerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D3A715190C5EC100038CEC /* ContainerViewController.m */; }; 18 | 29D3A71C191054E400038CEC /* Animator.m in Sources */ = {isa = PBXBuildFile; fileRef = 29D3A71B191054E400038CEC /* Animator.m */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | 29D3A6DF190C44CC00038CEC /* Transitions.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Transitions.app; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | 29D3A6E2190C44CC00038CEC /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 24 | 29D3A6E4190C44CC00038CEC /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 25 | 29D3A6E6190C44CC00038CEC /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 26 | 29D3A6EA190C44CC00038CEC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 27 | 29D3A6EE190C44CC00038CEC /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 28 | 29D3A6F0190C44CC00038CEC /* Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Prefix.pch; sourceTree = ""; }; 29 | 29D3A6F1190C44CC00038CEC /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 30 | 29D3A6F2190C44CC00038CEC /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 31 | 29D3A6F4190C44CC00038CEC /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 32 | 29D3A6FB190C44CC00038CEC /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 33 | 29D3A711190C4AA800038CEC /* ChildViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ChildViewController.h; sourceTree = ""; }; 34 | 29D3A712190C4AA800038CEC /* ChildViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ChildViewController.m; sourceTree = ""; }; 35 | 29D3A714190C5EC100038CEC /* ContainerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContainerViewController.h; sourceTree = ""; }; 36 | 29D3A715190C5EC100038CEC /* ContainerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContainerViewController.m; sourceTree = ""; }; 37 | 29D3A71A191054E400038CEC /* Animator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Animator.h; sourceTree = ""; }; 38 | 29D3A71B191054E400038CEC /* Animator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Animator.m; sourceTree = ""; }; 39 | /* End PBXFileReference section */ 40 | 41 | /* Begin PBXFrameworksBuildPhase section */ 42 | 29D3A6DC190C44CC00038CEC /* Frameworks */ = { 43 | isa = PBXFrameworksBuildPhase; 44 | buildActionMask = 2147483647; 45 | files = ( 46 | 29D3A6E5190C44CC00038CEC /* CoreGraphics.framework in Frameworks */, 47 | 29D3A6E7190C44CC00038CEC /* UIKit.framework in Frameworks */, 48 | 29D3A6E3190C44CC00038CEC /* Foundation.framework in Frameworks */, 49 | ); 50 | runOnlyForDeploymentPostprocessing = 0; 51 | }; 52 | /* End PBXFrameworksBuildPhase section */ 53 | 54 | /* Begin PBXGroup section */ 55 | 29D3A6D6190C44CC00038CEC = { 56 | isa = PBXGroup; 57 | children = ( 58 | 29D3A6E8190C44CC00038CEC /* Container Transitions */, 59 | 29D3A6E1190C44CC00038CEC /* Frameworks */, 60 | 29D3A6E0190C44CC00038CEC /* Products */, 61 | ); 62 | sourceTree = ""; 63 | }; 64 | 29D3A6E0190C44CC00038CEC /* Products */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 29D3A6DF190C44CC00038CEC /* Transitions.app */, 68 | ); 69 | name = Products; 70 | sourceTree = ""; 71 | }; 72 | 29D3A6E1190C44CC00038CEC /* Frameworks */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 29D3A6E2190C44CC00038CEC /* Foundation.framework */, 76 | 29D3A6E4190C44CC00038CEC /* CoreGraphics.framework */, 77 | 29D3A6E6190C44CC00038CEC /* UIKit.framework */, 78 | 29D3A6FB190C44CC00038CEC /* XCTest.framework */, 79 | ); 80 | name = Frameworks; 81 | sourceTree = ""; 82 | }; 83 | 29D3A6E8190C44CC00038CEC /* Container Transitions */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 29D3A6F1190C44CC00038CEC /* AppDelegate.h */, 87 | 29D3A6F2190C44CC00038CEC /* AppDelegate.m */, 88 | 29D3A714190C5EC100038CEC /* ContainerViewController.h */, 89 | 29D3A715190C5EC100038CEC /* ContainerViewController.m */, 90 | 29D3A711190C4AA800038CEC /* ChildViewController.h */, 91 | 29D3A712190C4AA800038CEC /* ChildViewController.m */, 92 | 29D3A71A191054E400038CEC /* Animator.h */, 93 | 29D3A71B191054E400038CEC /* Animator.m */, 94 | 29D3A6F4190C44CC00038CEC /* Images.xcassets */, 95 | 29D3A6E9190C44CC00038CEC /* Supporting Files */, 96 | ); 97 | path = "Container Transitions"; 98 | sourceTree = ""; 99 | }; 100 | 29D3A6E9190C44CC00038CEC /* Supporting Files */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 29D3A6EA190C44CC00038CEC /* Info.plist */, 104 | 29D3A6EE190C44CC00038CEC /* main.m */, 105 | 29D3A6F0190C44CC00038CEC /* Prefix.pch */, 106 | ); 107 | name = "Supporting Files"; 108 | sourceTree = ""; 109 | }; 110 | /* End PBXGroup section */ 111 | 112 | /* Begin PBXNativeTarget section */ 113 | 29D3A6DE190C44CC00038CEC /* App */ = { 114 | isa = PBXNativeTarget; 115 | buildConfigurationList = 29D3A70B190C44CD00038CEC /* Build configuration list for PBXNativeTarget "App" */; 116 | buildPhases = ( 117 | 29D3A6DB190C44CC00038CEC /* Sources */, 118 | 29D3A6DC190C44CC00038CEC /* Frameworks */, 119 | 29D3A6DD190C44CC00038CEC /* Resources */, 120 | ); 121 | buildRules = ( 122 | ); 123 | dependencies = ( 124 | ); 125 | name = App; 126 | productName = "Container Transitions"; 127 | productReference = 29D3A6DF190C44CC00038CEC /* Transitions.app */; 128 | productType = "com.apple.product-type.application"; 129 | }; 130 | /* End PBXNativeTarget section */ 131 | 132 | /* Begin PBXProject section */ 133 | 29D3A6D7190C44CC00038CEC /* Project object */ = { 134 | isa = PBXProject; 135 | attributes = { 136 | CLASSPREFIX = DG; 137 | LastUpgradeCheck = 0510; 138 | }; 139 | buildConfigurationList = 29D3A6DA190C44CC00038CEC /* Build configuration list for PBXProject "Container Transitions" */; 140 | compatibilityVersion = "Xcode 3.2"; 141 | developmentRegion = English; 142 | hasScannedForEncodings = 0; 143 | knownRegions = ( 144 | en, 145 | ); 146 | mainGroup = 29D3A6D6190C44CC00038CEC; 147 | productRefGroup = 29D3A6E0190C44CC00038CEC /* Products */; 148 | projectDirPath = ""; 149 | projectRoot = ""; 150 | targets = ( 151 | 29D3A6DE190C44CC00038CEC /* App */, 152 | ); 153 | }; 154 | /* End PBXProject section */ 155 | 156 | /* Begin PBXResourcesBuildPhase section */ 157 | 29D3A6DD190C44CC00038CEC /* Resources */ = { 158 | isa = PBXResourcesBuildPhase; 159 | buildActionMask = 2147483647; 160 | files = ( 161 | 29D3A6F5190C44CC00038CEC /* Images.xcassets in Resources */, 162 | ); 163 | runOnlyForDeploymentPostprocessing = 0; 164 | }; 165 | /* End PBXResourcesBuildPhase section */ 166 | 167 | /* Begin PBXSourcesBuildPhase section */ 168 | 29D3A6DB190C44CC00038CEC /* Sources */ = { 169 | isa = PBXSourcesBuildPhase; 170 | buildActionMask = 2147483647; 171 | files = ( 172 | 29D3A71C191054E400038CEC /* Animator.m in Sources */, 173 | 29D3A6EF190C44CC00038CEC /* main.m in Sources */, 174 | 29D3A6F3190C44CC00038CEC /* AppDelegate.m in Sources */, 175 | 29D3A716190C5EC100038CEC /* ContainerViewController.m in Sources */, 176 | 29D3A713190C4AA800038CEC /* ChildViewController.m in Sources */, 177 | ); 178 | runOnlyForDeploymentPostprocessing = 0; 179 | }; 180 | /* End PBXSourcesBuildPhase section */ 181 | 182 | /* Begin XCBuildConfiguration section */ 183 | 29D3A709190C44CD00038CEC /* Debug */ = { 184 | isa = XCBuildConfiguration; 185 | buildSettings = { 186 | ALWAYS_SEARCH_USER_PATHS = NO; 187 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 188 | CLANG_CXX_LIBRARY = "libc++"; 189 | CLANG_ENABLE_MODULES = YES; 190 | CLANG_ENABLE_OBJC_ARC = YES; 191 | CLANG_WARN_BOOL_CONVERSION = YES; 192 | CLANG_WARN_CONSTANT_CONVERSION = YES; 193 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 194 | CLANG_WARN_EMPTY_BODY = YES; 195 | CLANG_WARN_ENUM_CONVERSION = YES; 196 | CLANG_WARN_INT_CONVERSION = YES; 197 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 198 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 199 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 200 | COPY_PHASE_STRIP = NO; 201 | GCC_C_LANGUAGE_STANDARD = gnu99; 202 | GCC_DYNAMIC_NO_PIC = NO; 203 | GCC_OPTIMIZATION_LEVEL = 0; 204 | GCC_PREPROCESSOR_DEFINITIONS = ( 205 | "DEBUG=1", 206 | "$(inherited)", 207 | ); 208 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 209 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 210 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 211 | GCC_WARN_UNDECLARED_SELECTOR = YES; 212 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 213 | GCC_WARN_UNUSED_FUNCTION = YES; 214 | GCC_WARN_UNUSED_VARIABLE = YES; 215 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 216 | ONLY_ACTIVE_ARCH = YES; 217 | SDKROOT = iphoneos; 218 | TARGETED_DEVICE_FAMILY = "1,2"; 219 | }; 220 | name = Debug; 221 | }; 222 | 29D3A70A190C44CD00038CEC /* Release */ = { 223 | isa = XCBuildConfiguration; 224 | buildSettings = { 225 | ALWAYS_SEARCH_USER_PATHS = NO; 226 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 227 | CLANG_CXX_LIBRARY = "libc++"; 228 | CLANG_ENABLE_MODULES = YES; 229 | CLANG_ENABLE_OBJC_ARC = YES; 230 | CLANG_WARN_BOOL_CONVERSION = YES; 231 | CLANG_WARN_CONSTANT_CONVERSION = YES; 232 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 233 | CLANG_WARN_EMPTY_BODY = YES; 234 | CLANG_WARN_ENUM_CONVERSION = YES; 235 | CLANG_WARN_INT_CONVERSION = YES; 236 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 237 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 238 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 239 | COPY_PHASE_STRIP = YES; 240 | ENABLE_NS_ASSERTIONS = NO; 241 | GCC_C_LANGUAGE_STANDARD = gnu99; 242 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 243 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 244 | GCC_WARN_UNDECLARED_SELECTOR = YES; 245 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 246 | GCC_WARN_UNUSED_FUNCTION = YES; 247 | GCC_WARN_UNUSED_VARIABLE = YES; 248 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 249 | SDKROOT = iphoneos; 250 | TARGETED_DEVICE_FAMILY = "1,2"; 251 | VALIDATE_PRODUCT = YES; 252 | }; 253 | name = Release; 254 | }; 255 | 29D3A70C190C44CD00038CEC /* Debug */ = { 256 | isa = XCBuildConfiguration; 257 | buildSettings = { 258 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 259 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 260 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 261 | GCC_PREFIX_HEADER = "Container Transitions/Prefix.pch"; 262 | INFOPLIST_FILE = "$(SRCROOT)/Container Transitions/Info.plist"; 263 | PRODUCT_NAME = Transitions; 264 | WRAPPER_EXTENSION = app; 265 | }; 266 | name = Debug; 267 | }; 268 | 29D3A70D190C44CD00038CEC /* Release */ = { 269 | isa = XCBuildConfiguration; 270 | buildSettings = { 271 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 272 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 273 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 274 | GCC_PREFIX_HEADER = "Container Transitions/Prefix.pch"; 275 | INFOPLIST_FILE = "$(SRCROOT)/Container Transitions/Info.plist"; 276 | PRODUCT_NAME = Transitions; 277 | WRAPPER_EXTENSION = app; 278 | }; 279 | name = Release; 280 | }; 281 | /* End XCBuildConfiguration section */ 282 | 283 | /* Begin XCConfigurationList section */ 284 | 29D3A6DA190C44CC00038CEC /* Build configuration list for PBXProject "Container Transitions" */ = { 285 | isa = XCConfigurationList; 286 | buildConfigurations = ( 287 | 29D3A709190C44CD00038CEC /* Debug */, 288 | 29D3A70A190C44CD00038CEC /* Release */, 289 | ); 290 | defaultConfigurationIsVisible = 0; 291 | defaultConfigurationName = Release; 292 | }; 293 | 29D3A70B190C44CD00038CEC /* Build configuration list for PBXNativeTarget "App" */ = { 294 | isa = XCConfigurationList; 295 | buildConfigurations = ( 296 | 29D3A70C190C44CD00038CEC /* Debug */, 297 | 29D3A70D190C44CD00038CEC /* Release */, 298 | ); 299 | defaultConfigurationIsVisible = 0; 300 | defaultConfigurationName = Release; 301 | }; 302 | /* End XCConfigurationList section */ 303 | }; 304 | rootObject = 29D3A6D7190C44CC00038CEC /* Project object */; 305 | } 306 | -------------------------------------------------------------------------------- /Container Transitions.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Container Transitions.xcodeproj/xcshareddata/xcschemes/Container Transitions.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /Container Transitions/Animator.h: -------------------------------------------------------------------------------- 1 | // 2 | // Animator.h 3 | // NavigationTransitionTest 4 | // 5 | // Created by Chris Eidhof on 9/27/13. 6 | // Copyright (c) 2013 Chris Eidhof. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface Animator : NSObject 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Container Transitions/Animator.m: -------------------------------------------------------------------------------- 1 | // 2 | // Animator.m 3 | // NavigationTransitionTest 4 | // 5 | // Created by Chris Eidhof on 9/27/13. 6 | // Copyright (c) 2013 Chris Eidhof. All rights reserved. 7 | // 8 | 9 | #import "Animator.h" 10 | 11 | @implementation Animator 12 | 13 | - (NSTimeInterval)transitionDuration:(id )transitionContext 14 | { 15 | return 1; 16 | } 17 | 18 | - (void)animateTransition:(id)transitionContext 19 | { 20 | UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; 21 | UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; 22 | [[transitionContext containerView] addSubview:toViewController.view]; 23 | toViewController.view.alpha = 0; 24 | 25 | [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ 26 | fromViewController.view.transform = CGAffineTransformMakeScale(0.1, 0.1); 27 | toViewController.view.alpha = 1; 28 | } completion:^(BOOL finished) { 29 | fromViewController.view.transform = CGAffineTransformIdentity; 30 | [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; 31 | 32 | }]; 33 | } 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /Container Transitions/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Container Transitions 4 | // 5 | // Created by Joachim Bondo on 30/04/2014. 6 | // Copyright (c) 2014 Joachim Bondo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | @end 13 | -------------------------------------------------------------------------------- /Container Transitions/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Container Transitions 4 | // 5 | // Created by Joachim Bondo on 30/04/2014. 6 | // Copyright (c) 2014 Joachim Bondo. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "ContainerViewController.h" 11 | #import "ChildViewController.h" 12 | #import "Animator.h" 13 | 14 | @interface AppDelegate () 15 | @property (nonatomic, strong) UIWindow *privateWindow; 16 | @end 17 | 18 | @implementation AppDelegate 19 | 20 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 21 | 22 | self.privateWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 23 | self.privateWindow.rootViewController = [self _configuredRootViewController]; 24 | [self.privateWindow makeKeyAndVisible]; 25 | 26 | return YES; 27 | } 28 | 29 | #pragma mark - ContainerViewControllerDelegate Protocol 30 | 31 | - (id)containerViewController:(ContainerViewController *)containerViewController animationControllerForTransitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController { 32 | return [[Animator alloc] init]; 33 | } 34 | 35 | #pragma mark - Private Methods 36 | 37 | - (UIViewController *)_configuredRootViewController { 38 | 39 | NSArray *childViewControllers = [self _configuredChildViewControllers]; 40 | ContainerViewController *rootViewController = [[ContainerViewController alloc] initWithViewControllers:childViewControllers]; 41 | // rootViewController.delegate = self; 42 | 43 | return rootViewController; 44 | } 45 | 46 | - (NSArray *)_configuredChildViewControllers { 47 | 48 | // Set colors, titles and tab bar button icons which are used by the ContainerViewController class for display in its button pane. 49 | 50 | NSMutableArray *childViewControllers = [[NSMutableArray alloc] initWithCapacity:3]; 51 | NSArray *configurations = @[ 52 | @{@"title": @"First", @"color": [UIColor colorWithRed:0.4f green:0.8f blue:1 alpha:1]}, 53 | @{@"title": @"Second", @"color": [UIColor colorWithRed:1 green:0.4f blue:0.8f alpha:1]}, 54 | @{@"title": @"Third", @"color": [UIColor colorWithRed:1 green:0.8f blue:0.4f alpha:1]}, 55 | ]; 56 | 57 | for (NSDictionary *configuration in configurations) { 58 | ChildViewController *childViewController = [[ChildViewController alloc] init]; 59 | 60 | childViewController.title = configuration[@"title"]; 61 | childViewController.themeColor = configuration[@"color"]; 62 | childViewController.tabBarItem.image = [UIImage imageNamed:configuration[@"title"]]; 63 | childViewController.tabBarItem.selectedImage = [UIImage imageNamed:[configuration[@"title"] stringByAppendingString:@" Selected"]]; 64 | 65 | [childViewControllers addObject:childViewController]; 66 | } 67 | 68 | return childViewControllers; 69 | } 70 | 71 | @end 72 | -------------------------------------------------------------------------------- /Container Transitions/ChildViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ChildViewController.h 3 | // Container Transitions 4 | // 5 | // Created by Joachim Bondo on 30/04/2014. 6 | // Copyright (c) 2014 Joachim Bondo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /// A minimalistic view controller class, the perfect child view controller for testing view controller containment. 12 | @interface ChildViewController : UIViewController 13 | 14 | /** When the view is instantiated, this color will be applied as the view's backgroundColor and tintColor. 15 | @discussion This allows us to set up the view styling without necessarily having to instantiate the view. 16 | */ 17 | @property (nonatomic, strong) UIColor *themeColor; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Container Transitions/ChildViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ChildViewController.m 3 | // Container Transitions 4 | // 5 | // Created by Joachim Bondo on 30/04/2014. 6 | // Copyright (c) 2014 Joachim Bondo. All rights reserved. 7 | // 8 | 9 | #import "ChildViewController.h" 10 | 11 | @interface ChildViewController () 12 | @property (nonatomic, strong) UILabel *privateTitleLabel; 13 | @end 14 | 15 | @implementation ChildViewController 16 | 17 | - (void)setTitle:(NSString *)title { 18 | super.title = title; 19 | [self _updateAppearance]; 20 | } 21 | 22 | - (void)setThemeColor:(UIColor *)themeColor { 23 | _themeColor = themeColor; 24 | [self _updateAppearance]; 25 | } 26 | 27 | - (void)loadView { 28 | 29 | self.privateTitleLabel = [[UILabel alloc] init]; 30 | self.privateTitleLabel.backgroundColor = [UIColor clearColor]; 31 | self.privateTitleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]; 32 | self.privateTitleLabel.textAlignment = NSTextAlignmentCenter; 33 | self.privateTitleLabel.numberOfLines = 0; 34 | [self.privateTitleLabel setTranslatesAutoresizingMaskIntoConstraints:NO]; 35 | 36 | self.view = [[UIView alloc] init]; 37 | [self.view addSubview:self.privateTitleLabel]; 38 | 39 | // Center label in view. 40 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.privateTitleLabel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeWidth multiplier:0.6f constant:0]]; 41 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.privateTitleLabel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]]; 42 | [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.privateTitleLabel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]]; 43 | } 44 | 45 | - (void)viewDidLoad { 46 | 47 | self.privateTitleLabel.text = self.title; 48 | [self _updateAppearance]; 49 | 50 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector (_contentSizeCategoryDidChangeWithNotification:) name:UIContentSizeCategoryDidChangeNotification object:nil]; 51 | } 52 | 53 | - (void)dealloc { 54 | if ([self isViewLoaded]) { 55 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 56 | } 57 | } 58 | 59 | #pragma mark - Private Methods 60 | 61 | - (void)_updateAppearance { 62 | if ([self isViewLoaded]) { 63 | self.privateTitleLabel.text = self.title; 64 | self.view.backgroundColor = self.themeColor; 65 | self.view.tintColor = self.themeColor; 66 | } 67 | } 68 | 69 | - (void)_contentSizeCategoryDidChangeWithNotification:(NSNotification *)notification { 70 | self.privateTitleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline]; 71 | [self.privateTitleLabel invalidateIntrinsicContentSize]; 72 | } 73 | 74 | @end 75 | -------------------------------------------------------------------------------- /Container Transitions/ContainerViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ContainerViewController.h 3 | // Container Transitions 4 | // 5 | // Created by Joachim Bondo on 30/04/2014. 6 | // 7 | 8 | @import UIKit; 9 | @import Foundation; 10 | 11 | @protocol ContainerViewControllerDelegate; 12 | 13 | /** A very simple container view controller for demonstrating containment in an environment different from UINavigationController and UITabBarController. 14 | @discussion This class implements support for non-interactive custom view controller transitions. 15 | @note One of the many current limitations, besides not supporting interactive transitions, is that you cannot change view controllers after the object has been initialized. 16 | */ 17 | @interface ContainerViewController : UIViewController 18 | 19 | /// The container view controller delegate receiving the protocol callbacks. 20 | @property (nonatomic, weak) iddelegate; 21 | 22 | /// The view controllers currently managed by the container view controller. 23 | @property (nonatomic, copy, readonly) NSArray *viewControllers; 24 | 25 | /// The currently selected and visible child view controller. 26 | @property (nonatomic, assign) UIViewController *selectedViewController; 27 | 28 | /** Designated initializer. 29 | @note The view controllers array cannot be changed after initialization. 30 | */ 31 | - (instancetype)initWithViewControllers:(NSArray *)viewControllers; 32 | 33 | @end 34 | 35 | @protocol ContainerViewControllerDelegate 36 | @optional 37 | /** Informs the delegate that the user selected view controller by tapping the corresponding icon. 38 | @note The method is called regardless of whether the selected view controller changed or not and only as a result of the user tapped a button. The method is not called when the view controller is changed programmatically. This is the same pattern as UITabBarController uses. 39 | */ 40 | - (void)containerViewController:(ContainerViewController *)containerViewController didSelectViewController:(UIViewController *)viewController; 41 | 42 | /// Called on the delegate to obtain a UIViewControllerAnimatedTransitioning object which can be used to animate a non-interactive transition. 43 | - (id )containerViewController:(ContainerViewController *)containerViewController animationControllerForTransitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController; 44 | @end 45 | -------------------------------------------------------------------------------- /Container Transitions/ContainerViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ContainerViewController.m 3 | // Container Transitions 4 | // 5 | // Created by Joachim Bondo on 30/04/2014. 6 | // 7 | 8 | #import "ContainerViewController.h" 9 | 10 | static CGFloat const kButtonSlotWidth = 64; // Also distance between button centers 11 | static CGFloat const kButtonSlotHeight = 44; 12 | 13 | /** A private UIViewControllerContextTransitioning class to be provided transitioning delegates. 14 | @discussion Because we are a custom UIVievController class, with our own containment implementation, we have to provide an object conforming to the UIViewControllerContextTransitioning protocol. The system view controllers use one provided by the framework, which we cannot configure, let alone create. This class will be used even if the developer provides their own transitioning objects. 15 | @note The only methods that will be called on objects of this class are the ones defined in the UIViewControllerContextTransitioning protocol. The rest is our own private implementation. 16 | */ 17 | @interface PrivateTransitionContext : NSObject 18 | - (instancetype)initWithFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController goingRight:(BOOL)goingRight; /// Designated initializer. 19 | @property (nonatomic, copy) void (^completionBlock)(BOOL didComplete); /// A block of code we can set to execute after having received the completeTransition: message. 20 | @property (nonatomic, assign, getter=isAnimated) BOOL animated; /// Private setter for the animated property. 21 | @property (nonatomic, assign, getter=isInteractive) BOOL interactive; /// Private setter for the interactive property. 22 | @end 23 | 24 | /** Instances of this private class perform the default transition animation which is to slide child views horizontally. 25 | @note The class only supports UIViewControllerAnimatedTransitioning at this point. Not UIViewControllerInteractiveTransitioning. 26 | */ 27 | @interface PrivateAnimatedTransition : NSObject 28 | @end 29 | 30 | #pragma mark - 31 | 32 | @interface ContainerViewController () 33 | @property (nonatomic, copy, readwrite) NSArray *viewControllers; 34 | @property (nonatomic, strong) UIView *privateButtonsView; /// The view hosting the buttons of the child view controllers. 35 | @property (nonatomic, strong) UIView *privateContainerView; /// The view hosting the child view controllers views. 36 | @end 37 | 38 | @implementation ContainerViewController 39 | 40 | - (instancetype)initWithViewControllers:(NSArray *)viewControllers { 41 | NSParameterAssert ([viewControllers count] > 0); 42 | if ((self = [super init])) { 43 | self.viewControllers = [viewControllers copy]; 44 | } 45 | return self; 46 | } 47 | 48 | - (void)loadView { 49 | 50 | // Add container and buttons views. 51 | 52 | UIView *rootView = [[UIView alloc] init]; 53 | rootView.backgroundColor = [UIColor blackColor]; 54 | rootView.opaque = YES; 55 | 56 | self.privateContainerView = [[UIView alloc] init]; 57 | self.privateContainerView.backgroundColor = [UIColor blackColor]; 58 | self.privateContainerView.opaque = YES; 59 | 60 | self.privateButtonsView = [[UIView alloc] init]; 61 | self.privateButtonsView.backgroundColor = [UIColor clearColor]; 62 | self.privateButtonsView.tintColor = [UIColor colorWithWhite:1 alpha:0.75f]; 63 | 64 | [self.privateContainerView setTranslatesAutoresizingMaskIntoConstraints:NO]; 65 | [self.privateButtonsView setTranslatesAutoresizingMaskIntoConstraints:NO]; 66 | [rootView addSubview:self.privateContainerView]; 67 | [rootView addSubview:self.privateButtonsView]; 68 | 69 | // Container view fills out entire root view. 70 | [rootView addConstraint:[NSLayoutConstraint constraintWithItem:self.privateContainerView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:rootView attribute:NSLayoutAttributeWidth multiplier:1 constant:0]]; 71 | [rootView addConstraint:[NSLayoutConstraint constraintWithItem:self.privateContainerView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:rootView attribute:NSLayoutAttributeHeight multiplier:1 constant:0]]; 72 | [rootView addConstraint:[NSLayoutConstraint constraintWithItem:self.privateContainerView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:rootView attribute:NSLayoutAttributeLeft multiplier:1 constant:0]]; 73 | [rootView addConstraint:[NSLayoutConstraint constraintWithItem:self.privateContainerView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:rootView attribute:NSLayoutAttributeTop multiplier:1 constant:0]]; 74 | 75 | // Place buttons view in the top half, horizontally centered. 76 | [rootView addConstraint:[NSLayoutConstraint constraintWithItem:self.privateButtonsView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:[self.viewControllers count] * kButtonSlotWidth]]; 77 | [rootView addConstraint:[NSLayoutConstraint constraintWithItem:self.privateButtonsView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.privateContainerView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]]; 78 | [rootView addConstraint:[NSLayoutConstraint constraintWithItem:self.privateButtonsView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:kButtonSlotHeight]]; 79 | [rootView addConstraint:[NSLayoutConstraint constraintWithItem:self.privateButtonsView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.privateContainerView attribute:NSLayoutAttributeCenterY multiplier:0.4f constant:0]]; 80 | 81 | [self _addChildViewControllerButtons]; 82 | 83 | self.view = rootView; 84 | } 85 | 86 | - (void)viewDidLoad { 87 | [super viewDidLoad]; 88 | self.selectedViewController = (self.selectedViewController ?: self.viewControllers[0]); 89 | } 90 | 91 | - (UIStatusBarStyle)preferredStatusBarStyle { 92 | return UIStatusBarStyleLightContent; 93 | } 94 | 95 | - (UIViewController *)childViewControllerForStatusBarStyle { 96 | return self.selectedViewController; 97 | } 98 | 99 | -(void)setSelectedViewController:(UIViewController *)selectedViewController { 100 | NSParameterAssert (selectedViewController); 101 | [self _transitionToChildViewController:selectedViewController]; 102 | _selectedViewController = selectedViewController; 103 | [self _updateButtonSelection]; 104 | } 105 | 106 | #pragma mark Private Methods 107 | 108 | - (void)_addChildViewControllerButtons { 109 | 110 | [self.viewControllers enumerateObjectsUsingBlock:^(UIViewController *childViewController, NSUInteger idx, BOOL *stop) { 111 | 112 | UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 113 | UIImage *icon = [childViewController.tabBarItem.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 114 | [button setImage:icon forState:UIControlStateNormal]; 115 | UIImage *selectedIcon = [childViewController.tabBarItem.selectedImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 116 | [button setImage:selectedIcon forState:UIControlStateSelected]; 117 | 118 | button.tag = idx; 119 | [button addTarget:self action:@selector(_buttonTapped:) forControlEvents:UIControlEventTouchUpInside]; 120 | 121 | [self.privateButtonsView addSubview:button]; 122 | [button setTranslatesAutoresizingMaskIntoConstraints:NO]; 123 | 124 | [self.privateButtonsView addConstraint:[NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.privateButtonsView attribute:NSLayoutAttributeLeading multiplier:1 constant:(idx + 0.5f) * kButtonSlotWidth]]; 125 | [self.privateButtonsView addConstraint:[NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.privateButtonsView attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]]; 126 | }]; 127 | } 128 | 129 | - (void)_buttonTapped:(UIButton *)button { 130 | UIViewController *selectedViewController = self.viewControllers[button.tag]; 131 | self.selectedViewController = selectedViewController; 132 | 133 | if ([self.delegate respondsToSelector:@selector (containerViewController:didSelectViewController:)]) { 134 | [self.delegate containerViewController:self didSelectViewController:selectedViewController]; 135 | } 136 | } 137 | 138 | - (void)_updateButtonSelection { 139 | [self.privateButtonsView.subviews enumerateObjectsUsingBlock:^(UIButton *button, NSUInteger idx, BOOL *stop) { 140 | button.selected = (self.viewControllers[idx] == self.selectedViewController); 141 | }]; 142 | } 143 | 144 | - (void)_transitionToChildViewController:(UIViewController *)toViewController { 145 | 146 | UIViewController *fromViewController = ([self.childViewControllers count] > 0 ? self.childViewControllers[0] : nil); 147 | if (toViewController == fromViewController || ![self isViewLoaded]) { 148 | return; 149 | } 150 | 151 | UIView *toView = toViewController.view; 152 | [toView setTranslatesAutoresizingMaskIntoConstraints:YES]; 153 | toView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 154 | toView.frame = self.privateContainerView.bounds; 155 | 156 | [fromViewController willMoveToParentViewController:nil]; 157 | [self addChildViewController:toViewController]; 158 | 159 | // If this is the initial presentation, add the new child with no animation. 160 | if (!fromViewController) { 161 | [self.privateContainerView addSubview:toViewController.view]; 162 | [toViewController didMoveToParentViewController:self]; 163 | return; 164 | } 165 | 166 | // Animate the transition by calling the animator with our private transition context. If we don't have a delegate, or if it doesn't return an animated transitioning object, we will use our own, private animator. 167 | 168 | idanimator = nil; 169 | if ([self.delegate respondsToSelector:@selector (containerViewController:animationControllerForTransitionFromViewController:toViewController:)]) { 170 | animator = [self.delegate containerViewController:self animationControllerForTransitionFromViewController:fromViewController toViewController:toViewController]; 171 | } 172 | animator = (animator ?: [[PrivateAnimatedTransition alloc] init]); 173 | 174 | // Because of the nature of our view controller, with horizontally arranged buttons, we instantiate our private transition context with information about whether this is a left-to-right or right-to-left transition. The animator can use this information if it wants. 175 | NSUInteger fromIndex = [self.viewControllers indexOfObject:fromViewController]; 176 | NSUInteger toIndex = [self.viewControllers indexOfObject:toViewController]; 177 | PrivateTransitionContext *transitionContext = [[PrivateTransitionContext alloc] initWithFromViewController:fromViewController toViewController:toViewController goingRight:toIndex > fromIndex]; 178 | 179 | transitionContext.animated = YES; 180 | transitionContext.interactive = NO; 181 | transitionContext.completionBlock = ^(BOOL didComplete) { 182 | [fromViewController.view removeFromSuperview]; 183 | [fromViewController removeFromParentViewController]; 184 | [toViewController didMoveToParentViewController:self]; 185 | 186 | if ([animator respondsToSelector:@selector (animationEnded:)]) { 187 | [animator animationEnded:didComplete]; 188 | } 189 | self.privateButtonsView.userInteractionEnabled = YES; 190 | }; 191 | 192 | self.privateButtonsView.userInteractionEnabled = NO; // Prevent user tapping buttons mid-transition, messing up state 193 | [animator animateTransition:transitionContext]; 194 | } 195 | 196 | @end 197 | 198 | #pragma mark - Private Transitioning Classes 199 | 200 | @interface PrivateTransitionContext () 201 | @property (nonatomic, strong) NSDictionary *privateViewControllers; 202 | @property (nonatomic, assign) CGRect privateDisappearingFromRect; 203 | @property (nonatomic, assign) CGRect privateAppearingFromRect; 204 | @property (nonatomic, assign) CGRect privateDisappearingToRect; 205 | @property (nonatomic, assign) CGRect privateAppearingToRect; 206 | @property (nonatomic, weak) UIView *containerView; 207 | @property (nonatomic, assign) UIModalPresentationStyle presentationStyle; 208 | @end 209 | 210 | @implementation PrivateTransitionContext 211 | 212 | - (instancetype)initWithFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController goingRight:(BOOL)goingRight { 213 | NSAssert ([fromViewController isViewLoaded] && fromViewController.view.superview, @"The fromViewController view must reside in the container view upon initializing the transition context."); 214 | 215 | if ((self = [super init])) { 216 | self.presentationStyle = UIModalPresentationCustom; 217 | self.containerView = fromViewController.view.superview; 218 | self.privateViewControllers = @{ 219 | UITransitionContextFromViewControllerKey:fromViewController, 220 | UITransitionContextToViewControllerKey:toViewController, 221 | }; 222 | 223 | // Set the view frame properties which make sense in our specialized ContainerViewController context. Views appear from and disappear to the sides, corresponding to where the icon buttons are positioned. So tapping a button to the right of the currently selected, makes the view disappear to the left and the new view appear from the right. The animator object can choose to use this to determine whether the transition should be going left to right, or right to left, for example. 224 | CGFloat travelDistance = (goingRight ? -self.containerView.bounds.size.width : self.containerView.bounds.size.width); 225 | self.privateDisappearingFromRect = self.privateAppearingToRect = self.containerView.bounds; 226 | self.privateDisappearingToRect = CGRectOffset (self.containerView.bounds, travelDistance, 0); 227 | self.privateAppearingFromRect = CGRectOffset (self.containerView.bounds, -travelDistance, 0); 228 | } 229 | 230 | return self; 231 | } 232 | 233 | - (CGRect)initialFrameForViewController:(UIViewController *)viewController { 234 | if (viewController == [self viewControllerForKey:UITransitionContextFromViewControllerKey]) { 235 | return self.privateDisappearingFromRect; 236 | } else { 237 | return self.privateAppearingFromRect; 238 | } 239 | } 240 | 241 | - (CGRect)finalFrameForViewController:(UIViewController *)viewController { 242 | if (viewController == [self viewControllerForKey:UITransitionContextFromViewControllerKey]) { 243 | return self.privateDisappearingToRect; 244 | } else { 245 | return self.privateAppearingToRect; 246 | } 247 | } 248 | 249 | - (UIViewController *)viewControllerForKey:(NSString *)key { 250 | return self.privateViewControllers[key]; 251 | } 252 | 253 | - (void)completeTransition:(BOOL)didComplete { 254 | if (self.completionBlock) { 255 | self.completionBlock (didComplete); 256 | } 257 | } 258 | 259 | - (BOOL)transitionWasCancelled { return NO; } // Our non-interactive transition can't be cancelled (it could be interrupted, though) 260 | 261 | // Supress warnings by implementing empty interaction methods for the remainder of the protocol: 262 | 263 | - (void)updateInteractiveTransition:(CGFloat)percentComplete {} 264 | - (void)finishInteractiveTransition {} 265 | - (void)cancelInteractiveTransition {} 266 | 267 | @end 268 | 269 | @implementation PrivateAnimatedTransition 270 | 271 | static CGFloat const kChildViewPadding = 16; 272 | static CGFloat const kDamping = 0.75; 273 | static CGFloat const kInitialSpringVelocity = 0.5; 274 | 275 | - (NSTimeInterval)transitionDuration:(id)transitionContext { 276 | return 1; 277 | } 278 | 279 | /// Slide views horizontally, with a bit of space between, while fading out and in. 280 | - (void)animateTransition:(id)transitionContext { 281 | 282 | UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; 283 | UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; 284 | 285 | // When sliding the views horizontally in and out, figure out whether we are going left or right. 286 | BOOL goingRight = ([transitionContext initialFrameForViewController:toViewController].origin.x < [transitionContext finalFrameForViewController:toViewController].origin.x); 287 | CGFloat travelDistance = [transitionContext containerView].bounds.size.width + kChildViewPadding; 288 | CGAffineTransform travel = CGAffineTransformMakeTranslation (goingRight ? travelDistance : -travelDistance, 0); 289 | 290 | [[transitionContext containerView] addSubview:toViewController.view]; 291 | toViewController.view.alpha = 0; 292 | toViewController.view.transform = CGAffineTransformInvert (travel); 293 | 294 | [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 usingSpringWithDamping:kDamping initialSpringVelocity:kInitialSpringVelocity options:0x00 animations:^{ 295 | fromViewController.view.transform = travel; 296 | fromViewController.view.alpha = 0; 297 | toViewController.view.transform = CGAffineTransformIdentity; 298 | toViewController.view.alpha = 1; 299 | } completion:^(BOOL finished) { 300 | fromViewController.view.transform = CGAffineTransformIdentity; 301 | [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; 302 | }]; 303 | } 304 | 305 | @end 306 | -------------------------------------------------------------------------------- /Container Transitions/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "ipad", 20 | "size" : "29x29", 21 | "scale" : "1x" 22 | }, 23 | { 24 | "idiom" : "ipad", 25 | "size" : "29x29", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "ipad", 30 | "size" : "40x40", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "40x40", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "76x76", 41 | "scale" : "1x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "76x76", 46 | "scale" : "2x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /Container Transitions/Images.xcassets/First Selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "First Selected.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "First Selected@2x.png" 12 | } 13 | ], 14 | "info" : { 15 | "version" : 1, 16 | "author" : "xcode" 17 | } 18 | } -------------------------------------------------------------------------------- /Container Transitions/Images.xcassets/First Selected.imageset/First Selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osteslag/custom-container-transitions/fcf9dd6953ff62d5abbb593e421785caa6c48f17/Container Transitions/Images.xcassets/First Selected.imageset/First Selected.png -------------------------------------------------------------------------------- /Container Transitions/Images.xcassets/First Selected.imageset/First Selected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osteslag/custom-container-transitions/fcf9dd6953ff62d5abbb593e421785caa6c48f17/Container Transitions/Images.xcassets/First Selected.imageset/First Selected@2x.png -------------------------------------------------------------------------------- /Container Transitions/Images.xcassets/First.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "First.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "First@2x.png" 12 | } 13 | ], 14 | "info" : { 15 | "version" : 1, 16 | "author" : "xcode" 17 | } 18 | } -------------------------------------------------------------------------------- /Container Transitions/Images.xcassets/First.imageset/First.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osteslag/custom-container-transitions/fcf9dd6953ff62d5abbb593e421785caa6c48f17/Container Transitions/Images.xcassets/First.imageset/First.png -------------------------------------------------------------------------------- /Container Transitions/Images.xcassets/First.imageset/First@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osteslag/custom-container-transitions/fcf9dd6953ff62d5abbb593e421785caa6c48f17/Container Transitions/Images.xcassets/First.imageset/First@2x.png -------------------------------------------------------------------------------- /Container Transitions/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "orientation" : "portrait", 20 | "idiom" : "ipad", 21 | "extent" : "full-screen", 22 | "minimum-system-version" : "7.0", 23 | "scale" : "1x" 24 | }, 25 | { 26 | "orientation" : "landscape", 27 | "idiom" : "ipad", 28 | "extent" : "full-screen", 29 | "minimum-system-version" : "7.0", 30 | "scale" : "1x" 31 | }, 32 | { 33 | "orientation" : "portrait", 34 | "idiom" : "ipad", 35 | "extent" : "full-screen", 36 | "minimum-system-version" : "7.0", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "orientation" : "landscape", 41 | "idiom" : "ipad", 42 | "extent" : "full-screen", 43 | "minimum-system-version" : "7.0", 44 | "scale" : "2x" 45 | } 46 | ], 47 | "info" : { 48 | "version" : 1, 49 | "author" : "xcode" 50 | } 51 | } -------------------------------------------------------------------------------- /Container Transitions/Images.xcassets/Second Selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Second Selected.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "Second Selected@2x.png" 12 | } 13 | ], 14 | "info" : { 15 | "version" : 1, 16 | "author" : "xcode" 17 | } 18 | } -------------------------------------------------------------------------------- /Container Transitions/Images.xcassets/Second Selected.imageset/Second Selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osteslag/custom-container-transitions/fcf9dd6953ff62d5abbb593e421785caa6c48f17/Container Transitions/Images.xcassets/Second Selected.imageset/Second Selected.png -------------------------------------------------------------------------------- /Container Transitions/Images.xcassets/Second Selected.imageset/Second Selected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osteslag/custom-container-transitions/fcf9dd6953ff62d5abbb593e421785caa6c48f17/Container Transitions/Images.xcassets/Second Selected.imageset/Second Selected@2x.png -------------------------------------------------------------------------------- /Container Transitions/Images.xcassets/Second.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Second.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "Second@2x.png" 12 | } 13 | ], 14 | "info" : { 15 | "version" : 1, 16 | "author" : "xcode" 17 | } 18 | } -------------------------------------------------------------------------------- /Container Transitions/Images.xcassets/Second.imageset/Second.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osteslag/custom-container-transitions/fcf9dd6953ff62d5abbb593e421785caa6c48f17/Container Transitions/Images.xcassets/Second.imageset/Second.png -------------------------------------------------------------------------------- /Container Transitions/Images.xcassets/Second.imageset/Second@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osteslag/custom-container-transitions/fcf9dd6953ff62d5abbb593e421785caa6c48f17/Container Transitions/Images.xcassets/Second.imageset/Second@2x.png -------------------------------------------------------------------------------- /Container Transitions/Images.xcassets/Third Selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Third Selected.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "Third Selected@2x.png" 12 | } 13 | ], 14 | "info" : { 15 | "version" : 1, 16 | "author" : "xcode" 17 | } 18 | } -------------------------------------------------------------------------------- /Container Transitions/Images.xcassets/Third Selected.imageset/Third Selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osteslag/custom-container-transitions/fcf9dd6953ff62d5abbb593e421785caa6c48f17/Container Transitions/Images.xcassets/Third Selected.imageset/Third Selected.png -------------------------------------------------------------------------------- /Container Transitions/Images.xcassets/Third Selected.imageset/Third Selected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osteslag/custom-container-transitions/fcf9dd6953ff62d5abbb593e421785caa6c48f17/Container Transitions/Images.xcassets/Third Selected.imageset/Third Selected@2x.png -------------------------------------------------------------------------------- /Container Transitions/Images.xcassets/Third.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Third.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "Third@2x.png" 12 | } 13 | ], 14 | "info" : { 15 | "version" : 1, 16 | "author" : "xcode" 17 | } 18 | } -------------------------------------------------------------------------------- /Container Transitions/Images.xcassets/Third.imageset/Third.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osteslag/custom-container-transitions/fcf9dd6953ff62d5abbb593e421785caa6c48f17/Container Transitions/Images.xcassets/Third.imageset/Third.png -------------------------------------------------------------------------------- /Container Transitions/Images.xcassets/Third.imageset/Third@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osteslag/custom-container-transitions/fcf9dd6953ff62d5abbb593e421785caa6c48f17/Container Transitions/Images.xcassets/Third.imageset/Third@2x.png -------------------------------------------------------------------------------- /Container Transitions/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | net.bondo.container-transitions 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Container Transitions/Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_3_0 10 | #warning "This project uses features only available in iOS SDK 3.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /Container Transitions/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Container Transitions 4 | // 5 | // Created by Joachim Bondo on 30/04/2014. 6 | // 7 | 8 | #import 9 | #import "AppDelegate.h" 10 | 11 | int main (int argc, char * argv[]) { 12 | @autoreleasepool { 13 | return UIApplicationMain (argc, argv, nil, NSStringFromClass ([AppDelegate class])); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | This is the code repository accompanying the [Custom Container View Controller Transitions](http://www.objc.io/issue-12/custom-container-view-controller-transitions.html) article, [issue #12](http://www.objc.io/issue-12) of the [objc.io publication](http://www.objc.io). 4 | 5 | In three steps a custom container view controller is built with support for custom child view controller transition animations: 6 | 7 | 1. The Basics: implementing `ChildViewController` with no transition animation ([stage-1](https://github.com/osteslag/custom-container-transitions/tree/stage-1)) 8 | 9 | 2. Animating the Transition: using an existing animation controller ([stage-2](https://github.com/osteslag/custom-container-transitions/tree/stage-2), [diff](https://github.com/osteslag/custom-container-transitions/compare/stage-1...stage-2)) 10 | 11 | 3. Shrink-Wrapping: implement delegate pattern, external `UIViewControllerAnimatedTransitioning` vending ([stage-3](https://github.com/osteslag/custom-container-transitions/tree/stage-3), [diff](https://github.com/osteslag/custom-container-transitions/compare/stage-2...stage-3)) 12 | 13 | ![Stage 3](stage-3.gif) 14 | 15 | Read more on [objc.io](http://www.objc.io/issue-12/custom-container-view-controller-transitions.html). 16 | -------------------------------------------------------------------------------- /stage-3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/osteslag/custom-container-transitions/fcf9dd6953ff62d5abbb593e421785caa6c48f17/stage-3.gif --------------------------------------------------------------------------------