├── .DS_Store
├── Custom Transitions
├── Assets.xcassets
│ ├── Contents.json
│ ├── .DS_Store
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── .DS_Store
├── Info.plist
├── AppDelegate.swift
├── Base.lproj
│ ├── Main.storyboard
│ └── LaunchScreen.storyboard
├── SceneDelegate.swift
├── Logistical - No code samples here
│ ├── ControllerSetup.swift
│ └── PresentationScenario.swift
├── TransitionDelegate.swift
├── ViewController.swift
├── TransitionAnimator.swift
└── TransitionPresentationController.swift
├── Custom Transitions.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── project.pbxproj
├── LICENSE
├── README.md
└── .gitignore
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DreamingInBinary/Custom-ViewController-Transitions/HEAD/.DS_Store
--------------------------------------------------------------------------------
/Custom Transitions/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Custom Transitions/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DreamingInBinary/Custom-ViewController-Transitions/HEAD/Custom Transitions/.DS_Store
--------------------------------------------------------------------------------
/Custom Transitions/Assets.xcassets/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DreamingInBinary/Custom-ViewController-Transitions/HEAD/Custom Transitions/Assets.xcassets/.DS_Store
--------------------------------------------------------------------------------
/Custom Transitions.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Custom Transitions/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Custom Transitions.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Custom Transitions/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | UIApplicationSceneManifest
6 |
7 | UIApplicationSupportsMultipleScenes
8 |
9 | UISceneConfigurations
10 |
11 | UIWindowSceneSessionRoleApplication
12 |
13 |
14 | UISceneConfigurationName
15 | Default Configuration
16 | UISceneDelegateClassName
17 | $(PRODUCT_MODULE_NAME).SceneDelegate
18 | UISceneStoryboardFile
19 | Main
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Jordan Morgan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Custom Transitions/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Custom Transitions
4 | //
5 | // Created by Jordan Morgan on 9/30/21.
6 | //
7 |
8 | import UIKit
9 |
10 | @main
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 |
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 | // Override point for customization after application launch.
17 | return true
18 | }
19 |
20 | // MARK: UISceneSession Lifecycle
21 |
22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
23 | // Called when a new scene session is being created.
24 | // Use this method to select a configuration to create the new scene with.
25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
26 | }
27 |
28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
29 | // Called when the user discards a scene session.
30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
32 | }
33 |
34 |
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/Custom Transitions/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Custom Transitions/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Custom Transitions/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Custom View Controller Transitions
2 | This project explains and shows how to make custom view controller transitions in the most simple way possible. Each example builds off of each other.
3 |
4 | ## Objects you make
5 |
6 | 1. Transitioning delegate (Required)
7 | An object whose sole job is to vend either custom animators, presentation
8 | controllers, interactive animators or all of those to UIKit. Setting a custom transitioning delegate for a controllers'
9 | transitioningDelegate property is what tells UIKit you want to perform a
10 | custom controller transition.
11 |
12 | 2. Animator _or_ both an Animator and Interactive Animator (Optional)
13 | This decides the duration of the custom transition and performs the actual
14 | animations of the presented view during both presentation and dismissal. It does
15 | *not* decide the final frame of the presented view. Presentation controllers do.
16 | If it an interactive animator is created, it's only job is to calculate how much
17 | of the transition has been completed.
18 |
19 | 3. Presentation Controller (Optional)
20 | This manages chrome outside of the presented or presenting
21 | view controllers and can animate those, such as a dimmer view. It also decides
22 | the final size of the presented view controller's view. It also can respond
23 | to changes occur in the app’s environment.
24 |
25 | ## Objects UIKit makes
26 |
27 | 1. Tranistion Context
28 | Contains all key components of the transition, like to the to and from view controllers.
29 |
30 | 2. Transition Coordinator
31 | Used to hook into transitions and their animations to perform any other animations or
32 | changes on existing controllers. For example, if you wanted to animate the deselection
33 | of a table row in a root view controller on a navigation stack, you'd hook into the controller's
34 | transition coordinator to animate alongside a popped controller's presentation and dismissal.
35 | Much the same way, a presentation controller animates chrome using the presented controller's
36 | transition coordinator too.
37 |
38 | ## Performing the Custom Transition
39 |
40 | 1. Initialize the view controller to be presented.
41 | 2. Assign your transitioning delegate to the controller’s transitioningDelegate property. Using it, return either
42 | 2a) Custom animator or interactive animator.
43 | 2b) A custom presentation controller. Requires that the controller's modal presentation style is set to custom.
44 | 2c) Or both.
45 | 3. Call presentViewController:animated:completion.
46 |
--------------------------------------------------------------------------------
/Custom Transitions/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // Custom Transitions
4 | //
5 | // Created by Jordan Morgan on 9/30/21.
6 | //
7 |
8 | import UIKit
9 |
10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
11 |
12 | var window: UIWindow?
13 |
14 |
15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
19 | guard let _ = (scene as? UIWindowScene) else { return }
20 | }
21 |
22 | func sceneDidDisconnect(_ scene: UIScene) {
23 | // Called as the scene is being released by the system.
24 | // This occurs shortly after the scene enters the background, or when its session is discarded.
25 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
26 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
27 | }
28 |
29 | func sceneDidBecomeActive(_ scene: UIScene) {
30 | // Called when the scene has moved from an inactive state to an active state.
31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
32 | }
33 |
34 | func sceneWillResignActive(_ scene: UIScene) {
35 | // Called when the scene will move from an active state to an inactive state.
36 | // This may occur due to temporary interruptions (ex. an incoming phone call).
37 | }
38 |
39 | func sceneWillEnterForeground(_ scene: UIScene) {
40 | // Called as the scene transitions from the background to the foreground.
41 | // Use this method to undo the changes made on entering the background.
42 | }
43 |
44 | func sceneDidEnterBackground(_ scene: UIScene) {
45 | // Called as the scene transitions from the foreground to the background.
46 | // Use this method to save data, release shared resources, and store enough scene-specific state information
47 | // to restore the scene back to its current state.
48 | }
49 |
50 |
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 |
28 | ## App packaging
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | ## Playgrounds
34 | timeline.xctimeline
35 | playground.xcworkspace
36 |
37 | # Swift Package Manager
38 | #
39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40 | # Packages/
41 | # Package.pins
42 | # Package.resolved
43 | # *.xcodeproj
44 | #
45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46 | # hence it is not needed unless you have added a package configuration file to your project
47 | # .swiftpm
48 |
49 | .build/
50 |
51 | # CocoaPods
52 | #
53 | # We recommend against adding the Pods directory to your .gitignore. However
54 | # you should judge for yourself, the pros and cons are mentioned at:
55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
56 | #
57 | # Pods/
58 | #
59 | # Add this line if you want to avoid checking in source code from the Xcode workspace
60 | # *.xcworkspace
61 |
62 | # Carthage
63 | #
64 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
65 | # Carthage/Checkouts
66 |
67 | Carthage/Build/
68 |
69 | # Accio dependency management
70 | Dependencies/
71 | .accio/
72 |
73 | # fastlane
74 | #
75 | # It is recommended to not store the screenshots in the git repo.
76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
77 | # For more information about the recommended setup visit:
78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
79 |
80 | fastlane/report.xml
81 | fastlane/Preview.html
82 | fastlane/screenshots/**/*.png
83 | fastlane/test_output
84 |
85 | # Code Injection
86 | #
87 | # After new code Injection tools there's a generated folder /iOSInjectionProject
88 | # https://github.com/johnno1962/injectionforxcode
89 |
90 | iOSInjectionProject/
91 |
--------------------------------------------------------------------------------
/Custom Transitions/Logistical - No code samples here/ControllerSetup.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ControllerSetup.swift
3 | // Custom Transitions
4 | //
5 | // Created by Jordan Morgan on 10/1/21.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | typealias TableDataSource = UITableViewDiffableDataSource
12 |
13 | class Source: TableDataSource {
14 | override func tableView(_ tableView: UITableView, titleForFooterInSection
15 | section: Int) -> String? {
16 | return PresentationScenario.all[section].info
17 | }
18 | }
19 |
20 | extension ViewController {
21 | func createDemoController() -> UIViewController {
22 | let demoVC = UIViewController()
23 | let demoView = demoVC.view!
24 | demoView.backgroundColor = .blue
25 |
26 | let dismiss = UIButton(configuration: .borderedProminent(), primaryAction: UIAction() { _ in
27 | demoVC.dismiss(animated: true)
28 | })
29 | dismiss.setTitle("Dismiss", for: .normal)
30 | demoView.addSubview(dismiss)
31 | dismiss.frame.size = CGSize(width: 100, height: 54)
32 | dismiss.frame.origin = CGPoint(x: demoView.bounds.size.width/2 - 50,
33 | y: demoView.bounds.size.height/2 - 27)
34 |
35 |
36 | return demoVC
37 | }
38 |
39 | func setupViews() {
40 | tv.frame = view.bounds
41 | tv.delegate = self
42 | view.addSubview(tv)
43 | tv.autoresizingMask = [.flexibleWidth, .flexibleHeight]
44 | tv.register(UITableViewCell.classForCoder(), forCellReuseIdentifier: "cell")
45 |
46 | let allItems = PresentationScenario.all
47 |
48 | var snapshot = datasource.snapshot()
49 | snapshot.appendSections(allItems.map{ return allItems.firstIndex(of: $0)! })
50 | for (idx, val) in allItems.enumerated() {
51 | snapshot.appendItems([val], toSection: idx)
52 | }
53 |
54 | datasource.apply(snapshot)
55 | }
56 | }
57 |
58 | extension ViewController: UITableViewDelegate {
59 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
60 | switch indexPath.section {
61 | case 0:
62 | self.hookIntoExistingCoordinator()
63 | case 1:
64 | self.onlyUseCustomAnimator()
65 | case 2:
66 | self.onlyPresentationController()
67 | case 3:
68 | self.animatorAndPresentationController()
69 | case 4:
70 | self.interactiveAnimatorAndPresentationController()
71 | default:
72 | return
73 | }
74 | tableView.deselectRow(at: indexPath, animated: true)
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/Custom Transitions/TransitionDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TransitionDelegate.swift
3 | // Custom Transitions
4 | //
5 | // Created by Jordan Morgan on 9/30/21.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | enum UseCase {
12 | case justAnimator
13 | case justPresentationController
14 | case animatorAndPresentationController
15 | case interactiveAnimatorAndPresentationController
16 | }
17 |
18 | class TransitionDelegate: NSObject, UIViewControllerTransitioningDelegate {
19 | var demoedUseCase: UseCase = .justAnimator
20 | let interactiveAnimator: UIPercentDrivenInteractiveTransition = UIPercentDrivenInteractiveTransition()
21 |
22 | // MARK: Animators. Sometimes called animation controllers.
23 |
24 | func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
25 | return createAnimator(isPresenting: true)
26 | }
27 |
28 | func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
29 | return createAnimator(isPresenting: false)
30 | }
31 |
32 | // MARK: Interactive animators. Sometimes called interactive animation controllers.
33 |
34 | func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
35 | guard demoedUseCase == .interactiveAnimatorAndPresentationController else { return nil }
36 | return interactiveAnimator
37 | }
38 |
39 | // MARK: Presentation Controllers
40 |
41 | func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
42 | switch demoedUseCase {
43 | case .justAnimator:
44 | return nil
45 | case .justPresentationController:
46 | return TransitionPresentationController(presentedViewController: presented, presenting: presenting)
47 | case .animatorAndPresentationController:
48 | return TransitionPresentationController(presentedViewController: presented, presenting: presenting)
49 | case .interactiveAnimatorAndPresentationController:
50 | let presentationController = TransitionPresentationController(presentedViewController: presented, presenting: presenting)
51 | presentationController.transitioningDelegateWantsInteractiveDismissal = true
52 |
53 | return presentationController
54 | }
55 | }
56 |
57 | // MARK: Private
58 |
59 | private func createAnimator(isPresenting:Bool) -> UIViewControllerAnimatedTransitioning? {
60 | let animator = TransitionAnimator()
61 | animator.isPresenting = isPresenting
62 |
63 | switch demoedUseCase {
64 | case .justAnimator, .animatorAndPresentationController:
65 | return animator
66 | case .justPresentationController:
67 | return nil
68 | case .interactiveAnimatorAndPresentationController:
69 | return animator
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/Custom Transitions/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Custom Transitions
4 | //
5 | // Created by Jordan Morgan on 9/30/21.
6 | //
7 |
8 | import UIKit
9 |
10 | class ViewController: UIViewController {
11 | let customTransitionDelegate: TransitionDelegate = TransitionDelegate()
12 | let tv = UITableView(frame: .zero, style: .insetGrouped)
13 | lazy var datasource = Source(tableView: tv, cellProvider: { (tableView, indexPath, model) -> UITableViewCell? in
14 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
15 | cell.textLabel?.text = model.name
16 | return cell
17 | })
18 |
19 | override func viewDidLoad() {
20 | super.viewDidLoad()
21 | view.backgroundColor = .systemBackground
22 | setupViews()
23 | }
24 |
25 | // MARK: Custom animation options
26 |
27 | func hookIntoExistingCoordinator() {
28 | // This doesn't use any custom transition. It simply hooks into the
29 | // Ones already occuring.
30 | let vc = createDemoController()
31 | present(vc, animated: true, completion: nil)
32 |
33 | transitionCoordinator?.animate(alongsideTransition: { context in
34 | self.tv.backgroundColor = .red
35 | }, completion: { context in
36 | if context.isCancelled {
37 | self.tv.backgroundColor = .systemGroupedBackground
38 | } else {
39 | UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseOut) {
40 | self.tv.backgroundColor = .systemGroupedBackground
41 | } completion: { finished in
42 |
43 | }
44 | }
45 | })
46 | }
47 |
48 | func onlyUseCustomAnimator() {
49 | customTransitionDelegate.demoedUseCase = .justAnimator
50 |
51 | let vc = createDemoController()
52 | vc.transitioningDelegate = customTransitionDelegate
53 | vc.modalPresentationStyle = .overFullScreen
54 | present(vc, animated: true, completion: nil)
55 | }
56 |
57 | func onlyPresentationController() {
58 | customTransitionDelegate.demoedUseCase = .justPresentationController
59 |
60 | let vc = createDemoController()
61 | vc.transitioningDelegate = customTransitionDelegate
62 | vc.modalPresentationStyle = .custom
63 | present(vc, animated: true, completion: nil)
64 | }
65 |
66 | func animatorAndPresentationController() {
67 | customTransitionDelegate.demoedUseCase = .animatorAndPresentationController
68 |
69 | let vc = createDemoController()
70 | vc.transitioningDelegate = customTransitionDelegate
71 | vc.modalPresentationStyle = .custom
72 | present(vc, animated: true, completion: nil)
73 | }
74 |
75 | func interactiveAnimatorAndPresentationController() {
76 | customTransitionDelegate.demoedUseCase = .interactiveAnimatorAndPresentationController
77 |
78 | let vc = createDemoController()
79 | vc.transitioningDelegate = customTransitionDelegate
80 | vc.modalPresentationStyle = .custom
81 | vc.view.subviews.forEach { $0.removeFromSuperview() }
82 | present(vc, animated: true, completion: nil)
83 | }
84 | }
85 |
86 |
--------------------------------------------------------------------------------
/Custom Transitions/TransitionAnimator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TransitionAnimator.swift
3 | // Custom Transitions
4 | //
5 | // Created by Jordan Morgan on 9/30/21.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | class TransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
12 | var transitionDuration: TimeInterval = 1.0
13 | var isPresenting: Bool = true
14 |
15 | func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
16 | return transitionDuration
17 | }
18 |
19 | func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
20 | // Views and controllers
21 | let containerView = transitionContext.containerView
22 | let fromVC = transitionContext.viewController(forKey: .from)
23 | let toVC = transitionContext.viewController(forKey: .to)
24 | let fromView = transitionContext.view(forKey: .from)
25 | let toView = transitionContext.view(forKey: .to)
26 |
27 | // Rects
28 | let containerFrame: CGRect = containerView.frame
29 | var toViewBeginningFrame: CGRect = .zero
30 | var toViewEndingFrame: CGRect = .zero
31 | var fromViewEndingFrame: CGRect = .zero
32 |
33 | if let destinationView = toView, let presentedVC = toVC, let presentingVC = fromVC {
34 | containerView.addSubview(destinationView)
35 | toViewBeginningFrame = transitionContext.initialFrame(for: presentedVC)
36 | toViewEndingFrame = transitionContext.finalFrame(for: presentedVC)
37 | fromViewEndingFrame = transitionContext.finalFrame(for: presentingVC)
38 | }
39 |
40 | if isPresenting {
41 | toViewBeginningFrame.origin = CGPoint(x: containerFrame.size.width, y: containerFrame.size.width)
42 | toViewBeginningFrame.size = toViewEndingFrame.size
43 | } else {
44 | fromViewEndingFrame = CGRect(x:containerFrame.size.width,
45 | y:containerFrame.size.height,
46 | width:toView?.frame.size.width ?? 0,
47 | height:toView?.frame.size.height ?? 0);
48 | }
49 |
50 | toView?.frame = toViewBeginningFrame
51 |
52 | // For interactive animations, you might want to avoid nonlinear effects in the animations themselves. These usually decouple the touch location of events.
53 | UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.9, options: .curveEaseOut) {
54 | if self.isPresenting {
55 | toView?.frame = toViewEndingFrame
56 | fromVC?.view.frame = containerFrame
57 | } else {
58 | fromView?.frame = fromViewEndingFrame
59 | }
60 | } completion: { done in
61 | let succeeded = !transitionContext.transitionWasCancelled
62 | let failedPresenting = (self.isPresenting && !succeeded)
63 | let didDismiss = (!self.isPresenting && succeeded)
64 |
65 | if (failedPresenting || didDismiss) {
66 | toView?.removeFromSuperview()
67 | }
68 |
69 | transitionContext.completeTransition(succeeded)
70 | }
71 |
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Custom Transitions/Logistical - No code samples here/PresentationScenario.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PresentationScenario.swift
3 | // Custom Transitions
4 | //
5 | // Created by Jordan Morgan on 10/1/21.
6 | //
7 |
8 | import Foundation
9 |
10 | // Future examples: UINavigationController, subclassed UIPercentDrivenInteractiveTransition and UIViewPropertyAnimator
11 | struct PresentationScenario: Hashable {
12 | let id = UUID()
13 | let name: String
14 | let info: String
15 | static let all: [PresentationScenario] = [PresentationScenario(name: "Just Coordinator", info: "This hooks into this controller's UIViewControllerTransitionCoordinator to do some animation alongside another controller's presentation. In this case, it'll animate the background turning red as the new controller presents.\n\nTL;DR This lets you do arbitrary animations in lockstep with another presentation or dismissal."),
16 | PresentationScenario(name: "Just Animator", info: "Here, we have our transitioning delegate vend only an animator - which conforms to UIViewControllerAnimatedTransitioning. With it, we get what the final frame should be of the presented view - but are free to animate it to that CGRect any way we want. Also, we can set a starting rect. Here, we make it animate from the bottom right, up to center. It dismisses the same way.\n\nTL;DR This decides how to animate the presented view to its final CGRect."),
17 | PresentationScenario(name: "Just Presentation Controller", info: "In this case, a custom UIPresentationController is used. A presentation controller is typically used for two things (though it can do more); A) Animating \"chrome\" outside of the two controllers such as a dimming view and B) Calculating the final frame of the presented controller's view. So here, our transitioning delegate only vends a presentation controller which then tells UIKit that the presented controller's frame should be half of the width of the container view, positioned on the right half of the screen. It also animates a dimming view behind it all.\n\nTL;DR This can size the presented view, but doesn't control its animations, but it *can* animate chrome outside of the presenting or presented controller."),
18 | PresentationScenario(name: "Animator and Presentation Controller", info: "This is the most common use of the API. Here, the transitioning delegate returns both a custom animator object and a presentation controller. This means we can fully control every aspect of the transition. The presentation controller will tell the animator where to put the presented view, the animator will be in charge of animating it there and the presentation controller will also manage a dimming view (i.e. extra \"chrome\").\n\nTL;DR This is the most common way to do custom transitions. Create a custom transitioning delegate which tells UIKit which animator and presentation controller to use."),
19 | PresentationScenario(name: "Add Interactive Animator", info: "This is the same exact scenario as above, save for the fact that we can now use a custom UIGestureRecognizer to drive the completion percentage of the animation via UIViewControllerInteractiveTransitioning. This protocol is actually what makes a stock UINavigationController pop and push transition interactive. \n\nThis custom transition works the same way as above, except now using a UIPercentDrivenInteractiveTransition (which implements the aforementioned protocol for us) object vended in the transitioning delegate, we set up the event handling code needed to determine a completion percentage for the transition and update it accordingly. To use this, swipe down on the dimming view after presentation to kick off an interactive dismissmal.\n\nTL;DR This uses all the objects we've made so far, but adds one more object to calculate the completion percentage of a dismissal from a pan gesture, which drives the dismissal itself.")]
20 | }
21 |
--------------------------------------------------------------------------------
/Custom Transitions/TransitionPresentationController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TransitionPresentationController.swift
3 | // Custom Transitions
4 | //
5 | // Created by Jordan Morgan on 9/30/21.
6 | //
7 |
8 | import UIKit
9 |
10 | class TransitionPresentationController: UIPresentationController {
11 | var transitioningDelegateWantsInteractiveDismissal: Bool = false
12 | private var dimmingView: UIView = UIView(frame: .zero)
13 | private var panGesture: UIPanGestureRecognizer? = nil
14 |
15 | override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
16 | super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
17 | dimmingView.backgroundColor = UIColor(white: 0.0, alpha: 0.3)
18 | dimmingView.alpha = 0.0
19 | }
20 |
21 | // MARK: Chrome Animations
22 |
23 | override func presentationTransitionWillBegin() {
24 | guard let container = containerView else { return }
25 |
26 | dimmingView.frame = container.bounds
27 | dimmingView.alpha = 0.0
28 | container.insertSubview(dimmingView, at: 0)
29 |
30 | presentedViewController.transitionCoordinator?.animate(alongsideTransition: { [weak self] context in
31 | self?.dimmingView.alpha = 1.0
32 | }, completion: { [weak self] context in
33 | if context.isCancelled {
34 | self?.dimmingView.alpha = 0.0
35 | }
36 | })
37 | }
38 |
39 | override func presentationTransitionDidEnd(_ completed: Bool) {
40 | guard let container = containerView,
41 | transitioningDelegateWantsInteractiveDismissal == true,
42 | let _ = presentedViewController.transitioningDelegate as? TransitionDelegate else { return }
43 |
44 | // Attach a gesture recognizer to the dimming view to allow for a swipe to dismiss
45 | // Once we've confirmed that our transition delegate wants one
46 | panGesture = UIPanGestureRecognizer(target: self, action: #selector(handleSwipeToDismiss(pan:)))
47 | panGesture?.maximumNumberOfTouches = 1
48 | container.addGestureRecognizer(panGesture!)
49 | }
50 |
51 | override func dismissalTransitionWillBegin() {
52 | guard let coordinator = presentedViewController.transitionCoordinator else { return }
53 |
54 | coordinator.animate { [weak self] context in
55 | self?.dimmingView.alpha = 0.0
56 | } completion: { [weak self] context in
57 | if context.isCancelled {
58 | self?.dimmingView.alpha = 1.0
59 | }
60 | }
61 |
62 | }
63 |
64 | override func dismissalTransitionDidEnd(_ completed: Bool) {
65 | if completed {
66 | dimmingView.removeFromSuperview()
67 | }
68 | }
69 |
70 | // MARK: Sizing
71 |
72 | override var frameOfPresentedViewInContainerView: CGRect {
73 | var presentedViewFrame = CGRect.zero
74 | guard let containerBounds = containerView?.bounds else { return .zero }
75 |
76 | presentedViewFrame.size = CGSize(width: (containerBounds.size.width/2), height: containerBounds.size.height)
77 | presentedViewFrame.origin.x = containerBounds.size.width - presentedViewFrame.size.width;
78 | return presentedViewFrame;
79 | }
80 |
81 | // MARK: Private
82 |
83 | @objc private func handleSwipeToDismiss(pan:UIPanGestureRecognizer) {
84 | guard let container = containerView,
85 | let customTransitionDelegate = presentedViewController.transitioningDelegate as? TransitionDelegate else { return }
86 |
87 | let percentDriver = customTransitionDelegate.interactiveAnimator
88 | let translation = pan.translation(in: container)
89 | let percentComplete = abs(translation.y/container.bounds.height)
90 | let completionThreshold = percentComplete * 0.80
91 |
92 | func bailOut(withCancel: Bool) {
93 | if withCancel {
94 | percentDriver.cancel()
95 | } else {
96 | percentDriver.finish()
97 | container.removeGestureRecognizer(pan)
98 | }
99 | }
100 |
101 | switch pan.state {
102 | case .began:
103 | pan.setTranslation(.zero, in: container)
104 | presentedViewController.dismiss(animated: true) // Kicks off interactive transition
105 | case .changed:
106 | percentDriver.update(percentComplete)
107 | case .ended:
108 | if completionThreshold > 0.5 || pan.velocity(in: container).y > 0 {
109 | percentDriver.completionSpeed = 1.0
110 | bailOut(withCancel: false)
111 | } else {
112 | // Smooth out cancellation
113 | percentDriver.completionSpeed = 1.0 * percentComplete
114 | bailOut(withCancel: true)
115 | }
116 | case .cancelled:
117 | bailOut(withCancel: true)
118 | case .failed:
119 | bailOut(withCancel: false)
120 | default:
121 | bailOut(withCancel: false)
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/Custom Transitions.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 55;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 339A9DF02706B8E4006EC9D0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 339A9DEF2706B8E4006EC9D0 /* AppDelegate.swift */; };
11 | 339A9DF22706B8E4006EC9D0 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 339A9DF12706B8E4006EC9D0 /* SceneDelegate.swift */; };
12 | 339A9DF42706B8E4006EC9D0 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 339A9DF32706B8E4006EC9D0 /* ViewController.swift */; };
13 | 339A9DF72706B8E4006EC9D0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 339A9DF52706B8E4006EC9D0 /* Main.storyboard */; };
14 | 339A9DF92706B8E5006EC9D0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 339A9DF82706B8E5006EC9D0 /* Assets.xcassets */; };
15 | 339A9DFC2706B8E5006EC9D0 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 339A9DFA2706B8E5006EC9D0 /* LaunchScreen.storyboard */; };
16 | 339A9E042706B955006EC9D0 /* TransitionAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 339A9E032706B955006EC9D0 /* TransitionAnimator.swift */; };
17 | 339A9E062706B960006EC9D0 /* TransitionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 339A9E052706B960006EC9D0 /* TransitionDelegate.swift */; };
18 | 339A9E082706B976006EC9D0 /* TransitionPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 339A9E072706B976006EC9D0 /* TransitionPresentationController.swift */; };
19 | 339A9E0E2706BAA4006EC9D0 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 339A9E0D2706BAA4006EC9D0 /* README.md */; };
20 | 339A9E112707C4B7006EC9D0 /* PresentationScenario.swift in Sources */ = {isa = PBXBuildFile; fileRef = 339A9E102707C4B7006EC9D0 /* PresentationScenario.swift */; };
21 | 339A9E132707C4DF006EC9D0 /* ControllerSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 339A9E122707C4DF006EC9D0 /* ControllerSetup.swift */; };
22 | /* End PBXBuildFile section */
23 |
24 | /* Begin PBXFileReference section */
25 | 339A9DEC2706B8E4006EC9D0 /* Custom Transitions.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Custom Transitions.app"; sourceTree = BUILT_PRODUCTS_DIR; };
26 | 339A9DEF2706B8E4006EC9D0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
27 | 339A9DF12706B8E4006EC9D0 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
28 | 339A9DF32706B8E4006EC9D0 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
29 | 339A9DF62706B8E4006EC9D0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
30 | 339A9DF82706B8E5006EC9D0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
31 | 339A9DFB2706B8E5006EC9D0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
32 | 339A9DFD2706B8E5006EC9D0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
33 | 339A9E032706B955006EC9D0 /* TransitionAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransitionAnimator.swift; sourceTree = ""; };
34 | 339A9E052706B960006EC9D0 /* TransitionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransitionDelegate.swift; sourceTree = ""; };
35 | 339A9E072706B976006EC9D0 /* TransitionPresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransitionPresentationController.swift; sourceTree = ""; };
36 | 339A9E0D2706BAA4006EC9D0 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
37 | 339A9E102707C4B7006EC9D0 /* PresentationScenario.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentationScenario.swift; sourceTree = ""; };
38 | 339A9E122707C4DF006EC9D0 /* ControllerSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControllerSetup.swift; sourceTree = ""; };
39 | /* End PBXFileReference section */
40 |
41 | /* Begin PBXFrameworksBuildPhase section */
42 | 339A9DE92706B8E4006EC9D0 /* Frameworks */ = {
43 | isa = PBXFrameworksBuildPhase;
44 | buildActionMask = 2147483647;
45 | files = (
46 | );
47 | runOnlyForDeploymentPostprocessing = 0;
48 | };
49 | /* End PBXFrameworksBuildPhase section */
50 |
51 | /* Begin PBXGroup section */
52 | 339A9DE32706B8E4006EC9D0 = {
53 | isa = PBXGroup;
54 | children = (
55 | 339A9E0D2706BAA4006EC9D0 /* README.md */,
56 | 339A9DEE2706B8E4006EC9D0 /* Custom Transitions */,
57 | 339A9DED2706B8E4006EC9D0 /* Products */,
58 | );
59 | sourceTree = "";
60 | };
61 | 339A9DED2706B8E4006EC9D0 /* Products */ = {
62 | isa = PBXGroup;
63 | children = (
64 | 339A9DEC2706B8E4006EC9D0 /* Custom Transitions.app */,
65 | );
66 | name = Products;
67 | sourceTree = "";
68 | };
69 | 339A9DEE2706B8E4006EC9D0 /* Custom Transitions */ = {
70 | isa = PBXGroup;
71 | children = (
72 | 339A9E0F2707C49A006EC9D0 /* Logistical - No code samples here */,
73 | 339A9DEF2706B8E4006EC9D0 /* AppDelegate.swift */,
74 | 339A9DF12706B8E4006EC9D0 /* SceneDelegate.swift */,
75 | 339A9DF32706B8E4006EC9D0 /* ViewController.swift */,
76 | 339A9DF52706B8E4006EC9D0 /* Main.storyboard */,
77 | 339A9DF82706B8E5006EC9D0 /* Assets.xcassets */,
78 | 339A9DFA2706B8E5006EC9D0 /* LaunchScreen.storyboard */,
79 | 339A9DFD2706B8E5006EC9D0 /* Info.plist */,
80 | 339A9E032706B955006EC9D0 /* TransitionAnimator.swift */,
81 | 339A9E052706B960006EC9D0 /* TransitionDelegate.swift */,
82 | 339A9E072706B976006EC9D0 /* TransitionPresentationController.swift */,
83 | );
84 | path = "Custom Transitions";
85 | sourceTree = "";
86 | };
87 | 339A9E0F2707C49A006EC9D0 /* Logistical - No code samples here */ = {
88 | isa = PBXGroup;
89 | children = (
90 | 339A9E102707C4B7006EC9D0 /* PresentationScenario.swift */,
91 | 339A9E122707C4DF006EC9D0 /* ControllerSetup.swift */,
92 | );
93 | path = "Logistical - No code samples here";
94 | sourceTree = "";
95 | };
96 | /* End PBXGroup section */
97 |
98 | /* Begin PBXNativeTarget section */
99 | 339A9DEB2706B8E4006EC9D0 /* Custom Transitions */ = {
100 | isa = PBXNativeTarget;
101 | buildConfigurationList = 339A9E002706B8E5006EC9D0 /* Build configuration list for PBXNativeTarget "Custom Transitions" */;
102 | buildPhases = (
103 | 339A9DE82706B8E4006EC9D0 /* Sources */,
104 | 339A9DE92706B8E4006EC9D0 /* Frameworks */,
105 | 339A9DEA2706B8E4006EC9D0 /* Resources */,
106 | );
107 | buildRules = (
108 | );
109 | dependencies = (
110 | );
111 | name = "Custom Transitions";
112 | productName = "Custom Transitions";
113 | productReference = 339A9DEC2706B8E4006EC9D0 /* Custom Transitions.app */;
114 | productType = "com.apple.product-type.application";
115 | };
116 | /* End PBXNativeTarget section */
117 |
118 | /* Begin PBXProject section */
119 | 339A9DE42706B8E4006EC9D0 /* Project object */ = {
120 | isa = PBXProject;
121 | attributes = {
122 | BuildIndependentTargetsInParallel = 1;
123 | LastSwiftUpdateCheck = 1300;
124 | LastUpgradeCheck = 1300;
125 | TargetAttributes = {
126 | 339A9DEB2706B8E4006EC9D0 = {
127 | CreatedOnToolsVersion = 13.0;
128 | };
129 | };
130 | };
131 | buildConfigurationList = 339A9DE72706B8E4006EC9D0 /* Build configuration list for PBXProject "Custom Transitions" */;
132 | compatibilityVersion = "Xcode 13.0";
133 | developmentRegion = en;
134 | hasScannedForEncodings = 0;
135 | knownRegions = (
136 | en,
137 | Base,
138 | );
139 | mainGroup = 339A9DE32706B8E4006EC9D0;
140 | productRefGroup = 339A9DED2706B8E4006EC9D0 /* Products */;
141 | projectDirPath = "";
142 | projectRoot = "";
143 | targets = (
144 | 339A9DEB2706B8E4006EC9D0 /* Custom Transitions */,
145 | );
146 | };
147 | /* End PBXProject section */
148 |
149 | /* Begin PBXResourcesBuildPhase section */
150 | 339A9DEA2706B8E4006EC9D0 /* Resources */ = {
151 | isa = PBXResourcesBuildPhase;
152 | buildActionMask = 2147483647;
153 | files = (
154 | 339A9DFC2706B8E5006EC9D0 /* LaunchScreen.storyboard in Resources */,
155 | 339A9DF92706B8E5006EC9D0 /* Assets.xcassets in Resources */,
156 | 339A9E0E2706BAA4006EC9D0 /* README.md in Resources */,
157 | 339A9DF72706B8E4006EC9D0 /* Main.storyboard in Resources */,
158 | );
159 | runOnlyForDeploymentPostprocessing = 0;
160 | };
161 | /* End PBXResourcesBuildPhase section */
162 |
163 | /* Begin PBXSourcesBuildPhase section */
164 | 339A9DE82706B8E4006EC9D0 /* Sources */ = {
165 | isa = PBXSourcesBuildPhase;
166 | buildActionMask = 2147483647;
167 | files = (
168 | 339A9DF42706B8E4006EC9D0 /* ViewController.swift in Sources */,
169 | 339A9E062706B960006EC9D0 /* TransitionDelegate.swift in Sources */,
170 | 339A9E082706B976006EC9D0 /* TransitionPresentationController.swift in Sources */,
171 | 339A9E132707C4DF006EC9D0 /* ControllerSetup.swift in Sources */,
172 | 339A9E042706B955006EC9D0 /* TransitionAnimator.swift in Sources */,
173 | 339A9E112707C4B7006EC9D0 /* PresentationScenario.swift in Sources */,
174 | 339A9DF02706B8E4006EC9D0 /* AppDelegate.swift in Sources */,
175 | 339A9DF22706B8E4006EC9D0 /* SceneDelegate.swift in Sources */,
176 | );
177 | runOnlyForDeploymentPostprocessing = 0;
178 | };
179 | /* End PBXSourcesBuildPhase section */
180 |
181 | /* Begin PBXVariantGroup section */
182 | 339A9DF52706B8E4006EC9D0 /* Main.storyboard */ = {
183 | isa = PBXVariantGroup;
184 | children = (
185 | 339A9DF62706B8E4006EC9D0 /* Base */,
186 | );
187 | name = Main.storyboard;
188 | sourceTree = "";
189 | };
190 | 339A9DFA2706B8E5006EC9D0 /* LaunchScreen.storyboard */ = {
191 | isa = PBXVariantGroup;
192 | children = (
193 | 339A9DFB2706B8E5006EC9D0 /* Base */,
194 | );
195 | name = LaunchScreen.storyboard;
196 | sourceTree = "";
197 | };
198 | /* End PBXVariantGroup section */
199 |
200 | /* Begin XCBuildConfiguration section */
201 | 339A9DFE2706B8E5006EC9D0 /* Debug */ = {
202 | isa = XCBuildConfiguration;
203 | buildSettings = {
204 | ALWAYS_SEARCH_USER_PATHS = NO;
205 | CLANG_ANALYZER_NONNULL = YES;
206 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
207 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
208 | CLANG_CXX_LIBRARY = "libc++";
209 | CLANG_ENABLE_MODULES = YES;
210 | CLANG_ENABLE_OBJC_ARC = YES;
211 | CLANG_ENABLE_OBJC_WEAK = YES;
212 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
213 | CLANG_WARN_BOOL_CONVERSION = YES;
214 | CLANG_WARN_COMMA = YES;
215 | CLANG_WARN_CONSTANT_CONVERSION = YES;
216 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
217 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
218 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
219 | CLANG_WARN_EMPTY_BODY = YES;
220 | CLANG_WARN_ENUM_CONVERSION = YES;
221 | CLANG_WARN_INFINITE_RECURSION = YES;
222 | CLANG_WARN_INT_CONVERSION = YES;
223 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
224 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
225 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
226 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
227 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
228 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
229 | CLANG_WARN_STRICT_PROTOTYPES = YES;
230 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
231 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
232 | CLANG_WARN_UNREACHABLE_CODE = YES;
233 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
234 | COPY_PHASE_STRIP = NO;
235 | DEBUG_INFORMATION_FORMAT = dwarf;
236 | ENABLE_STRICT_OBJC_MSGSEND = YES;
237 | ENABLE_TESTABILITY = YES;
238 | GCC_C_LANGUAGE_STANDARD = gnu11;
239 | GCC_DYNAMIC_NO_PIC = NO;
240 | GCC_NO_COMMON_BLOCKS = YES;
241 | GCC_OPTIMIZATION_LEVEL = 0;
242 | GCC_PREPROCESSOR_DEFINITIONS = (
243 | "DEBUG=1",
244 | "$(inherited)",
245 | );
246 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
247 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
248 | GCC_WARN_UNDECLARED_SELECTOR = YES;
249 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
250 | GCC_WARN_UNUSED_FUNCTION = YES;
251 | GCC_WARN_UNUSED_VARIABLE = YES;
252 | IPHONEOS_DEPLOYMENT_TARGET = 15.0;
253 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
254 | MTL_FAST_MATH = YES;
255 | ONLY_ACTIVE_ARCH = YES;
256 | SDKROOT = iphoneos;
257 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
258 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
259 | };
260 | name = Debug;
261 | };
262 | 339A9DFF2706B8E5006EC9D0 /* Release */ = {
263 | isa = XCBuildConfiguration;
264 | buildSettings = {
265 | ALWAYS_SEARCH_USER_PATHS = NO;
266 | CLANG_ANALYZER_NONNULL = YES;
267 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
268 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
269 | CLANG_CXX_LIBRARY = "libc++";
270 | CLANG_ENABLE_MODULES = YES;
271 | CLANG_ENABLE_OBJC_ARC = YES;
272 | CLANG_ENABLE_OBJC_WEAK = YES;
273 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
274 | CLANG_WARN_BOOL_CONVERSION = YES;
275 | CLANG_WARN_COMMA = YES;
276 | CLANG_WARN_CONSTANT_CONVERSION = YES;
277 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
278 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
279 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
280 | CLANG_WARN_EMPTY_BODY = YES;
281 | CLANG_WARN_ENUM_CONVERSION = YES;
282 | CLANG_WARN_INFINITE_RECURSION = YES;
283 | CLANG_WARN_INT_CONVERSION = YES;
284 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
285 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
286 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
287 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
288 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
289 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
290 | CLANG_WARN_STRICT_PROTOTYPES = YES;
291 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
292 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
293 | CLANG_WARN_UNREACHABLE_CODE = YES;
294 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
295 | COPY_PHASE_STRIP = NO;
296 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
297 | ENABLE_NS_ASSERTIONS = NO;
298 | ENABLE_STRICT_OBJC_MSGSEND = YES;
299 | GCC_C_LANGUAGE_STANDARD = gnu11;
300 | GCC_NO_COMMON_BLOCKS = YES;
301 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
302 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
303 | GCC_WARN_UNDECLARED_SELECTOR = YES;
304 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
305 | GCC_WARN_UNUSED_FUNCTION = YES;
306 | GCC_WARN_UNUSED_VARIABLE = YES;
307 | IPHONEOS_DEPLOYMENT_TARGET = 15.0;
308 | MTL_ENABLE_DEBUG_INFO = NO;
309 | MTL_FAST_MATH = YES;
310 | SDKROOT = iphoneos;
311 | SWIFT_COMPILATION_MODE = wholemodule;
312 | SWIFT_OPTIMIZATION_LEVEL = "-O";
313 | VALIDATE_PRODUCT = YES;
314 | };
315 | name = Release;
316 | };
317 | 339A9E012706B8E5006EC9D0 /* Debug */ = {
318 | isa = XCBuildConfiguration;
319 | buildSettings = {
320 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
321 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
322 | CODE_SIGN_STYLE = Automatic;
323 | CURRENT_PROJECT_VERSION = 1;
324 | DEVELOPMENT_TEAM = G6E6N7P5UN;
325 | GENERATE_INFOPLIST_FILE = YES;
326 | INFOPLIST_FILE = "Custom Transitions/Info.plist";
327 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
328 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
329 | INFOPLIST_KEY_UIMainStoryboardFile = Main;
330 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
331 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
332 | LD_RUNPATH_SEARCH_PATHS = (
333 | "$(inherited)",
334 | "@executable_path/Frameworks",
335 | );
336 | MARKETING_VERSION = 1.0;
337 | PRODUCT_BUNDLE_IDENTIFIER = "com.jordanMorgan.Custom-Transitions";
338 | PRODUCT_NAME = "$(TARGET_NAME)";
339 | SWIFT_EMIT_LOC_STRINGS = YES;
340 | SWIFT_VERSION = 5.0;
341 | TARGETED_DEVICE_FAMILY = "1,2";
342 | };
343 | name = Debug;
344 | };
345 | 339A9E022706B8E5006EC9D0 /* Release */ = {
346 | isa = XCBuildConfiguration;
347 | buildSettings = {
348 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
349 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
350 | CODE_SIGN_STYLE = Automatic;
351 | CURRENT_PROJECT_VERSION = 1;
352 | DEVELOPMENT_TEAM = G6E6N7P5UN;
353 | GENERATE_INFOPLIST_FILE = YES;
354 | INFOPLIST_FILE = "Custom Transitions/Info.plist";
355 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
356 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
357 | INFOPLIST_KEY_UIMainStoryboardFile = Main;
358 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
359 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
360 | LD_RUNPATH_SEARCH_PATHS = (
361 | "$(inherited)",
362 | "@executable_path/Frameworks",
363 | );
364 | MARKETING_VERSION = 1.0;
365 | PRODUCT_BUNDLE_IDENTIFIER = "com.jordanMorgan.Custom-Transitions";
366 | PRODUCT_NAME = "$(TARGET_NAME)";
367 | SWIFT_EMIT_LOC_STRINGS = YES;
368 | SWIFT_VERSION = 5.0;
369 | TARGETED_DEVICE_FAMILY = "1,2";
370 | };
371 | name = Release;
372 | };
373 | /* End XCBuildConfiguration section */
374 |
375 | /* Begin XCConfigurationList section */
376 | 339A9DE72706B8E4006EC9D0 /* Build configuration list for PBXProject "Custom Transitions" */ = {
377 | isa = XCConfigurationList;
378 | buildConfigurations = (
379 | 339A9DFE2706B8E5006EC9D0 /* Debug */,
380 | 339A9DFF2706B8E5006EC9D0 /* Release */,
381 | );
382 | defaultConfigurationIsVisible = 0;
383 | defaultConfigurationName = Release;
384 | };
385 | 339A9E002706B8E5006EC9D0 /* Build configuration list for PBXNativeTarget "Custom Transitions" */ = {
386 | isa = XCConfigurationList;
387 | buildConfigurations = (
388 | 339A9E012706B8E5006EC9D0 /* Debug */,
389 | 339A9E022706B8E5006EC9D0 /* Release */,
390 | );
391 | defaultConfigurationIsVisible = 0;
392 | defaultConfigurationName = Release;
393 | };
394 | /* End XCConfigurationList section */
395 | };
396 | rootObject = 339A9DE42706B8E4006EC9D0 /* Project object */;
397 | }
398 |
--------------------------------------------------------------------------------