├── 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 | 
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
--------------------------------------------------------------------------------