├── .swift-version ├── images └── demo.gif ├── TransEasy ├── Assets.xcassets │ ├── Contents.json │ ├── qr-64.imageset │ │ ├── qr-64.png │ │ ├── qr-64@2x.png │ │ ├── qr-64@3x.png │ │ └── Contents.json │ ├── qr-200.imageset │ │ ├── qr-200.png │ │ ├── qr-200@2x.png │ │ ├── qr-200@3x.png │ │ └── Contents.json │ └── AppIcon.appiconset │ │ ├── Icon-1024@1x-1.png │ │ ├── Icon-29.0@1x.png │ │ ├── Icon-29.0@2x.png │ │ ├── Icon-29.0@3x.png │ │ ├── Icon-40.0@1x.png │ │ ├── Icon-40.0@2x.png │ │ ├── Icon-40.0@3x.png │ │ ├── Icon-60.0@2x.png │ │ ├── Icon-60.0@3x.png │ │ ├── Icon-76.0@1x.png │ │ ├── Icon-76.0@2x.png │ │ ├── Icon-83.5@2x.png │ │ └── Contents.json ├── Info.plist ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── SecondViewController.swift ├── FirstViewController.swift └── AppDelegate.swift ├── TransEasy.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── project.pbxproj ├── CONTRIBUTING.md ├── .swiftlint.yml ├── Package.swift ├── LICENSE.txt ├── .gitignore ├── TransEasy.podspec ├── README.md └── Sources └── TransEasy ├── TransEasySegue.swift ├── UIViewController+TransEasy.swift └── TransEasyAnimationController.swift /.swift-version: -------------------------------------------------------------------------------- 1 | 4.2 2 | -------------------------------------------------------------------------------- /images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohpor/TransEasy/HEAD/images/demo.gif -------------------------------------------------------------------------------- /TransEasy/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /TransEasy/Assets.xcassets/qr-64.imageset/qr-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohpor/TransEasy/HEAD/TransEasy/Assets.xcassets/qr-64.imageset/qr-64.png -------------------------------------------------------------------------------- /TransEasy/Assets.xcassets/qr-200.imageset/qr-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohpor/TransEasy/HEAD/TransEasy/Assets.xcassets/qr-200.imageset/qr-200.png -------------------------------------------------------------------------------- /TransEasy/Assets.xcassets/qr-64.imageset/qr-64@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohpor/TransEasy/HEAD/TransEasy/Assets.xcassets/qr-64.imageset/qr-64@2x.png -------------------------------------------------------------------------------- /TransEasy/Assets.xcassets/qr-64.imageset/qr-64@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohpor/TransEasy/HEAD/TransEasy/Assets.xcassets/qr-64.imageset/qr-64@3x.png -------------------------------------------------------------------------------- /TransEasy/Assets.xcassets/qr-200.imageset/qr-200@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohpor/TransEasy/HEAD/TransEasy/Assets.xcassets/qr-200.imageset/qr-200@2x.png -------------------------------------------------------------------------------- /TransEasy/Assets.xcassets/qr-200.imageset/qr-200@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohpor/TransEasy/HEAD/TransEasy/Assets.xcassets/qr-200.imageset/qr-200@3x.png -------------------------------------------------------------------------------- /TransEasy/Assets.xcassets/AppIcon.appiconset/Icon-1024@1x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohpor/TransEasy/HEAD/TransEasy/Assets.xcassets/AppIcon.appiconset/Icon-1024@1x-1.png -------------------------------------------------------------------------------- /TransEasy/Assets.xcassets/AppIcon.appiconset/Icon-29.0@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohpor/TransEasy/HEAD/TransEasy/Assets.xcassets/AppIcon.appiconset/Icon-29.0@1x.png -------------------------------------------------------------------------------- /TransEasy/Assets.xcassets/AppIcon.appiconset/Icon-29.0@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohpor/TransEasy/HEAD/TransEasy/Assets.xcassets/AppIcon.appiconset/Icon-29.0@2x.png -------------------------------------------------------------------------------- /TransEasy/Assets.xcassets/AppIcon.appiconset/Icon-29.0@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohpor/TransEasy/HEAD/TransEasy/Assets.xcassets/AppIcon.appiconset/Icon-29.0@3x.png -------------------------------------------------------------------------------- /TransEasy/Assets.xcassets/AppIcon.appiconset/Icon-40.0@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohpor/TransEasy/HEAD/TransEasy/Assets.xcassets/AppIcon.appiconset/Icon-40.0@1x.png -------------------------------------------------------------------------------- /TransEasy/Assets.xcassets/AppIcon.appiconset/Icon-40.0@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohpor/TransEasy/HEAD/TransEasy/Assets.xcassets/AppIcon.appiconset/Icon-40.0@2x.png -------------------------------------------------------------------------------- /TransEasy/Assets.xcassets/AppIcon.appiconset/Icon-40.0@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohpor/TransEasy/HEAD/TransEasy/Assets.xcassets/AppIcon.appiconset/Icon-40.0@3x.png -------------------------------------------------------------------------------- /TransEasy/Assets.xcassets/AppIcon.appiconset/Icon-60.0@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohpor/TransEasy/HEAD/TransEasy/Assets.xcassets/AppIcon.appiconset/Icon-60.0@2x.png -------------------------------------------------------------------------------- /TransEasy/Assets.xcassets/AppIcon.appiconset/Icon-60.0@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohpor/TransEasy/HEAD/TransEasy/Assets.xcassets/AppIcon.appiconset/Icon-60.0@3x.png -------------------------------------------------------------------------------- /TransEasy/Assets.xcassets/AppIcon.appiconset/Icon-76.0@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohpor/TransEasy/HEAD/TransEasy/Assets.xcassets/AppIcon.appiconset/Icon-76.0@1x.png -------------------------------------------------------------------------------- /TransEasy/Assets.xcassets/AppIcon.appiconset/Icon-76.0@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohpor/TransEasy/HEAD/TransEasy/Assets.xcassets/AppIcon.appiconset/Icon-76.0@2x.png -------------------------------------------------------------------------------- /TransEasy/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohpor/TransEasy/HEAD/TransEasy/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png -------------------------------------------------------------------------------- /TransEasy.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TransEasy.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Fork, then clone the repo: 4 | 5 | git clone git@github.com:your-username/TransEasy.git 6 | 7 | Push to your fork and [submit a pull request][pr]. 8 | 9 | [pr]: https://github.com/mohpor/TransEasy/compare/ 10 | 11 | I will check your pull request and upon success, will merge your changes to the repo. 12 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: # rule identifiers to exclude from running 2 | - trailing_whitespace 3 | - trailing_newline 4 | - line_length 5 | - function_body_length 6 | - multiple_closures_with_trailing_closure 7 | 8 | identifier_name: 9 | min_length: 2 10 | file_length: 11 | warning: 1000 12 | error: 2000 13 | type_body_length: 14 | warning: 1000 15 | excluded: # paths to ignore during linting. Takes precedence over `included`. 16 | - Build -------------------------------------------------------------------------------- /TransEasy/Assets.xcassets/qr-64.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "qr-64.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "qr-64@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "qr-64@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /TransEasy/Assets.xcassets/qr-200.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "qr-200.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "qr-200@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "qr-200@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "TransEasy", 8 | products: [ 9 | // Products define the executables and libraries a package produces, and make them visible to other packages. 10 | .library( 11 | name: "TransEasy", 12 | targets: ["TransEasy"]) 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 21 | .target( 22 | name: "TransEasy", 23 | dependencies: []) 24 | ] 25 | ) 26 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Mohammad Poroushani 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 | -------------------------------------------------------------------------------- /TransEasy/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | 67 | .DS_Store 68 | -------------------------------------------------------------------------------- /TransEasy/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 | 27 | 28 | -------------------------------------------------------------------------------- /TransEasy.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint TransEasy.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'TransEasy' 11 | s.version = '0.8.0' 12 | s.summary = 'A simple way to have gorgeous transitions.' 13 | 14 | # This description is used to generate tags and improve search results. 15 | # * Think: What does it do? Why did you write it? What is the focus? 16 | # * Try to keep it short, snappy and to the point. 17 | # * Write the description between the DESC delimiters below. 18 | # * Finally, don't worry about the indent, CocoaPods strips it! 19 | 20 | s.description = <<-DESC 21 | Ever wanted to have that gorgeous zoom transition with your segues but hesitated because it looked way too complex to implement? Well, I thought it is too complex too! Custom Transitions shouldn't be this complex. 22 | DESC 23 | 24 | s.homepage = 'https://github.com/mohpor/TransEasy' 25 | # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' 26 | s.license = { :type => 'MIT', :file => 'LICENSE.txt' } 27 | s.author = { 'M. Porooshani' => 'porooshani@gmail.com' } 28 | s.source = { :git => 'https://github.com/mohpor/TransEasy.git', :tag => s.version.to_s } 29 | # s.social_media_url = 'https://twitter.com/mohpor' 30 | 31 | s.ios.deployment_target = '10.0' 32 | 33 | s.source_files = 'Sources/TransEasy**/*' 34 | 35 | # s.resource_bundles = { 36 | # 'TransEasy' => ['TransEasy/Assets/*.png'] 37 | # } 38 | 39 | # s.public_header_files = 'Pod/Classes/**/*.h' 40 | # s.frameworks = 'UIKit', 'MapKit' 41 | # s.dependency 'AFNetworking', '~> 2.3' 42 | end 43 | -------------------------------------------------------------------------------- /TransEasy/SecondViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+TransEasy.swift 3 | // TransEasy 4 | // 5 | // Created by Mohammad Porooshani on 7/21/16. 6 | // Copyright © 2016 Porooshani. All rights reserved. 7 | // 8 | // The MIT License (MIT) 9 | // 10 | // Copyright (c) 2016 Mohammad Poroushani 11 | // 12 | // Permission is hereby granted, free of charge, to any person obtaining a copy 13 | // of this software and associated documentation files (the "Software"), to deal 14 | // in the Software without restriction, including without limitation the rights 15 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | // copies of the Software, and to permit persons to whom the Software is 17 | // furnished to do so, subject to the following conditions: 18 | // 19 | // The above copyright notice and this permission notice shall be included in all 20 | // copies or substantial portions of the Software. 21 | // 22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | // SOFTWARE. 29 | 30 | import UIKit 31 | 32 | class SecondViewController: UIViewController { 33 | 34 | @IBOutlet weak var qrImage: UIImageView! 35 | @IBOutlet weak var qrTextView: UITextView! 36 | override func viewDidLoad() { 37 | super.viewDidLoad() 38 | } 39 | 40 | @IBAction func closeButtonClicked(_ sender: AnyObject) { 41 | 42 | if let navC = navigationController { 43 | navC.popViewController(animated: true) 44 | } else { 45 | dismiss(animated: true, completion: nil) 46 | } 47 | } 48 | 49 | override var preferredStatusBarStyle: UIStatusBarStyle { 50 | return .lightContent 51 | } 52 | 53 | } 54 | 55 | extension SecondViewController: TransEasyDestinationViewControllerProtocol { 56 | 57 | func transEasyDestinationView() -> UIView { 58 | return qrImage 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /TransEasy/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 | "filename" : "Icon-29.0@2x.png", 15 | "idiom" : "iphone", 16 | "scale" : "2x", 17 | "size" : "29x29" 18 | }, 19 | { 20 | "filename" : "Icon-29.0@3x.png", 21 | "idiom" : "iphone", 22 | "scale" : "3x", 23 | "size" : "29x29" 24 | }, 25 | { 26 | "filename" : "Icon-40.0@2x.png", 27 | "idiom" : "iphone", 28 | "scale" : "2x", 29 | "size" : "40x40" 30 | }, 31 | { 32 | "filename" : "Icon-40.0@3x.png", 33 | "idiom" : "iphone", 34 | "scale" : "3x", 35 | "size" : "40x40" 36 | }, 37 | { 38 | "filename" : "Icon-60.0@2x.png", 39 | "idiom" : "iphone", 40 | "scale" : "2x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "filename" : "Icon-60.0@3x.png", 45 | "idiom" : "iphone", 46 | "scale" : "3x", 47 | "size" : "60x60" 48 | }, 49 | { 50 | "idiom" : "ipad", 51 | "scale" : "1x", 52 | "size" : "20x20" 53 | }, 54 | { 55 | "idiom" : "ipad", 56 | "scale" : "2x", 57 | "size" : "20x20" 58 | }, 59 | { 60 | "filename" : "Icon-29.0@1x.png", 61 | "idiom" : "ipad", 62 | "scale" : "1x", 63 | "size" : "29x29" 64 | }, 65 | { 66 | "filename" : "Icon-29.0@2x.png", 67 | "idiom" : "ipad", 68 | "scale" : "2x", 69 | "size" : "29x29" 70 | }, 71 | { 72 | "filename" : "Icon-40.0@1x.png", 73 | "idiom" : "ipad", 74 | "scale" : "1x", 75 | "size" : "40x40" 76 | }, 77 | { 78 | "filename" : "Icon-40.0@2x.png", 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "40x40" 82 | }, 83 | { 84 | "filename" : "Icon-76.0@1x.png", 85 | "idiom" : "ipad", 86 | "scale" : "1x", 87 | "size" : "76x76" 88 | }, 89 | { 90 | "filename" : "Icon-76.0@2x.png", 91 | "idiom" : "ipad", 92 | "scale" : "2x", 93 | "size" : "76x76" 94 | }, 95 | { 96 | "filename" : "Icon-83.5@2x.png", 97 | "idiom" : "ipad", 98 | "scale" : "2x", 99 | "size" : "83.5x83.5" 100 | }, 101 | { 102 | "filename" : "Icon-1024@1x-1.png", 103 | "idiom" : "ios-marketing", 104 | "scale" : "1x", 105 | "size" : "1024x1024" 106 | } 107 | ], 108 | "info" : { 109 | "author" : "xcode", 110 | "version" : 1 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /TransEasy/FirstViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+TransEasy.swift 3 | // TransEasy 4 | // 5 | // Created by Mohammad Porooshani on 7/21/16. 6 | // Copyright © 2016 Porooshani. All rights reserved. 7 | // 8 | // The MIT License (MIT) 9 | // 10 | // Copyright (c) 2016 Mohammad Poroushani 11 | // 12 | // Permission is hereby granted, free of charge, to any person obtaining a copy 13 | // of this software and associated documentation files (the "Software"), to deal 14 | // in the Software without restriction, including without limitation the rights 15 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | // copies of the Software, and to permit persons to whom the Software is 17 | // furnished to do so, subject to the following conditions: 18 | // 19 | // The above copyright notice and this permission notice shall be included in all 20 | // copies or substantial portions of the Software. 21 | // 22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | // SOFTWARE. 29 | 30 | import UIKit 31 | 32 | private let toSecondViewSegueID = "toSecondViewSegue" 33 | 34 | class FirstViewController: UIViewController, TransEasyDestinationViewControllerProtocol { 35 | 36 | @IBOutlet weak var qrButton: UIButton! 37 | @IBOutlet weak var qrLabel: UILabel! 38 | @IBOutlet weak var presentationStyleButton: UIBarButtonItem! 39 | 40 | var isSegueModal = false 41 | 42 | override func viewDidLoad() { 43 | super.viewDidLoad() 44 | } 45 | 46 | override func didReceiveMemoryWarning() { 47 | super.didReceiveMemoryWarning() 48 | } 49 | @IBAction func qrButtonClicked(_ sender: AnyObject) { 50 | guard let destinationViewController = storyboard?.instantiateViewController(withIdentifier: "secondVC") else { 51 | return 52 | } 53 | // This method adds easy trans to the SecondViewController using the provided options for present and dismiss. 54 | setupEasyTransition(on: destinationViewController, presentOptions: TransEasyPresentOptions(duration: 0.4, sourceView: qrButton, blurStyle: UIBlurEffect.Style.dark), dismissOptions: TransEasyDismissOptions(duration: 0.4, destinationView: qrButton, interactive: true)) 55 | if isSegueModal { 56 | present(destinationViewController, animated: true, completion: nil) 57 | } else { 58 | performSegue(withIdentifier: toSecondViewSegueID, sender: sender) 59 | } 60 | 61 | } 62 | 63 | func transEasyDestinationView() -> UIView { 64 | return qrButton 65 | } 66 | 67 | @IBAction func modalButtonClicked(_ sender: AnyObject) { 68 | isSegueModal = !isSegueModal 69 | presentationStyleButton.title = "Style: " + (isSegueModal ? "Modal" : "Push") 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /TransEasy/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+TransEasy.swift 3 | // TransEasy 4 | // 5 | // Created by Mohammad Porooshani on 7/21/16. 6 | // Copyright © 2016 Porooshani. All rights reserved. 7 | // 8 | // The MIT License (MIT) 9 | // 10 | // Copyright (c) 2016 Mohammad Poroushani 11 | // 12 | // Permission is hereby granted, free of charge, to any person obtaining a copy 13 | // of this software and associated documentation files (the "Software"), to deal 14 | // in the Software without restriction, including without limitation the rights 15 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | // copies of the Software, and to permit persons to whom the Software is 17 | // furnished to do so, subject to the following conditions: 18 | // 19 | // The above copyright notice and this permission notice shall be included in all 20 | // copies or substantial portions of the Software. 21 | // 22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | // SOFTWARE. 29 | 30 | import UIKit 31 | 32 | @UIApplicationMain 33 | class AppDelegate: UIResponder, UIApplicationDelegate { 34 | 35 | var window: UIWindow? 36 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { 37 | // Override point for customization after application launch. 38 | return true 39 | } 40 | 41 | func applicationWillResignActive(_ application: UIApplication) { 42 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 43 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 44 | } 45 | 46 | func applicationDidEnterBackground(_ application: UIApplication) { 47 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 48 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 49 | } 50 | 51 | func applicationWillEnterForeground(_ application: UIApplication) { 52 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 53 | } 54 | 55 | func applicationDidBecomeActive(_ application: UIApplication) { 56 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 57 | } 58 | 59 | func applicationWillTerminate(_ application: UIApplication) { 60 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 61 | } 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | Platform iOS 4 | Swift 3.0 compatible 5 | 6 | MIT 7 | 8 |

9 | 10 | # TransEasy 11 | An easy to implement custom transitions. 12 |
13 | demo 14 |
15 | ## Overview: 16 | 17 | This library will help easily customize your transitions (Modal and Push) so that you can be able to move your views from one to another. 18 | 19 | --- 20 | ## How to setup: 21 | 22 | ### Cocoapods (Recommended) 23 | 24 | 1- In your pod file add: 25 | ``` 26 | pod 'TransEasy' 27 | ``` 28 | 2- In terminal: 29 | ``` 30 | $ pod update 31 | ``` 32 | 33 | ### Manual 34 | 35 | Clone or download this repo, add files inside `Source` folder to your project. 36 | 37 | --- 38 | 39 | ## How to use: 40 | 41 | ### Real easy approach: 42 | 43 | In this method you will setup the EasyTrans very easily using a simple method and us it for both push transitions and modal presentations. 44 | 45 | ```swift 46 | func next() { 47 | 48 | guard let destinationViewController = storyboard?.instantiateViewControllerWithIdentifier("secondVC") else { 49 | return 50 | } 51 | // This method adds easy trans to the SecondViewController using the provided options for present and dismiss. 52 | 53 | setupEasyTransition(on: destinationViewController, presentOptions: TransEasyPresentOptions(duration: 0.4, sourceView: qrButton, blurStyle: UIBlurEffectStyle.Dark), dismissOptions: TransEasyDismissOptions(duration: 0.4, destinationView: qrButton, interactive: true)) 54 | 55 | if modal { 56 | presentViewController(destinationViewController, animated: true, completion: nil) 57 | } else { 58 | performSegueWithIdentifier(toSecondViewSegueID, sender: sender) 59 | } 60 | 61 | } 62 | 63 | ``` 64 | 65 | In the destination view controller: 66 | 67 | ```swift 68 | extension SecondViewController: TransEasyDestinationViewControllerProtocol { 69 | 70 | func transEasyDestinationView() -> UIView { 71 | return qrImage 72 | } 73 | 74 | } 75 | 76 | ``` 77 | 78 | And to be able to use TransEasy for pop transitions in source view controller: 79 | 80 | ```swift 81 | 82 | func transEasyDestinationView() -> UIView { 83 | return qrButton 84 | } 85 | 86 | ``` 87 | 88 | 89 | 90 | ### Not so easy approach (Modal Presentation Only): 91 | Alternatively, you can implement the `transitioningDelegate` yourself and just use the animator controller. 92 | * In your view controller add required properties to hold animators: 93 | 94 | ```swift 95 | 96 | let presentAnimator: EasyPresentAnimationController = EasyPresentAnimationController() 97 | let dismissAnimator: EasyDismissAnimationController = EasyDismissAnimationController() 98 | ``` 99 | 100 | * In `prepareForSegue`, set the `transitioningDelegate`: 101 | 102 | ```swift 103 | 104 | segue.destinationViewController.transitioningDelegate = self 105 | 106 | 107 | ``` 108 | 109 | * Extend your view controller to use the **TransEasy** transitions: 110 | 111 | 112 | ```swift 113 | 114 | 115 | extension ViewController: UIViewControllerTransitioningDelegate { 116 | 117 | func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? { 118 | 119 | guard let secondVC = presented as? SecondViewController else { 120 | return nil 121 | } 122 | 123 | presentAnimator.duration = 0.4 124 | presentAnimator.originalView = qrButton 125 | presentAnimator.destinationView = secondVC.qrImage 126 | presentAnimator.blurEffectStyle = .Dark 127 | 128 | 129 | return presentAnimator 130 | } 131 | 132 | func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { 133 | 134 | guard let secondVC = dismissed as? SecondViewController else { 135 | return nil 136 | } 137 | dismissAnimator.duration = 0.4 138 | dismissAnimator.originalView = secondVC.qrImage 139 | dismissAnimator.destinationView = qrButton 140 | 141 | return dismissAnimator 142 | } 143 | 144 | } 145 | 146 | ``` 147 | 148 | --- 149 | 150 | ## TODO: 151 | 152 | - [x] Setup basic Structure for project. 153 | - [x] Create demo views and make the relations. 154 | - [x] Create Required Classes and Protocols 155 | - [x] Add License file. 156 | - [x] Add Documentations. 157 | - [x] Add screenshots. 158 | - [ ] Add CI. 159 | - [x] Add Pod support. 160 | - [ ] Make Transitions Interactive. 161 | - [ ] Match Pop Animation to the original animation. 162 | - [ ] Add Present transitioning options for Modal presentations. 163 | - [ ] Add Delegate object and events for transitions. 164 | -------------------------------------------------------------------------------- /Sources/TransEasy/TransEasySegue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+TransEasy.swift 3 | // TransEasy 4 | // 5 | // Created by Mohammad Porooshani on 7/25/16. 6 | // Copyright © 2016 Porooshani. All rights reserved. 7 | // 8 | // The MIT License (MIT) 9 | // 10 | // Copyright (c) 2016 Mohammad Poroushani 11 | // 12 | // Permission is hereby granted, free of charge, to any person obtaining a copy 13 | // of this software and associated documentation files (the "Software"), to deal 14 | // in the Software without restriction, including without limitation the rights 15 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | // copies of the Software, and to permit persons to whom the Software is 17 | // furnished to do so, subject to the following conditions: 18 | // 19 | // The above copyright notice and this permission notice shall be included in all 20 | // copies or substantial portions of the Software. 21 | // 22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | // SOFTWARE. 29 | 30 | import UIKit 31 | 32 | /// A custom segue for EasyTrans animations. 33 | public class TransEasySegue: UIStoryboardSegue { 34 | 35 | /// The source view of the transition. (For TransEasy transitions.) 36 | var sourceView: UIView? 37 | 38 | override public func perform() { 39 | 40 | // Keeping the original frame of the source controller for later use. 41 | let sourceOriginalFrame = source.view.frame 42 | // Starts the actual transition, to generate the required info. We will take control of the transition later on. 43 | super.perform() 44 | 45 | // Find strong references to required objects, or bail on the transitioning if need be. 46 | guard let transitionCoorrdinator = destination.transitionCoordinator, 47 | let secondVC = destination as? TransEasyDestinationViewControllerProtocol, 48 | let sourceV = sourceView ?? (source as? TransEasyDestinationViewControllerProtocol)?.transEasyDestinationView() 49 | else { 50 | // In case something is wrong with the transition, we will perform an appropriate transition animation just in case. 51 | print("Segue is not correctly prepared!") 52 | if let navControl = source.navigationController { 53 | navControl.pushViewController(destination, animated: true) 54 | } else { 55 | source.present(destination, animated: true, completion: nil) 56 | } 57 | return 58 | } 59 | 60 | // Initalizes The view for the destination view controller. (Interestingly enough, this method will cause the destination view controller to be initialized too! should prevent from calling it, because it is causing double instantiations.) 61 | destination.view.layoutIfNeeded() 62 | 63 | let destV = secondVC.transEasyDestinationView() 64 | let containerView = transitionCoorrdinator.containerView 65 | 66 | let originalFrame = sourceV.frame 67 | let destinationFrame = destV.frame 68 | 69 | let sourceSnapshot = sourceV.snapshot() 70 | sourceSnapshot.frame = originalFrame 71 | 72 | let destinationSnapshot = destV.snapshot() 73 | destinationSnapshot.frame = originalFrame 74 | destinationSnapshot.alpha = 0.0 75 | 76 | // Hide original views, while we work on snapshots. 77 | destV.isHidden = true 78 | sourceV.isHidden = true 79 | 80 | let sourceFullSnap = source.view.snapshot() 81 | sourceFullSnap.frame = sourceOriginalFrame 82 | 83 | containerView.addSubview(sourceFullSnap) 84 | source.view.isHidden = true 85 | 86 | // The order of these insertions are important, because that will be the way they are being rendered on top of each other. 87 | containerView.insertSubview(sourceSnapshot, aboveSubview: sourceFullSnap) 88 | containerView.insertSubview(destinationSnapshot, aboveSubview: sourceSnapshot) 89 | 90 | // This is where we start to animate alongside the tranition coordinator. 91 | transitionCoorrdinator.animate(alongsideTransition: { _ in 92 | 93 | containerView.bringSubviewToFront(destinationSnapshot) 94 | containerView.bringSubviewToFront(sourceSnapshot) 95 | 96 | sourceFullSnap.frame = self.source.view.convert(self.source.view.frame, to: containerView) 97 | 98 | sourceSnapshot.frame = destinationFrame 99 | destinationSnapshot.frame = destinationFrame 100 | 101 | sourceSnapshot.alpha = 0.0 102 | destinationSnapshot.alpha = 1.0 103 | 104 | }) { _ in 105 | 106 | sourceSnapshot.removeFromSuperview() 107 | sourceFullSnap.removeFromSuperview() 108 | destinationSnapshot.removeFromSuperview() 109 | destV.isHidden = false 110 | sourceV.isHidden = false 111 | self.source.view.isHidden = false 112 | 113 | } 114 | 115 | } 116 | 117 | } 118 | 119 | -------------------------------------------------------------------------------- /Sources/TransEasy/UIViewController+TransEasy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+TransEasy.swift 3 | // TransEasy 4 | // 5 | // Created by Mohammad Porooshani on 7/21/16. 6 | // Copyright © 2016 Porooshani. All rights reserved. 7 | // 8 | // The MIT License (MIT) 9 | // 10 | // Copyright (c) 2016 Mohammad Poroushani 11 | // 12 | // Permission is hereby granted, free of charge, to any person obtaining a copy 13 | // of this software and associated documentation files (the "Software"), to deal 14 | // in the Software without restriction, including without limitation the rights 15 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | // copies of the Software, and to permit persons to whom the Software is 17 | // furnished to do so, subject to the following conditions: 18 | // 19 | // The above copyright notice and this permission notice shall be included in all 20 | // copies or substantial portions of the Software. 21 | // 22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | // SOFTWARE. 29 | 30 | import UIKit 31 | 32 | /// Holds a reference to the memory for associated delegate object. 33 | var presentAssociatedHandler: UInt8 = 0 34 | 35 | /** 36 | * A simple struct to encapsulate present transition's settings. 37 | * This struct is used only if you use the UIViewController's extension related to TransEasy. 38 | */ 39 | public struct TransEasyPresentOptions { 40 | /// The duration of the animation. 41 | public var duration: TimeInterval = 0.4 42 | /// The view present will start from. 43 | public let sourceView: UIView 44 | /// The blur effect to use as background. (If set as nil, won't add blur effect) 45 | public let blurStyle: UIBlurEffect.Style? 46 | 47 | public init(duration: TimeInterval, sourceView: UIView, blurStyle: UIBlurEffect.Style? = nil) { 48 | self.duration = duration 49 | self.sourceView = sourceView 50 | self.blurStyle = blurStyle 51 | } 52 | } 53 | 54 | /** 55 | * A simple struct to encapsulate dismiss transition's settings. 56 | * This struct is used only if you use the UIViewController's extension related to TransEasy. 57 | */ 58 | public struct TransEasyDismissOptions { 59 | /// The duration of the animation. 60 | public var duration: TimeInterval = 0.4 61 | /// The view dismiss transition animation will end on. 62 | public let destinationView: UIView 63 | /** 64 | Indicates the dismiss action could be interactive. 65 | - Please be advised that this is a simple suggestion. FOr now, if the presentation style is not modal (push, replace, etc...) this will not add an interactive dismissal. 66 | */ 67 | public var interactive = false 68 | 69 | public init(duration: TimeInterval, destinationView: UIView, interactive: Bool = false) { 70 | self.duration = duration 71 | self.destinationView = destinationView 72 | self.interactive = interactive 73 | } 74 | 75 | } 76 | 77 | public extension UIViewController { 78 | 79 | /// The reference to the animator object. The `transitioningDelegate` of the `UIViewController` is of weak type therefore ot will be lost after setup. 80 | internal var easyTransDelegate: EasyPresentHelper? { 81 | get { 82 | return objc_getAssociatedObject(self, &presentAssociatedHandler) as? EasyPresentHelper 83 | } 84 | 85 | set { 86 | objc_setAssociatedObject(self, &presentAssociatedHandler, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 87 | 88 | } 89 | } 90 | 91 | /** 92 | Allows the view controller to have an easy transition from a view controller to another view controller, moving a view in between. 93 | 94 | - parameter targetViewController: The destination of the transition. If you are using a segue, this is the actual `destinationViewController` of the segue. 95 | - parameter presentOptions: The options for present animation. 96 | - parameter dismissOptions: The options for dimiss animation. 97 | 98 | - discussion `targetViewController` should implement `TransEasyDestinationViewControllerProtocol`, because in `prepareForSegue` views are not yet initialized and you wouldn't have access to view's references. 99 | 100 | */ 101 | func setupEasyTransition(on targetViewController: UIViewController, presentOptions: TransEasyPresentOptions?, dismissOptions: TransEasyDismissOptions?) { 102 | 103 | let transDel = EasyPresentHelper(presentOptions: presentOptions, dismissOptions: dismissOptions) 104 | 105 | easyTransDelegate = transDel 106 | 107 | if true == dismissOptions?.interactive { 108 | transDel.interactiveAnimator.attach(to: targetViewController) 109 | } 110 | 111 | targetViewController.transitioningDelegate = easyTransDelegate 112 | self.navigationController?.delegate = transDel 113 | 114 | } 115 | 116 | /** 117 | Determines if the presentatin is modal. 118 | 119 | - returns: true if is modally presented, false otherwise. 120 | */ 121 | func isModal() -> Bool { 122 | if self.presentingViewController != nil { 123 | return true 124 | } 125 | 126 | if self.presentingViewController?.presentedViewController == self { 127 | return true 128 | } 129 | 130 | if self.navigationController?.presentingViewController?.presentedViewController == self.navigationController { 131 | return true 132 | } 133 | 134 | if self.tabBarController?.presentingViewController is UITabBarController { 135 | return true 136 | } 137 | 138 | return false 139 | } 140 | } 141 | 142 | /// A class that will act as animation controller for the view controllers. 143 | class EasyPresentHelper: NSObject, UIViewControllerTransitioningDelegate, UINavigationControllerDelegate { 144 | 145 | /// A lazy instance of `EasyPresentAnimationController` that will hold on to present animation controller. 146 | lazy var presentAnimator = EasyPresentAnimationController() 147 | /// A lazy instance of `EasyDismissAnimationController` that will hold on to dimiss animation controller. 148 | lazy var dismissAnimator = EasyDismissAnimationController() 149 | /// A lazy instance of `EasyPopAnimationController` that will hold on to pop animation. 150 | lazy var popAnimator = EasyPopAnimationController() 151 | /// A lazy `EasyInteractiveAnimationController` that will hold on to an interactive animation controller. 152 | lazy var interactiveAnimator = EasyInteractiveAnimationController() 153 | /// The present options. 154 | let presentOptions: TransEasyPresentOptions? 155 | /// The dismiss options. 156 | let dismissOptions: TransEasyDismissOptions? 157 | 158 | init(presentOptions: TransEasyPresentOptions?, dismissOptions: TransEasyDismissOptions?) { 159 | self.presentOptions = presentOptions 160 | self.dismissOptions = dismissOptions 161 | } 162 | 163 | func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { 164 | 165 | /// If setup is not complete, this method will return nil allowing UIKit to use the default transition. 166 | guard let pOptions = presentOptions, 167 | let pDestPro = presented as? TransEasyDestinationViewControllerProtocol 168 | else { 169 | return nil 170 | } 171 | 172 | // Setup animator's settings. 173 | presentAnimator.blurEffectStyle = pOptions.blurStyle 174 | presentAnimator.duration = pOptions.duration 175 | presentAnimator.originalView = pOptions.sourceView 176 | presentAnimator.destinationView = pDestPro.transEasyDestinationView() 177 | 178 | return presentAnimator 179 | 180 | } 181 | 182 | func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { 183 | /// If setup is not complete, this method will return nil allowing UIKit to use the default transition. 184 | guard let dOption = dismissOptions, 185 | let sDestPro = dismissed as? TransEasyDestinationViewControllerProtocol 186 | else { 187 | return nil 188 | } 189 | 190 | // Setup animator's settings. 191 | dismissAnimator.duration = dOption.duration 192 | dismissAnimator.originalView = sDestPro.transEasyDestinationView() 193 | dismissAnimator.destinationView = dOption.destinationView 194 | 195 | return dismissAnimator 196 | 197 | } 198 | 199 | func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { 200 | 201 | interactiveAnimator.panDistance = presentAnimator.transitionDistance == 0.0 ? 200.0 : presentAnimator.transitionDistance 202 | return interactiveAnimator.isInteracting ? interactiveAnimator : nil 203 | 204 | } 205 | 206 | func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { 207 | 208 | guard operation == .pop else { 209 | return nil 210 | } 211 | 212 | /// If setup is not complete, this method will return nil allowing UIKit to use the default transition. 213 | guard let dOption = dismissOptions, 214 | let sDestPro = fromVC as? TransEasyDestinationViewControllerProtocol 215 | else { 216 | return nil 217 | } 218 | 219 | // Setup animator's settings. 220 | popAnimator.duration = dOption.duration 221 | popAnimator.originalView = sDestPro.transEasyDestinationView() 222 | popAnimator.destinationView = dOption.destinationView 223 | 224 | return popAnimator 225 | 226 | } 227 | } 228 | 229 | /** 230 | * A simple protocol that will be used instead of a property, to let the destination view be selected lazily. 231 | */ 232 | public protocol TransEasyDestinationViewControllerProtocol { 233 | 234 | /** 235 | The View that transition will finish on. Please note that this method can be used for dismiss options as well. 236 | 237 | - returns: A UIView that will be used at final view (or initial view for dimiss) transition. 238 | */ 239 | func transEasyDestinationView() -> UIView 240 | 241 | } 242 | -------------------------------------------------------------------------------- /TransEasy/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 48 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. 110 | 111 | 112 | 113 | 114 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /TransEasy.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 113326CC1D3DE48900D43634 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113326CB1D3DE48900D43634 /* AppDelegate.swift */; }; 11 | 113326D11D3DE48900D43634 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 113326CF1D3DE48900D43634 /* Main.storyboard */; }; 12 | 113326D31D3DE48900D43634 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 113326D21D3DE48900D43634 /* Assets.xcassets */; }; 13 | 113326D61D3DE48900D43634 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 113326D41D3DE48900D43634 /* LaunchScreen.storyboard */; }; 14 | 113326E11D3E2DCE00D43634 /* FirstViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113326E01D3E2DCE00D43634 /* FirstViewController.swift */; }; 15 | 113326E31D3E2DDC00D43634 /* SecondViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 113326E21D3E2DDC00D43634 /* SecondViewController.swift */; }; 16 | 9517998B26D852C30013C64D /* TransEasyAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9517998826D852C30013C64D /* TransEasyAnimationController.swift */; }; 17 | 9517998C26D852C30013C64D /* UIViewController+TransEasy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9517998926D852C30013C64D /* UIViewController+TransEasy.swift */; }; 18 | 9517998D26D852C30013C64D /* TransEasySegue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9517998A26D852C30013C64D /* TransEasySegue.swift */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | 113326C81D3DE48900D43634 /* TransEasy.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TransEasy.app; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | 113326CB1D3DE48900D43634 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 24 | 113326D01D3DE48900D43634 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 25 | 113326D21D3DE48900D43634 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 26 | 113326D51D3DE48900D43634 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 27 | 113326D71D3DE48900D43634 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 28 | 113326E01D3E2DCE00D43634 /* FirstViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirstViewController.swift; sourceTree = ""; }; 29 | 113326E21D3E2DDC00D43634 /* SecondViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecondViewController.swift; sourceTree = ""; }; 30 | 9517998826D852C30013C64D /* TransEasyAnimationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransEasyAnimationController.swift; sourceTree = ""; }; 31 | 9517998926D852C30013C64D /* UIViewController+TransEasy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+TransEasy.swift"; sourceTree = ""; }; 32 | 9517998A26D852C30013C64D /* TransEasySegue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransEasySegue.swift; sourceTree = ""; }; 33 | /* End PBXFileReference section */ 34 | 35 | /* Begin PBXFrameworksBuildPhase section */ 36 | 113326C51D3DE48900D43634 /* Frameworks */ = { 37 | isa = PBXFrameworksBuildPhase; 38 | buildActionMask = 2147483647; 39 | files = ( 40 | ); 41 | runOnlyForDeploymentPostprocessing = 0; 42 | }; 43 | /* End PBXFrameworksBuildPhase section */ 44 | 45 | /* Begin PBXGroup section */ 46 | 113326BF1D3DE48900D43634 = { 47 | isa = PBXGroup; 48 | children = ( 49 | 9517998626D852C30013C64D /* Sources */, 50 | 113326CA1D3DE48900D43634 /* TransEasy */, 51 | 113326C91D3DE48900D43634 /* Products */, 52 | ); 53 | indentWidth = 2; 54 | sourceTree = ""; 55 | tabWidth = 2; 56 | }; 57 | 113326C91D3DE48900D43634 /* Products */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | 113326C81D3DE48900D43634 /* TransEasy.app */, 61 | ); 62 | name = Products; 63 | sourceTree = ""; 64 | }; 65 | 113326CA1D3DE48900D43634 /* TransEasy */ = { 66 | isa = PBXGroup; 67 | children = ( 68 | 113326CB1D3DE48900D43634 /* AppDelegate.swift */, 69 | 113326CF1D3DE48900D43634 /* Main.storyboard */, 70 | 113326D21D3DE48900D43634 /* Assets.xcassets */, 71 | 113326D41D3DE48900D43634 /* LaunchScreen.storyboard */, 72 | 113326D71D3DE48900D43634 /* Info.plist */, 73 | 113326E01D3E2DCE00D43634 /* FirstViewController.swift */, 74 | 113326E21D3E2DDC00D43634 /* SecondViewController.swift */, 75 | ); 76 | path = TransEasy; 77 | sourceTree = ""; 78 | }; 79 | 9517998626D852C30013C64D /* Sources */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 9517998726D852C30013C64D /* TransEasy */, 83 | ); 84 | path = Sources; 85 | sourceTree = ""; 86 | }; 87 | 9517998726D852C30013C64D /* TransEasy */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | 9517998826D852C30013C64D /* TransEasyAnimationController.swift */, 91 | 9517998926D852C30013C64D /* UIViewController+TransEasy.swift */, 92 | 9517998A26D852C30013C64D /* TransEasySegue.swift */, 93 | ); 94 | path = TransEasy; 95 | sourceTree = ""; 96 | }; 97 | /* End PBXGroup section */ 98 | 99 | /* Begin PBXNativeTarget section */ 100 | 113326C71D3DE48900D43634 /* TransEasy */ = { 101 | isa = PBXNativeTarget; 102 | buildConfigurationList = 113326DA1D3DE48900D43634 /* Build configuration list for PBXNativeTarget "TransEasy" */; 103 | buildPhases = ( 104 | 113326C41D3DE48900D43634 /* Sources */, 105 | 113326C51D3DE48900D43634 /* Frameworks */, 106 | 113326C61D3DE48900D43634 /* Resources */, 107 | 1127CC7D1D44D02C001686D0 /* ShellScript */, 108 | ); 109 | buildRules = ( 110 | ); 111 | dependencies = ( 112 | ); 113 | name = TransEasy; 114 | productName = TransEasy; 115 | productReference = 113326C81D3DE48900D43634 /* TransEasy.app */; 116 | productType = "com.apple.product-type.application"; 117 | }; 118 | /* End PBXNativeTarget section */ 119 | 120 | /* Begin PBXProject section */ 121 | 113326C01D3DE48900D43634 /* Project object */ = { 122 | isa = PBXProject; 123 | attributes = { 124 | LastSwiftUpdateCheck = 0730; 125 | LastUpgradeCheck = 1250; 126 | ORGANIZATIONNAME = Porooshani; 127 | TargetAttributes = { 128 | 113326C71D3DE48900D43634 = { 129 | CreatedOnToolsVersion = 7.3.1; 130 | LastSwiftMigration = 1250; 131 | }; 132 | }; 133 | }; 134 | buildConfigurationList = 113326C31D3DE48900D43634 /* Build configuration list for PBXProject "TransEasy" */; 135 | compatibilityVersion = "Xcode 3.2"; 136 | developmentRegion = en; 137 | hasScannedForEncodings = 0; 138 | knownRegions = ( 139 | en, 140 | Base, 141 | ); 142 | mainGroup = 113326BF1D3DE48900D43634; 143 | productRefGroup = 113326C91D3DE48900D43634 /* Products */; 144 | projectDirPath = ""; 145 | projectRoot = ""; 146 | targets = ( 147 | 113326C71D3DE48900D43634 /* TransEasy */, 148 | ); 149 | }; 150 | /* End PBXProject section */ 151 | 152 | /* Begin PBXResourcesBuildPhase section */ 153 | 113326C61D3DE48900D43634 /* Resources */ = { 154 | isa = PBXResourcesBuildPhase; 155 | buildActionMask = 2147483647; 156 | files = ( 157 | 113326D61D3DE48900D43634 /* LaunchScreen.storyboard in Resources */, 158 | 113326D31D3DE48900D43634 /* Assets.xcassets in Resources */, 159 | 113326D11D3DE48900D43634 /* Main.storyboard in Resources */, 160 | ); 161 | runOnlyForDeploymentPostprocessing = 0; 162 | }; 163 | /* End PBXResourcesBuildPhase section */ 164 | 165 | /* Begin PBXShellScriptBuildPhase section */ 166 | 1127CC7D1D44D02C001686D0 /* ShellScript */ = { 167 | isa = PBXShellScriptBuildPhase; 168 | buildActionMask = 2147483647; 169 | files = ( 170 | ); 171 | inputPaths = ( 172 | ); 173 | outputPaths = ( 174 | ); 175 | runOnlyForDeploymentPostprocessing = 0; 176 | shellPath = /bin/sh; 177 | shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi"; 178 | }; 179 | /* End PBXShellScriptBuildPhase section */ 180 | 181 | /* Begin PBXSourcesBuildPhase section */ 182 | 113326C41D3DE48900D43634 /* Sources */ = { 183 | isa = PBXSourcesBuildPhase; 184 | buildActionMask = 2147483647; 185 | files = ( 186 | 113326E31D3E2DDC00D43634 /* SecondViewController.swift in Sources */, 187 | 113326CC1D3DE48900D43634 /* AppDelegate.swift in Sources */, 188 | 9517998D26D852C30013C64D /* TransEasySegue.swift in Sources */, 189 | 113326E11D3E2DCE00D43634 /* FirstViewController.swift in Sources */, 190 | 9517998B26D852C30013C64D /* TransEasyAnimationController.swift in Sources */, 191 | 9517998C26D852C30013C64D /* UIViewController+TransEasy.swift in Sources */, 192 | ); 193 | runOnlyForDeploymentPostprocessing = 0; 194 | }; 195 | /* End PBXSourcesBuildPhase section */ 196 | 197 | /* Begin PBXVariantGroup section */ 198 | 113326CF1D3DE48900D43634 /* Main.storyboard */ = { 199 | isa = PBXVariantGroup; 200 | children = ( 201 | 113326D01D3DE48900D43634 /* Base */, 202 | ); 203 | name = Main.storyboard; 204 | sourceTree = ""; 205 | }; 206 | 113326D41D3DE48900D43634 /* LaunchScreen.storyboard */ = { 207 | isa = PBXVariantGroup; 208 | children = ( 209 | 113326D51D3DE48900D43634 /* Base */, 210 | ); 211 | name = LaunchScreen.storyboard; 212 | sourceTree = ""; 213 | }; 214 | /* End PBXVariantGroup section */ 215 | 216 | /* Begin XCBuildConfiguration section */ 217 | 113326D81D3DE48900D43634 /* Debug */ = { 218 | isa = XCBuildConfiguration; 219 | buildSettings = { 220 | ALWAYS_SEARCH_USER_PATHS = NO; 221 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 222 | CLANG_ANALYZER_NONNULL = YES; 223 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 224 | CLANG_CXX_LIBRARY = "libc++"; 225 | CLANG_ENABLE_MODULES = YES; 226 | CLANG_ENABLE_OBJC_ARC = YES; 227 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 228 | CLANG_WARN_BOOL_CONVERSION = YES; 229 | CLANG_WARN_COMMA = YES; 230 | CLANG_WARN_CONSTANT_CONVERSION = YES; 231 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = 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_INFINITE_RECURSION = YES; 236 | CLANG_WARN_INT_CONVERSION = YES; 237 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 238 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 239 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 240 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 241 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 242 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 243 | CLANG_WARN_STRICT_PROTOTYPES = YES; 244 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 245 | CLANG_WARN_UNREACHABLE_CODE = YES; 246 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 247 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 248 | COPY_PHASE_STRIP = NO; 249 | DEBUG_INFORMATION_FORMAT = dwarf; 250 | ENABLE_STRICT_OBJC_MSGSEND = YES; 251 | ENABLE_TESTABILITY = YES; 252 | GCC_C_LANGUAGE_STANDARD = gnu99; 253 | GCC_DYNAMIC_NO_PIC = NO; 254 | GCC_NO_COMMON_BLOCKS = YES; 255 | GCC_OPTIMIZATION_LEVEL = 0; 256 | GCC_PREPROCESSOR_DEFINITIONS = ( 257 | "DEBUG=1", 258 | "$(inherited)", 259 | ); 260 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 261 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 262 | GCC_WARN_UNDECLARED_SELECTOR = YES; 263 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 264 | GCC_WARN_UNUSED_FUNCTION = YES; 265 | GCC_WARN_UNUSED_VARIABLE = YES; 266 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 267 | MTL_ENABLE_DEBUG_INFO = YES; 268 | ONLY_ACTIVE_ARCH = YES; 269 | SDKROOT = iphoneos; 270 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 271 | TARGETED_DEVICE_FAMILY = "1,2"; 272 | }; 273 | name = Debug; 274 | }; 275 | 113326D91D3DE48900D43634 /* Release */ = { 276 | isa = XCBuildConfiguration; 277 | buildSettings = { 278 | ALWAYS_SEARCH_USER_PATHS = NO; 279 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 280 | CLANG_ANALYZER_NONNULL = YES; 281 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 282 | CLANG_CXX_LIBRARY = "libc++"; 283 | CLANG_ENABLE_MODULES = YES; 284 | CLANG_ENABLE_OBJC_ARC = YES; 285 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 286 | CLANG_WARN_BOOL_CONVERSION = YES; 287 | CLANG_WARN_COMMA = YES; 288 | CLANG_WARN_CONSTANT_CONVERSION = YES; 289 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 290 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 291 | CLANG_WARN_EMPTY_BODY = YES; 292 | CLANG_WARN_ENUM_CONVERSION = YES; 293 | CLANG_WARN_INFINITE_RECURSION = YES; 294 | CLANG_WARN_INT_CONVERSION = YES; 295 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 296 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 297 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 298 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 299 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 300 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 301 | CLANG_WARN_STRICT_PROTOTYPES = YES; 302 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 303 | CLANG_WARN_UNREACHABLE_CODE = YES; 304 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 305 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 306 | COPY_PHASE_STRIP = NO; 307 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 308 | ENABLE_NS_ASSERTIONS = NO; 309 | ENABLE_STRICT_OBJC_MSGSEND = YES; 310 | GCC_C_LANGUAGE_STANDARD = gnu99; 311 | GCC_NO_COMMON_BLOCKS = YES; 312 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 313 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 314 | GCC_WARN_UNDECLARED_SELECTOR = YES; 315 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 316 | GCC_WARN_UNUSED_FUNCTION = YES; 317 | GCC_WARN_UNUSED_VARIABLE = YES; 318 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 319 | MTL_ENABLE_DEBUG_INFO = NO; 320 | SDKROOT = iphoneos; 321 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 322 | TARGETED_DEVICE_FAMILY = "1,2"; 323 | VALIDATE_PRODUCT = YES; 324 | }; 325 | name = Release; 326 | }; 327 | 113326DB1D3DE48900D43634 /* Debug */ = { 328 | isa = XCBuildConfiguration; 329 | buildSettings = { 330 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 331 | CODE_SIGN_IDENTITY = "iPhone Developer"; 332 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 333 | DEVELOPMENT_TEAM = ""; 334 | INFOPLIST_FILE = TransEasy/Info.plist; 335 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 336 | PRODUCT_BUNDLE_IDENTIFIER = com.porooshani.TransEasy; 337 | PRODUCT_NAME = "$(TARGET_NAME)"; 338 | PROVISIONING_PROFILE = ""; 339 | SWIFT_VERSION = 5.0; 340 | }; 341 | name = Debug; 342 | }; 343 | 113326DC1D3DE48900D43634 /* Release */ = { 344 | isa = XCBuildConfiguration; 345 | buildSettings = { 346 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 347 | CODE_SIGN_IDENTITY = "iPhone Developer"; 348 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 349 | DEVELOPMENT_TEAM = ""; 350 | INFOPLIST_FILE = TransEasy/Info.plist; 351 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 352 | PRODUCT_BUNDLE_IDENTIFIER = com.porooshani.TransEasy; 353 | PRODUCT_NAME = "$(TARGET_NAME)"; 354 | PROVISIONING_PROFILE = ""; 355 | SWIFT_VERSION = 5.0; 356 | }; 357 | name = Release; 358 | }; 359 | /* End XCBuildConfiguration section */ 360 | 361 | /* Begin XCConfigurationList section */ 362 | 113326C31D3DE48900D43634 /* Build configuration list for PBXProject "TransEasy" */ = { 363 | isa = XCConfigurationList; 364 | buildConfigurations = ( 365 | 113326D81D3DE48900D43634 /* Debug */, 366 | 113326D91D3DE48900D43634 /* Release */, 367 | ); 368 | defaultConfigurationIsVisible = 0; 369 | defaultConfigurationName = Release; 370 | }; 371 | 113326DA1D3DE48900D43634 /* Build configuration list for PBXNativeTarget "TransEasy" */ = { 372 | isa = XCConfigurationList; 373 | buildConfigurations = ( 374 | 113326DB1D3DE48900D43634 /* Debug */, 375 | 113326DC1D3DE48900D43634 /* Release */, 376 | ); 377 | defaultConfigurationIsVisible = 0; 378 | defaultConfigurationName = Release; 379 | }; 380 | /* End XCConfigurationList section */ 381 | }; 382 | rootObject = 113326C01D3DE48900D43634 /* Project object */; 383 | } 384 | -------------------------------------------------------------------------------- /Sources/TransEasy/TransEasyAnimationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+TransEasy.swift 3 | // TransEasy 4 | // 5 | // Created by Mohammad Porooshani on 7/21/16. 6 | // Copyright © 2016 Porooshani. All rights reserved. 7 | // 8 | // The MIT License (MIT) 9 | // 10 | // Copyright (c) 2016 Mohammad Poroushani 11 | // 12 | // Permission is hereby granted, free of charge, to any person obtaining a copy 13 | // of this software and associated documentation files (the "Software"), to deal 14 | // in the Software without restriction, including without limitation the rights 15 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | // copies of the Software, and to permit persons to whom the Software is 17 | // furnished to do so, subject to the following conditions: 18 | // 19 | // The above copyright notice and this permission notice shall be included in all 20 | // copies or substantial portions of the Software. 21 | // 22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | // SOFTWARE. 29 | 30 | import UIKit 31 | 32 | /// Handles animations reqired for the TransEasy Present. 33 | public class EasyPresentAnimationController: NSObject, UIViewControllerAnimatedTransitioning { 34 | 35 | /// The view animation would use as starting point. 36 | public var originalView: UIView? 37 | /// The view that originalView will land to. 38 | public var destinationView: UIView? 39 | /// The duration of animation. 40 | public var duration: TimeInterval = 0.4 41 | /// The background's blur style. If nil, won't add blur effect. 42 | public var blurEffectStyle: UIBlurEffect.Style? 43 | 44 | // Helps figuring the distance views has moved to better handle a possible pan gesture. 45 | internal var transitionDistance: CGFloat = 0.0 46 | 47 | public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 48 | return duration 49 | } 50 | 51 | public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 52 | 53 | // Check the integrity of context 54 | guard let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from), 55 | let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to), 56 | let originView = originalView, 57 | let destView = destinationView 58 | else { 59 | print("Transition has not been setup!") 60 | return 61 | } 62 | let containerView = transitionContext.containerView 63 | print("Easy Present") 64 | 65 | // Prepares the presented view before moving on. 66 | toVC.view.frame = transitionContext.finalFrame(for: toVC) 67 | toVC.view.setNeedsDisplay() 68 | toVC.view.layoutIfNeeded() 69 | 70 | // Prepares required snapshots. 71 | let finalFrame = destView.frame 72 | let originalFrame = originView.frame 73 | let fromSnapshot = originView.snapshot() 74 | let toSnapshot = destView.snapshot() 75 | 76 | // Setup snapshot states before starting animations. 77 | fromSnapshot.alpha = 1.0 78 | toSnapshot.alpha = 0.0 79 | toVC.view.alpha = 0.0 80 | 81 | fromSnapshot.frame = originalFrame 82 | toSnapshot.frame = originalFrame 83 | 84 | let xTrans = abs(originalFrame.minX - destView.frame.minX) 85 | let yTrans = abs(originalFrame.minY - destView.frame.minY) 86 | transitionDistance = distance(of: CGPoint(x: xTrans, y: yTrans)) 87 | 88 | destView.isHidden = true 89 | originView.isHidden = true 90 | 91 | // Add blur style, in case a blur style has been set. 92 | if let blurStyle = blurEffectStyle { 93 | let fromWholeSnapshot = fromVC.view.snapshot() 94 | let effectView = UIVisualEffectView(effect: UIBlurEffect(style: blurStyle)) 95 | effectView.frame = transitionContext.finalFrame(for: toVC) 96 | effectView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 97 | effectView.addSubview(fromWholeSnapshot) 98 | toVC.view.insertSubview(fromWholeSnapshot, at: 0) 99 | toVC.view.insertSubview(effectView, aboveSubview: fromWholeSnapshot) 100 | 101 | } 102 | 103 | // Adds views to container view to start animations. 104 | containerView.addSubview(toVC.view) 105 | containerView.addSubview(fromSnapshot) 106 | containerView.addSubview(toSnapshot) 107 | 108 | let duration = transitionDuration(using: transitionContext) 109 | 110 | // Animations will be handled with keyframe animations. 111 | UIView.animateKeyframes(withDuration: duration, delay: 0, options: [.calculationModeCubicPaced], animations: { 112 | 113 | // The move animation. 114 | UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1, animations: { 115 | fromSnapshot.frame = finalFrame 116 | toSnapshot.frame = finalFrame 117 | toVC.view.alpha = 1.0 118 | }) 119 | 120 | // Fades source view to destination. 121 | UIView.addKeyframe(withRelativeStartTime: 1/2, relativeDuration: 1/2, animations: { 122 | fromSnapshot.alpha = 0.0 123 | toSnapshot.alpha = 1.0 124 | }) 125 | 126 | }) { _ in 127 | 128 | // Wrap up final state of the transition. 129 | destView.layoutIfNeeded() 130 | destView.isHidden = false 131 | originView.isHidden = false 132 | 133 | fromSnapshot.removeFromSuperview() 134 | toSnapshot.removeFromSuperview() 135 | 136 | transitionContext.completeTransition(!transitionContext.transitionWasCancelled) 137 | } 138 | 139 | } 140 | 141 | } 142 | 143 | /// Handles animations reqired for the TransEasy Dismiss. 144 | public class EasyDismissAnimationController: NSObject, UIViewControllerAnimatedTransitioning { 145 | 146 | // The source view dimiss transition starts from. 147 | public var originalView: UIView? 148 | // The view that dimiss will land to. 149 | public var destinationView: UIView? 150 | // Transitions duration. 151 | public var duration: TimeInterval = 0.4 152 | 153 | public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 154 | return duration 155 | } 156 | 157 | public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 158 | 159 | // Check the integrity of context 160 | guard let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from), 161 | let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to), 162 | let originView = originalView, 163 | let destView = destinationView 164 | else { 165 | print("Transition has not been setup!") 166 | return 167 | } 168 | let containerView = transitionContext.containerView 169 | print("Easy Dismiss") 170 | // Prepare required info fro transitions. 171 | let finalFrame = destView.frame 172 | let originalFrame = originView.frame 173 | let fromSnapshot = originView.snapshotView(afterScreenUpdates: false) 174 | let toSnapshot = destView.snapshot() 175 | 176 | // Setup initial state of the snapshots and other views. 177 | fromSnapshot?.alpha = 1.0 178 | toSnapshot.alpha = 0.0 179 | fromVC.view.alpha = 1.0 180 | toVC.view.alpha = 1.0 181 | fromSnapshot?.frame = originalFrame 182 | toSnapshot.frame = originalFrame 183 | 184 | originView.isHidden = true 185 | destView.isHidden = true 186 | let fromWholeSnapshot = fromVC.view.snapshot() 187 | 188 | // Add views to transition's container view. 189 | containerView.addSubview(toVC.view) 190 | containerView.addSubview(fromWholeSnapshot) 191 | containerView.addSubview(fromSnapshot!) 192 | containerView.addSubview(toSnapshot) 193 | 194 | let duration = transitionDuration(using: transitionContext) 195 | 196 | // Transition's animation will be handled using keyframe. 197 | UIView.animateKeyframes(withDuration: duration, delay: 0, options: [.calculationModeCubicPaced], animations: { 198 | 199 | // The move transition. 200 | UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1, animations: { 201 | fromSnapshot?.frame = finalFrame 202 | toSnapshot.frame = finalFrame 203 | 204 | }) 205 | 206 | UIView.addKeyframe(withRelativeStartTime: 1/4, relativeDuration: 3/4, animations: { 207 | fromWholeSnapshot.alpha = 0.0 208 | }) 209 | 210 | // Fade animation from source to destination view. 211 | UIView.addKeyframe(withRelativeStartTime: 1/2, relativeDuration: 1/2, animations: { 212 | fromSnapshot?.alpha = 0.0 213 | toSnapshot.alpha = 1.0 214 | fromVC.view.alpha = 0.0 215 | }) 216 | 217 | }) { _ in 218 | 219 | // Wrap up final state of the transitions. 220 | destView.layoutIfNeeded() 221 | 222 | destView.isHidden = false 223 | originView.isHidden = false 224 | 225 | fromSnapshot?.removeFromSuperview() 226 | toSnapshot.removeFromSuperview() 227 | fromWholeSnapshot.removeFromSuperview() 228 | 229 | // For interactive dismissal, we need to know if the transition was cancelled and revert the effect of transition causing source view to be removed from container view. 230 | if transitionContext.transitionWasCancelled { 231 | containerView.addSubview(fromVC.view) 232 | transitionContext.completeTransition(false) 233 | } else { 234 | transitionContext.completeTransition(true) 235 | } 236 | 237 | } 238 | 239 | } 240 | 241 | } 242 | 243 | public class EasyPopAnimationController: NSObject, UIViewControllerAnimatedTransitioning { 244 | 245 | // The source view dimiss transition starts from. 246 | public var originalView: UIView? 247 | // The view that dimiss will land to. 248 | public var destinationView: UIView? 249 | // Transitions duration. 250 | public var duration: TimeInterval = 0.4 251 | 252 | public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { 253 | return duration 254 | } 255 | 256 | public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 257 | 258 | // Check the integrity of context 259 | guard let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from), 260 | let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to), 261 | let originView = originalView, 262 | let destView = destinationView 263 | else { 264 | print("Transition has not been setup!") 265 | return 266 | } 267 | let containerView = transitionContext.containerView 268 | print("Easy Pop") 269 | // Prepare required info fro transitions. 270 | let finalFrame = destView.frame 271 | let originalFrame = originView.frame 272 | let fromSnapshot = originView.snapshotView(afterScreenUpdates: false) 273 | let toSnapshot = destView.snapshot() 274 | 275 | // Setup initial state of the snapshots and other views. 276 | fromSnapshot?.alpha = 1.0 277 | toSnapshot.alpha = 0.0 278 | fromVC.view.alpha = 1.0 279 | toVC.view.alpha = 1.0 280 | fromSnapshot?.frame = originalFrame 281 | toSnapshot.frame = originalFrame 282 | 283 | originView.isHidden = true 284 | destView.isHidden = true 285 | let fromWholeSnapshot = fromVC.view.snapshot() 286 | 287 | // Add views to transition's container view. 288 | toVC.view.frame = toVC.view.frame.offsetBy(dx: -(toVC.view.frame.width / 3.0), dy: 0) 289 | containerView.addSubview(toVC.view) 290 | containerView.addSubview(fromWholeSnapshot) 291 | containerView.addSubview(fromSnapshot!) 292 | containerView.addSubview(toSnapshot) 293 | 294 | let duration = transitionDuration(using: transitionContext) 295 | 296 | // Transition's animation will be handled using keyframe. 297 | UIView.animateKeyframes(withDuration: duration, delay: 0, options: [.calculationModeCubicPaced], animations: { 298 | 299 | // The move transition. 300 | UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1, animations: { 301 | fromSnapshot?.frame = finalFrame 302 | toSnapshot.frame = finalFrame 303 | fromWholeSnapshot.frame = fromWholeSnapshot.frame.offsetBy(dx: fromWholeSnapshot.frame.width, dy: 0) 304 | toVC.view.frame.origin.x = 0 305 | }) 306 | 307 | // Fade animation from source to destination view. 308 | UIView.addKeyframe(withRelativeStartTime: 1/2, relativeDuration: 1/2, animations: { 309 | fromSnapshot?.alpha = 0.0 310 | toSnapshot.alpha = 1.0 311 | fromVC.view.alpha = 0.0 312 | }) 313 | 314 | }) { _ in 315 | 316 | // Wrap up final state of the transitions. 317 | destView.layoutIfNeeded() 318 | 319 | destView.isHidden = false 320 | originView.isHidden = false 321 | 322 | fromSnapshot?.removeFromSuperview() 323 | toSnapshot.removeFromSuperview() 324 | fromWholeSnapshot.removeFromSuperview() 325 | 326 | transitionContext.completeTransition(!transitionContext.transitionWasCancelled) 327 | } 328 | 329 | } 330 | 331 | } 332 | 333 | /// Handles Interactive dismissal of Modally presented controllers. 334 | public class EasyInteractiveAnimationController: UIPercentDrivenInteractiveTransition { 335 | 336 | /// Determines whether this instance is interactively dismissing a controller. 337 | var isInteracting = false 338 | /// The amount of pixels user has to pan in order to dismiss the controller. (more than half would be conidered good enough) 339 | var panDistance: CGFloat = 200 340 | /// Determines whether the transition must be finalized (If not cancelled or left before the good enough point in interaction.) 341 | private var shouldFinish = false 342 | /// The view controller to add the interactive dismissal on. 343 | private weak var targetController: UIViewController! 344 | 345 | /** 346 | Applies interactive dismissal to a view controller. uses the view's parameter for gesture recognizer. 347 | 348 | - parameter controller: the controller to add interactive dismissal to. 349 | */ 350 | public func attach(to controller: UIViewController) { 351 | targetController = controller 352 | prepareGesture(for: controller.view) 353 | } 354 | 355 | /** 356 | Prepares the required gestures to handle percent driven interactive transitions. 357 | 358 | - parameter view: The view to add the gesture to. 359 | - Currently, we are using pan gesture to handle the touch events. 360 | */ 361 | private func prepareGesture(for view: UIView) { 362 | let gesture = UIPanGestureRecognizer(target: self, action: #selector(handle(_:))) 363 | view.addGestureRecognizer(gesture) 364 | } 365 | 366 | /** 367 | Handles the gesture's state change to update the transition's progress. 368 | 369 | - parameter gesture: The gesture events happened on. 370 | */ 371 | @objc private func handle(_ gesture: UIPanGestureRecognizer) { 372 | 373 | guard let superView = gesture.view?.superview else { 374 | print("Gesture's not been correctly setup") 375 | return 376 | } 377 | 378 | guard panDistance != 0 else { 379 | print("panLength cannot be 0!") 380 | return 381 | } 382 | 383 | let translation = gesture.translation(in: superView) 384 | var progress: CGFloat = distance(of: translation) / panDistance 385 | progress = min(max(progress, 0.0), 1.0) 386 | 387 | switch gesture.state { 388 | 389 | case .began: 390 | isInteracting = true 391 | targetController.dismiss(animated: true, completion: nil) 392 | case .changed: 393 | shouldFinish = progress > 0.5 394 | update(progress) 395 | case .ended: 396 | 397 | isInteracting = false 398 | if !shouldFinish { 399 | cancel() 400 | } else { 401 | finish() 402 | } 403 | case .cancelled: 404 | isInteracting = false 405 | cancel() 406 | default: 407 | print("Gesture state invalid!") 408 | return 409 | } 410 | 411 | } 412 | 413 | } 414 | 415 | internal func distance(of translation: CGPoint) -> CGFloat { 416 | return hypot(translation.x, translation.y) 417 | } 418 | 419 | // A handy extension to allow snapshotting views. Because UIView's snapshot method messes up auto-layout. 420 | internal extension UIView { 421 | func snapshot() -> UIImageView { 422 | UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0.0) 423 | layer.render(in: UIGraphicsGetCurrentContext()!) 424 | let img = UIGraphicsGetImageFromCurrentImageContext() 425 | 426 | return UIImageView(image: img) 427 | 428 | } 429 | } 430 | 431 | --------------------------------------------------------------------------------