├── .gitignore
├── .swift-version
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE.md
├── Motion.podspec
├── Motion.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── Motion.xcscmblueprint
└── xcshareddata
│ └── xcschemes
│ └── Motion.xcscheme
├── Package.swift
├── README.md
└── Sources
├── Animator
├── MotionAnimator.swift
├── MotionAnimatorViewContext.swift
├── MotionCoreAnimationViewContext.swift
├── MotionCoreAnimator.swift
├── MotionViewPropertyViewContext.swift
└── MotionViewTransitionAnimator.swift
├── Application.swift
├── Extensions
├── Motion+Array.swift
├── Motion+CALayer.swift
├── Motion+CAMediaTimingFunction.swift
├── Motion+CG.swift
├── Motion+Obj-C.swift
├── Motion+UIKit.swift
├── Motion+UIView.swift
└── Motion+UIViewController.swift
├── Info.plist
├── LICENSE
├── Motion.h
├── MotionAnimation.swift
├── MotionAnimationState.swift
├── MotionCAAnimation.swift
├── MotionContext.swift
├── MotionCoordinateSpace.swift
├── MotionModifier.swift
├── MotionPlugin.swift
├── MotionSnapshotType.swift
├── MotionTargetState.swift
├── MotionTransitionObserver.swift
├── MotionViewOrderStrategy.swift
├── MotionViewTransition.swift
├── Preprocessors
├── CascadePreprocessor.swift
├── ConditionalPreprocessor.swift
├── IgnoreSubviewModifiersPreprocessor.swift
├── MatchPreprocessor.swift
├── MotionCorePreprocessor.swift
├── MotionPreprocessor.swift
├── SourcePreprocessor.swift
└── TransitionPreprocessor.swift
└── Transition
├── MotionProgressRunner.swift
├── MotionTransition+Animate.swift
├── MotionTransition+Complete.swift
├── MotionTransition+CustomTransition.swift
├── MotionTransition+Interactive.swift
├── MotionTransition+Start.swift
├── MotionTransition+UINavigationControllerDelegate.swift
├── MotionTransition+UITabBarControllerDelegate.swift
├── MotionTransition+UIViewControllerTransitioningDelegate.swift
└── MotionTransition.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | #OS X
2 | .DS_Store
3 |
4 | # Xcode
5 | build/*
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | xcuserdata
15 | profile
16 | *.moved-aside
17 | *.playground
18 | *.framework
19 | DerivedData
20 | Index
21 | Build
22 |
23 |
--------------------------------------------------------------------------------
/.swift-version:
--------------------------------------------------------------------------------
1 | 5.0
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 3.1.3
2 |
3 | - [pr-76](https://github.com/CosmicMind/Motion/pull/76): Added missing module for Swift Package Manager.
4 | * Updated Xcode 11 settings.
5 |
6 | ## 3.1.2
7 |
8 | * Updated layout version in Xcode project.
9 |
10 | ## 3.1.1
11 |
12 | * Added installation instructions to README.
13 |
14 | ## 3.1.0
15 |
16 | - Updated to swift 4.2.
17 | - [pr-45](https://github.com/CosmicMind/Motion/pull/45): Fixed issue-44, view is hidden below status bar during call.
18 | - [issue-44](https://github.com/CosmicMind/Motion/issues/44): View is hidden below status bar when a call is active.
19 | - [pr-50](https://github.com/CosmicMind/Motion/pull/50): Allow framework to be linked from extensions.
20 | - [pr-53](https://github.com/CosmicMind/Motion/pull/53): Bug fixes and `MotionViewTransition`.
21 | - Made `MotionTransition.{start|update|finish|cancel}` methods public.
22 | - Changed to updating model before firing animations for `CALayer` animations.
23 | - Added reverting `XXForNextTransition` options back to default.
24 | - Fixed issue when interactive transition was cancelled it was calling finish instead of cancel.
25 | - Fixed appearance transition callbacks for transition cancellation.
26 | - Fixed resume time was calculated incorrectly.
27 | - Fixed ~0.001 seconds precision issue by storing current time.
28 | - Added `MotionViewTransition` to make transitions of views possible.
29 | - [pr-54](https://github.com/CosmicMind/Motion/pull/54): Added setCompletionCallbackForNextTransition.
30 | - [pr-55](https://github.com/CosmicMind/Motion/pull/55): Fixed UITabBarController had userinteraction enabled during transition.
31 |
32 | ## 1.5.0
33 |
34 | * Updated for Swift 4.2.
35 |
36 | ## 1.4.3
37 |
38 | * [pr-42](https://github.com/CosmicMind/Motion/pull/42): Fixed unbalanced calls to begin/end appearance transitions.
39 | * [issue-29](https://github.com/CosmicMind/Motion/issues/29): Unbalanced calls to begin/end appearance.
40 |
41 | ## 1.4.2
42 |
43 | * [pr-40](https://github.com/CosmicMind/Motion/pull/40): Fixed delegation issue, where UINavigationController and UITabBarController were not correctly calling their delegate methods.
44 |
45 | ## 1.4.1
46 |
47 | * Minor cleanup.
48 |
49 | ## 1.4.0
50 |
51 | * Updated for Xcode 9.3.
52 |
53 | ## 1.3.5
54 |
55 | * [issue-26](https://github.com/CosmicMind/Motion/issues/26): Fixed typo.
56 |
57 | ## 1.3.4
58 |
59 | * [issue-1022](https://github.com/CosmicMind/Material/issues/1022): Fixed alpha issue, where alpha was being set to 1 and not respecting the initial set alpha value.
60 |
61 | ## 1.3.3
62 |
63 | * [issue-24](https://github.com/CosmicMind/Motion/issues/24): Fixed regression where view lifecycle functions were not being called.
64 |
65 | ## 1.3.2
66 |
67 | * Fixed unbalanced calls in Motion transitions.
68 |
69 | ## 1.3.1
70 |
71 | * Updated isMotionEnabled check, as it was determined incorrectly.
72 |
73 | ## 1.3.0
74 |
75 | * Reworked Motion internals.
76 |
77 | ## 1.2.5
78 |
79 | * `UIViewController.motionModalTransitionType` renamed to `UIViewController.motionTransitionType`.
80 | * Updated logic steps for `TabsController.motionTransitionType` and child view controller `motionTransitionType` values.
81 |
82 | ## 1.2.4
83 |
84 | * Added begin / end transition methods for from / to view controllers.
85 |
86 | ## 1.2.3
87 |
88 | * Replaced DispatchQueue.main.async calls to Motion.async.
89 |
90 | ## 1.2.2
91 |
92 | * Updated Motion for iOS 11, where snapshot would no longer include a container view.
93 |
94 | ## 1.2.1
95 |
96 | * Submodule access rights update for [Material](https://github.com/CosmicMind/Material).
97 |
98 | ## 1.2.0
99 |
100 | * Updated to `Swift 4`.
101 | * Fixed a couple memory leaks.
102 |
103 | ## 1.1.2
104 |
105 | * Minor internal updates.
106 |
107 | ## 1.1.1
108 |
109 | * Added Motion logo to README.
110 |
111 | ## 1.1.0
112 |
113 | * [issue-5](https://github.com/CosmicMind/Motion/issues/5): Added the ability to add custom timing functions.
114 | * [issue-4](https://github.com/CosmicMind/Motion/issues/4): Fixed an issue where a white flash occurred when pushing/popping a view controller.
115 | * [issue-8](https://github.com/CosmicMind/Motion/issues/8): Added the ability to add animation immediately.
116 | * [issue-6](https://github.com/CosmicMind/Motion/issues/6): Added the ability to animate views that are not paired.
117 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | This document contains information and guidelines about contributing to this project.
4 | Please read it before you start participating.
5 |
6 | **Topics**
7 |
8 | * [Pull Request Submissions](#pull-request-submissions)
9 | * [Asking Questions](#asking-questions)
10 | * [Reporting Security Issues](#reporting-security-issues)
11 | * [Reporting Issues](#reporting-other-issues)
12 | * [Developers Certificate of Origin](#developers-certificate-of-origin)
13 | * [Code of Conduct](#code-of-conduct)
14 |
15 |
16 | ## Pull Request Submissions.
17 |
18 | All pull requests should be made to the development branch. Details should be available describing your fix. Before submitting a pull request, please confirm that merge issues are resolved.
19 |
20 |
21 | ## Asking Questions
22 |
23 | We don't use GitHub as a support forum.
24 | For any usage questions that are not specific to the project itself,
25 | please ask on [Stack Overflow](http://stackoverflow.com/questions/tagged/cosmicmind) instead.
26 | By doing so, you'll be more likely to quickly solve your problem,
27 | and you'll allow anyone else with the same question to find the answer.
28 | This also allows maintainers to focus on improving the project for others.
29 |
30 |
31 | ## Reporting Security Issues
32 |
33 | CosmicMind takes security seriously.
34 | If you discover a security issue, please bring it to our attention right away!
35 |
36 | Please **DO NOT** file a public issue,
37 | instead send your report privately to .
38 | This will help ensure that any vulnerabilities that _are_ found
39 | can be [disclosed responsibly](http://en.wikipedia.org/wiki/Responsible_disclosure)
40 | to any affected parties.
41 |
42 |
43 | ## Reporting Other Issues
44 |
45 | A great way to contribute to the project
46 | is to send a detailed issue when you encounter an problem.
47 | We always appreciate a well-written, thorough bug report.
48 |
49 | Check that the project issues database
50 | doesn't already include that problem or suggestion before submitting an issue.
51 | If you find a match, add a quick "+1" or "I have this problem too."
52 | Doing this helps prioritize the most common problems and requests.
53 |
54 | When reporting issues, please include the following:
55 |
56 | * The version of Xcode you're using
57 | * The version of iOS you're targeting
58 | * The full output of any stack trace or compiler error
59 | * A code snippet that reproduces the described behavior, if applicable
60 | * Any other details that would be useful in understanding the problem
61 |
62 | This information will help us review and fix your issue faster.
63 |
64 |
65 | ## Developer's Certificate of Origin 1.1
66 |
67 | By making a contribution to this project, I certify that:
68 |
69 | - (a) The contribution was created in whole or in part by me and I
70 | have the right to submit it under the open source license
71 | indicated in the file; or
72 |
73 | - (b) The contribution is based upon previous work that, to the best
74 | of my knowledge, is covered under an appropriate open source
75 | license and I have the right under that license to submit that
76 | work with modifications, whether created in whole or in part
77 | by me, under the same open source license (unless I am
78 | permitted to submit under a different license), as indicated
79 | in the file; or
80 |
81 | - (c) The contribution was provided directly to me by some other
82 | person who certified (a), (b) or (c) and I have not modified
83 | it.
84 |
85 | - (d) I understand and agree that this project and the contribution
86 | are public and that a record of the contribution (including all
87 | personal information I submit with it, including my sign-off) is
88 | maintained indefinitely and may be redistributed consistent with
89 | this project or the open source license(s) involved.
90 |
91 | ---
92 |
93 | *Some of the ideas and wording for the statements above were based on work by the [Alamofire](https://github.com/Alamofire/Alamofire/blob/master/CONTRIBUTING.md), [Docker](https://github.com/docker/docker/blob/master/CONTRIBUTING.md) and [Linux](http://elinux.org/Developer_Certificate_Of_Origin) communities. We commend them for their efforts to facilitate collaboration in their projects.*
94 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (C) 2019, CosmicMind, Inc. .
4 | All rights reserved.
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/Motion.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'Motion'
3 | s.version = '3.1.3'
4 | s.swift_version = '5.0'
5 | s.license = 'MIT'
6 | s.summary = 'A library used to create beautiful animations and transitions for iOS.'
7 | s.homepage = 'http://cosmicmind.com'
8 | s.social_media_url = 'https://www.facebook.com/cosmicmindcom'
9 | s.authors = { 'CosmicMind, Inc.' => 'support@cosmicmind.com' }
10 | s.source = { :git => 'https://github.com/CosmicMind/Motion.git', :tag => s.version }
11 | s.platform = :ios, '8.0'
12 |
13 | s.default_subspec = 'Core'
14 |
15 | s.subspec 'Core' do |s|
16 | s.ios.deployment_target = '8.0'
17 | s.ios.source_files = 'Sources/**/*.swift'
18 | s.requires_arc = true
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/Motion.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Motion.xcodeproj/project.xcworkspace/xcshareddata/Motion.xcscmblueprint:
--------------------------------------------------------------------------------
1 | {
2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "0F3E254D46E5A5B90D1542854F510B7D145A9F31",
3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
4 |
5 | },
6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
7 | "0F3E254D46E5A5B90D1542854F510B7D145A9F31" : 9223372036854775807,
8 | "9CF35BC641478FBD765F7442D4034ED362261E0A" : 9223372036854775807
9 | },
10 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "6AC70DB0-50D8-45F1-A69E-812AE5A97A0A",
11 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
12 | "0F3E254D46E5A5B90D1542854F510B7D145A9F31" : "Motion\/",
13 | "9CF35BC641478FBD765F7442D4034ED362261E0A" : ".."
14 | },
15 | "DVTSourceControlWorkspaceBlueprintNameKey" : "Motion",
16 | "DVTSourceControlWorkspaceBlueprintVersion" : 204,
17 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "Motion.xcodeproj",
18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
19 | {
20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/CosmicMind\/Motion.git",
21 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "0F3E254D46E5A5B90D1542854F510B7D145A9F31"
23 | },
24 | {
25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/CosmicMind\/Material.git",
26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "9CF35BC641478FBD765F7442D4034ED362261E0A"
28 | }
29 | ]
30 | }
--------------------------------------------------------------------------------
/Motion.xcodeproj/xcshareddata/xcschemes/Motion.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
52 |
53 |
59 |
60 |
66 |
67 |
68 |
69 |
71 |
72 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:4.2
2 | import PackageDescription
3 |
4 | let package = Package(
5 | name: "Motion",
6 | // platforms: [.iOS("8.0")],
7 | products: [
8 | .library(name: "Motion", targets: ["Motion"])
9 | ],
10 | targets: [
11 | .target(
12 | name: "Motion",
13 | path: "Sources"
14 | )
15 | ]
16 | )
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Motion
4 |
5 | Welcome to **Motion,** a library used to create beautiful animations and transitions for views, layers, and view controllers.
6 |
7 | ## Photos Sample
8 |
9 | Take a look at a sample [Photos](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/Photos) project to get started.
10 |
11 | 
12 |
13 | * [Photos Sample](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/Photos)
14 |
15 | ### Who is Motion for?
16 |
17 | Motion is designed for beginner to expert developers. For beginners, you will be exposed to very powerful APIs that would take time and experience to develop on your own, and experts will appreciate the time saved by using Motion.
18 |
19 | ### What you will learn
20 |
21 | You will learn how to use Motion with a general introduction to fundamental concepts and easy to use code snippets.
22 |
23 | # Transitions
24 |
25 | Motion transitions a source view to a destination view using a linking identifier property named `motionIdentifier`.
26 |
27 | | Match | Translate | Rotate | Arc | Scale |
28 | |:---:|:---:|:---:|:---:|:---:|
29 | |  |  |  |  |  |
30 |
31 | ### Example Usage
32 |
33 | An example of transitioning from one view controller to another with transitions:
34 |
35 | #### View Controller 1
36 |
37 | ```swift
38 | greyView.motionIdentifier = "foo"
39 | orangeView.motionIdentifier = "bar"
40 | ```
41 |
42 | #### View Controller 2
43 |
44 | ```swift
45 | isMotionEnabled = true
46 | greyView.motionIdentifier = "foo"
47 | orangeView.motionIdentifier = "bar"
48 | orangeView.transition(.translate(x: -100))
49 | ```
50 |
51 | The above code snippet tells the source views in `view controller 1` to link to the destination views in `view controller 2` using the `motionIdentifier`. Animations may be added to views during a transition using the **transition** method. The *transition* method accepts MotionTransition structs that configure the view's animation.
52 |
53 | * [MotionTransition API](https://cosmicmind.gitbooks.io/motion/content/motion_transition_api.html)
54 | * [Code Samples](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/TransitionsWithIdentifier)
55 |
56 | ## UINavigationController, UITabBarController, and UIViewController Transitions
57 |
58 | Motion offers default transitions that may be used by UINavigationControllers, UITabBarControllers, and presenting UIViewControllers.
59 |
60 | | Push | Slide | ZoomSlide | Cover | Page | Fade | Zoom |
61 | |:---:|:---:|:---:|:---:|:---:|:---:|:---:|
62 | |  | |  |  |  |  | |
63 |
64 | ### Example Usage
65 |
66 | An example of transitioning from one view controller to another using a UINavigationController with a zoom transition:
67 |
68 | #### UINavigationController
69 |
70 | ```swift
71 | class AppNavigationController: UINavigationController {
72 | open override func viewDidLoad() {
73 | super.viewDidLoad()
74 | isMotionEnabled = true
75 | motionNavigationTransitionType = .zoom
76 | }
77 | }
78 | ```
79 |
80 | To add an automatic reverse transition, use `autoReverse`.
81 |
82 | ```swift
83 | motionNavigationTransitionType = .autoReverse(presenting: .zoom)
84 | ```
85 |
86 | * [Code Samples](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/Transitions)
87 |
88 | # Animations
89 |
90 | Motion provides the building blocks necessary to create stunning animations without much effort. Motion's animation API will make maintenance a breeze and changes even easier. To create an animation, use the **animate** method of a view or layer and pass in a list of MotionAnimation structs. MotionAnimation structs are configurable values that describe how to animate a property or group of properties.
91 |
92 | | Background Color | Corner Radius | Fade | Rotate | Size | Spring |
93 | |:---:|:---:|:---:|:---:|:---:|:---:|
94 | |  |  |  |  |  |  |
95 |
96 | | Border Color & Border Width | Depth | Position | Scale | Spin | Translate |
97 | |:---:|:---:|:---:|:---:|:---:|:---:|
98 | | |  |  |  |  |  |
99 |
100 | ### Example Usage
101 |
102 | ```swift
103 | let box = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
104 | box.backgroundColor = .blue
105 |
106 | box.animate(.background(color: .red), .rotate(180), .delay(1))
107 | ```
108 |
109 | In the above code example, a box view is created with a width of 100, height of 100, and an initial background color of blue. Following the general creation of the view, the _Motion animate_ method is passed _MotionAnimation structs_ that tell the view to animate to a red background color and rotate 180 degrees after a delay of 1 second. That's pretty much the general idea of creating animations.
110 |
111 | * [MotionAnimation API](https://cosmicmind.gitbooks.io/motion/content/motion_animation_api.html)
112 | * [Code Samples](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/Animations)
113 |
114 | ## Requirements
115 |
116 | * iOS 8.0+
117 | * Xcode 8.0+
118 |
119 | ## Communication
120 |
121 | - If you **need help**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/cosmicmind). (Tag 'cosmicmind')
122 | - If you'd like to **ask a general question**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/cosmicmind).
123 | - If you **found a bug**, _and can provide steps to reliably reproduce it_, open an issue.
124 | - If you **have a feature request**, open an issue.
125 | - If you **want to contribute**, submit a pull request.
126 |
127 | ## Installation
128 |
129 | > **Embedded frameworks require a minimum deployment target of iOS 8.**
130 | > - [Download Motion](https://github.com/CosmicMind/Motion/archive/master.zip)
131 |
132 | ## CocoaPods
133 |
134 | [CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command:
135 |
136 | ```bash
137 | $ gem install cocoapods
138 | ```
139 |
140 | To integrate Motion's core features into your Xcode project using CocoaPods, specify it in your `Podfile`:
141 |
142 | ```ruby
143 | source 'https://github.com/CocoaPods/Specs.git'
144 | platform :ios, '8.0'
145 | use_frameworks!
146 |
147 | pod 'Motion', '~> 3.1.0'
148 | ```
149 |
150 | Then, run the following command:
151 |
152 | ```bash
153 | $ pod install
154 | ```
155 |
156 | ## Carthage
157 |
158 | Carthage is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.
159 |
160 | You can install Carthage with Homebrew using the following command:
161 |
162 | ```bash
163 | $ brew update
164 | $ brew install carthage
165 | ```
166 | To integrate Motion into your Xcode project using Carthage, specify it in your Cartfile:
167 |
168 | ```bash
169 | github "CosmicMind/Motion"
170 | ```
171 |
172 | Run `carthage update` to build the framework and drag the built `Motion.framework` into your Xcode project.
173 |
174 | ## Change Log
175 |
176 | Motion is a growing project and will encounter changes throughout its development. It is recommended that the [Change Log](https://github.com/CosmicMind/Motion/blob/master/CHANGELOG.md) be reviewed prior to updating versions.
177 |
178 | ## License
179 |
180 | The MIT License (MIT)
181 |
182 | Copyright (C) 2019, CosmicMind, Inc. .
183 | All rights reserved.
184 |
185 | Permission is hereby granted, free of charge, to any person obtaining a copy
186 | of this software and associated documentation files (the "Software"), to deal
187 | in the Software without restriction, including without limitation the rights
188 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
189 | copies of the Software, and to permit persons to whom the Software is
190 | furnished to do so, subject to the following conditions:
191 |
192 | The above copyright notice and this permission notice shall be included in
193 | all copies or substantial portions of the Software.
194 |
195 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
196 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
197 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
198 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
199 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
200 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
201 | THE SOFTWARE.
202 |
--------------------------------------------------------------------------------
/Sources/Animator/MotionAnimator.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | public protocol MotionAnimator: class {
29 | /// A reference to Motion.
30 | var motion: MotionTransition! { get set }
31 |
32 | /// Cleans the contexts.
33 | func clean()
34 |
35 | /**
36 | A function that determines if a view can be animated.
37 | - Parameter view: A UIView.
38 | - Parameter isAppearing: A boolean that determines whether the
39 | view is appearing.
40 | */
41 | func canAnimate(view: UIView, isAppearing: Bool) -> Bool
42 |
43 | /**
44 | Animates the fromViews to the toViews.
45 | - Parameter fromViews: An Array of UIViews.
46 | - Parameter toViews: An Array of UIViews.
47 | - Returns: A TimeInterval.
48 | */
49 | func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval
50 |
51 | /**
52 | Moves the view's animation to the given elapsed time.
53 | - Parameter to progress: A TimeInterval.
54 | */
55 | func seek(to progress: TimeInterval)
56 |
57 | /**
58 | Resumes the animation with a given elapsed time and
59 | optional reversed boolean.
60 | - Parameter at progress: A TimeInterval.
61 | - Parameter isReversed: A boolean to reverse the animation
62 | or not.
63 | */
64 | func resume(at progress: TimeInterval, isReversed: Bool) -> TimeInterval
65 |
66 | /**
67 | Applies the given state to the given view.
68 | - Parameter state: A MotionModifier.
69 | - Parameter to view: A UIView.
70 | */
71 | func apply(state: MotionTargetState, to view: UIView)
72 | }
73 |
--------------------------------------------------------------------------------
/Sources/Animator/MotionAnimatorViewContext.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | internal class MotionAnimatorViewContext: NSObject {
29 | /// An optional reference to a MotionAnimator.
30 | var animator: MotionAnimator?
31 |
32 | /// A reference to the snapshot UIView.
33 | var snapshot: UIView
34 |
35 | /// The animation target state.
36 | var targetState: MotionTargetState
37 |
38 | /// A boolean indicating if the view is appearing.
39 | var isAppearing: Bool
40 |
41 | /// Animation duration time.
42 | var duration: TimeInterval = 0
43 |
44 | /// A container view for the transition.
45 | var container: UIView? {
46 | return animator?.motion.context.container
47 | }
48 |
49 | /**
50 | An initializer.
51 | - Parameter animator: A MotionAnimator.
52 | - Parameter snapshot: A UIView.
53 | - Parameter targetState: A MotionModifier.
54 | - Parameter isAppearing: A Boolean.
55 | */
56 | required init(animator: MotionAnimator, snapshot: UIView, targetState: MotionTargetState, isAppearing: Bool) {
57 | self.animator = animator
58 | self.snapshot = snapshot
59 | self.targetState = targetState
60 | self.isAppearing = isAppearing
61 | }
62 |
63 | /// Cleans the context.
64 | func clean() {
65 | animator = nil
66 | }
67 |
68 | /**
69 | A class function that determines if a view can be animated
70 | to a given state.
71 | - Parameter view: A UIView.
72 | - Parameter state: A MotionModifier.
73 | - Parameter isAppearing: A boolean that determines whether the
74 | view is appearing.
75 | */
76 | class func canAnimate(view: UIView, state: MotionTargetState, isAppearing: Bool) -> Bool {
77 | return false
78 | }
79 |
80 | /**
81 | Resumes the animation with a given elapsed time and
82 | optional reversed boolean.
83 | - Parameter at progress: A TimeInterval.
84 | - Parameter isReversed: A boolean to reverse the animation
85 | or not.
86 | - Returns: A TimeInterval.
87 | */
88 | @discardableResult
89 | func resume(at progress: TimeInterval, isReversed: Bool) -> TimeInterval {
90 | return 0
91 | }
92 |
93 | /**
94 | Moves the animation to the given elapsed time.
95 | - Parameter to progress: A TimeInterval.
96 | */
97 | func seek(to progress: TimeInterval) {}
98 |
99 | /**
100 | Applies the given state to the target state.
101 | - Parameter state: A MotionModifier.
102 | */
103 | func apply(state: MotionTargetState) {}
104 |
105 | /**
106 | Starts the animations with an appearing boolean flag.
107 | - Parameter isAppearing: A boolean value whether the view
108 | is appearing or not.
109 | */
110 | @discardableResult
111 | func startAnimations() -> TimeInterval {
112 | return 0
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/Sources/Animator/MotionCoreAnimator.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | internal class MotionCoreAnimator: MotionAnimator {
29 | /**
30 | Backing field for storing CACurrentMediaTime to ensure all
31 | animations begin at the exact same time.
32 |
33 | Should be invalidated using invalidateCurrentTime method
34 | after firing all animations.
35 | */
36 | private var storedCurrentTime: TimeInterval?
37 |
38 | /// Current time for the animator.
39 | var currentTime: TimeInterval {
40 | if nil == storedCurrentTime {
41 | storedCurrentTime = CACurrentMediaTime()
42 | }
43 |
44 | return storedCurrentTime!
45 | }
46 |
47 | /// Invalidates stored current time.
48 | func invalidateCurrentTime() {
49 | storedCurrentTime = nil
50 | }
51 |
52 | weak public var motion: MotionTransition!
53 |
54 | /// A reference to the MotionContext.
55 | public var context: MotionContext! {
56 | return motion.context
57 | }
58 |
59 | /// An index of views to their corresponding animator context.
60 | var viewToContexts = [UIView: T]()
61 |
62 | /// Cleans the contexts.
63 | func clean() {
64 | for v in viewToContexts.values {
65 | v.clean()
66 | }
67 |
68 | viewToContexts.removeAll()
69 | invalidateCurrentTime()
70 | }
71 |
72 | /**
73 | A function that determines if a view can be animated.
74 | - Parameter view: A UIView.
75 | - Parameter isAppearing: A boolean that determines whether the
76 | view is appearing.
77 | */
78 | func canAnimate(view: UIView, isAppearing: Bool) -> Bool {
79 | guard let state = targetState(for: view) else {
80 | return false
81 | }
82 |
83 | return T.canAnimate(view: view, state: state, isAppearing: isAppearing)
84 | }
85 |
86 | /**
87 | Animates the fromViews to the toViews.
88 | - Parameter fromViews: An Array of UIViews.
89 | - Parameter toViews: An Array of UIViews.
90 | - Returns: A TimeInterval.
91 | */
92 | func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval {
93 | var d: TimeInterval = 0
94 |
95 | for v in fromViews {
96 | createViewContext(view: v, isAppearing: false)
97 | }
98 |
99 | for v in toViews {
100 | createViewContext(view: v, isAppearing: true)
101 | }
102 |
103 | for v in viewToContexts.values {
104 | if let duration = v.targetState.duration, .infinity != duration {
105 | v.duration = duration
106 | d = max(d, duration)
107 |
108 | } else {
109 | let duration = v.snapshot.optimizedDuration(targetState: v.targetState)
110 |
111 | if nil == v.targetState.duration {
112 | v.duration = duration
113 | }
114 |
115 | d = max(d, duration)
116 | }
117 | }
118 |
119 | for v in viewToContexts.values {
120 | if .infinity == v.targetState.duration {
121 | v.duration = d
122 | }
123 |
124 | d = max(d, v.startAnimations())
125 | }
126 |
127 | invalidateCurrentTime()
128 |
129 | return d
130 | }
131 |
132 | /**
133 | Moves the view's animation to the given elapsed time.
134 | - Parameter to progress: A TimeInterval.
135 | */
136 | func seek(to progress: TimeInterval) {
137 | for v in viewToContexts.values {
138 | v.seek(to: progress)
139 | }
140 |
141 | invalidateCurrentTime()
142 | }
143 |
144 | /**
145 | Resumes the animation with a given elapsed time and
146 | optional reversed boolean.
147 | - Parameter at progress: A TimeInterval.
148 | - Parameter isReversed: A boolean to reverse the animation
149 | or not.
150 | */
151 | func resume(at progress: TimeInterval, isReversed: Bool) -> TimeInterval {
152 | var duration: TimeInterval = 0
153 |
154 | for (_, v) in viewToContexts {
155 | duration = max(duration, v.resume(at: progress, isReversed: isReversed))
156 | }
157 |
158 | invalidateCurrentTime()
159 | return duration
160 | }
161 |
162 | /**
163 | Applies the given state to the given view.
164 | - Parameter state: A MotionModifier.
165 | - Parameter to view: A UIView.
166 | */
167 | func apply(state: MotionTargetState, to view: UIView) {
168 | guard let v = viewToContexts[view] else {
169 | return
170 | }
171 |
172 | v.apply(state: state)
173 |
174 | invalidateCurrentTime()
175 | }
176 |
177 | /**
178 | Returns MotionTargetState for the given view.
179 | - Parameter for view: A UIView.
180 | - Returns: A MotionTargetState.
181 | */
182 | func targetState(for view: UIView) -> MotionTargetState? {
183 | return context[view]
184 | }
185 |
186 | /**
187 | Returns snapshot view for the given view.
188 | - Parameter for view: A UIView.
189 | - Returns: A snapshot UIView.
190 | */
191 | func snapshotView(for view: UIView) -> UIView {
192 | return context.snapshotView(for: view)
193 | }
194 | }
195 |
196 | fileprivate extension MotionCoreAnimator {
197 | /**
198 | Creates a view context for a given view.
199 | - Parameter view: A UIView.
200 | - Parameter isAppearing: A boolean that determines whether the
201 | view is appearing.
202 | */
203 | func createViewContext(view: UIView, isAppearing: Bool) {
204 | viewToContexts[view] = T(animator: self, snapshot: snapshotView(for: view), targetState: targetState(for: view)!, isAppearing: isAppearing)
205 | }
206 | }
207 |
208 |
209 |
--------------------------------------------------------------------------------
/Sources/Animator/MotionViewPropertyViewContext.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | @available(iOS 10, tvOS 10, *)
29 | internal class MotionViewPropertyViewContext: MotionAnimatorViewContext {
30 | /// A reference to the UIViewPropertyAnimator.
31 | fileprivate var viewPropertyAnimator: UIViewPropertyAnimator!
32 |
33 | /// Ending effect.
34 | fileprivate var endEffect: UIVisualEffect?
35 |
36 | /// Starting effect.
37 | fileprivate var startEffect: UIVisualEffect?
38 |
39 | override class func canAnimate(view: UIView, state: MotionTargetState, isAppearing: Bool) -> Bool {
40 | return view is UIVisualEffectView && nil != state.opacity
41 | }
42 |
43 | override func resume(at progress: TimeInterval, isReversed: Bool) -> TimeInterval {
44 | guard let visualEffectView = snapshot as? UIVisualEffectView else {
45 | return 0
46 | }
47 |
48 | if isReversed {
49 | viewPropertyAnimator?.stopAnimation(false)
50 | viewPropertyAnimator?.finishAnimation(at: .current)
51 | viewPropertyAnimator = UIViewPropertyAnimator(duration: duration, curve: .linear) { [weak self] in
52 | guard let `self` = self else {
53 | return
54 | }
55 |
56 | visualEffectView.effect = isReversed ? self.startEffect : self.endEffect
57 | }
58 | }
59 |
60 | viewPropertyAnimator.startAnimation()
61 |
62 | return duration
63 | }
64 |
65 | override func seek(to progress: TimeInterval) {
66 | viewPropertyAnimator?.pauseAnimation()
67 | viewPropertyAnimator?.fractionComplete = CGFloat(progress / duration)
68 | }
69 |
70 | override func clean() {
71 | super.clean()
72 | viewPropertyAnimator?.stopAnimation(false)
73 | viewPropertyAnimator?.finishAnimation(at: .current)
74 | viewPropertyAnimator = nil
75 | }
76 |
77 | override func startAnimations() -> TimeInterval {
78 | guard let visualEffectView = snapshot as? UIVisualEffectView else {
79 | return 0
80 | }
81 |
82 | let appearedEffect = visualEffectView.effect
83 | let disappearedEffect = 0 == targetState.opacity ? nil : visualEffectView.effect
84 |
85 | startEffect = isAppearing ? disappearedEffect : appearedEffect
86 | endEffect = isAppearing ? appearedEffect : disappearedEffect
87 |
88 | visualEffectView.effect = startEffect
89 |
90 | viewPropertyAnimator = UIViewPropertyAnimator(duration: duration, curve: .linear) { [weak self] in
91 | guard let `self` = self else {
92 | return
93 | }
94 |
95 | visualEffectView.effect = self.endEffect
96 | }
97 |
98 | viewPropertyAnimator.startAnimation()
99 |
100 | return duration
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/Sources/Animator/MotionViewTransitionAnimator.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | internal class MotionViewTransitionAnimator: MotionCoreAnimator {
29 | /**
30 | Returns MotionTargetState for the given view.
31 | - Parameter for view: A UIView.
32 | - Returns: A MotionTargetState.
33 | */
34 | override func targetState(for view: UIView) -> MotionTargetState? {
35 | guard let modifiers = view.motionModifiers else {
36 | return nil
37 | }
38 |
39 | return MotionTargetState(modifiers: modifiers)
40 | }
41 |
42 | /**
43 | Returns snapshot view for the given view.
44 | - Parameter for view: A UIView.
45 | - Returns: A snapshot UIView.
46 | */
47 | override func snapshotView(for view: UIView) -> UIView {
48 | return view
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/Application.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | internal class Application {
29 | static var shared: UIApplication {
30 | let sharedSelector = NSSelectorFromString("sharedApplication")
31 | guard UIApplication.responds(to: sharedSelector) else {
32 | fatalError("[Motion: Extensions cannot access Application]")
33 | }
34 |
35 | let shared = UIApplication.perform(sharedSelector)
36 | return shared?.takeUnretainedValue() as! UIApplication
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/Extensions/Motion+Array.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | internal extension Array {
29 | /**
30 | Retrieves the element at the given index if it exists.
31 | - Parameter _ index: An Int.
32 | - Returns: An optional Element value.
33 | */
34 | func get(_ index: Int) -> Element? {
35 | if index < count {
36 | return self[index]
37 | }
38 | return nil
39 | }
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/Sources/Extensions/Motion+CALayer.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | internal extension CALayer {
29 | /// Swizzle the `add(_:forKey:) selector.
30 | static var motionAddedAnimations: [(CALayer, String, CAAnimation)]? = {
31 | let swizzling: (AnyClass, Selector, Selector) -> Void = { forClass, originalSelector, swizzledSelector in
32 | if let originalMethod = class_getInstanceMethod(forClass, originalSelector), let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector) {
33 | method_exchangeImplementations(originalMethod, swizzledMethod)
34 | }
35 | }
36 |
37 | swizzling(CALayer.self, #selector(add(_:forKey:)), #selector(motionAdd(anim:forKey:)))
38 |
39 | return nil
40 | }()
41 |
42 | @objc
43 | dynamic func motionAdd(anim: CAAnimation, forKey: String?) {
44 | if nil == CALayer.motionAddedAnimations {
45 | motionAdd(anim: anim, forKey: forKey)
46 | } else {
47 | let copiedAnim = anim.copy() as! CAAnimation
48 | copiedAnim.delegate = nil // having delegate resulted some weird animation behavior
49 | CALayer.motionAddedAnimations?.append((self, forKey!, copiedAnim))
50 | }
51 | }
52 |
53 | /// Retrieves all currently running animations for the layer.
54 | var animations: [(String, CAAnimation)] {
55 | guard let keys = animationKeys() else {
56 | return []
57 | }
58 |
59 | return keys.map {
60 | return ($0, self.animation(forKey: $0)!.copy() as! CAAnimation)
61 | }
62 | }
63 |
64 | /**
65 | Concats transforms and returns the result.
66 | - Parameters layer: A CALayer.
67 | - Returns: A CATransform3D.
68 | */
69 | func flatTransformTo(layer: CALayer) -> CATransform3D {
70 | var l = layer
71 | var t = l.transform
72 |
73 | while let sl = l.superlayer, self != sl {
74 | t = CATransform3DConcat(sl.transform, t)
75 | l = sl
76 | }
77 |
78 | return t
79 | }
80 |
81 | /// Removes all Motion animations.
82 | func removeAllMotionAnimations() {
83 | guard let keys = animationKeys() else {
84 | return
85 | }
86 |
87 | for animationKey in keys where animationKey.hasPrefix("motion.") {
88 | removeAnimation(forKey: animationKey)
89 | }
90 | }
91 | }
92 |
93 | public extension CALayer {
94 | /**
95 | A function that accepts CAAnimation objects and executes them on the
96 | view's backing layer.
97 | - Parameter animation: A CAAnimation instance.
98 | */
99 | func animate(_ animations: CAAnimation...) {
100 | animate(animations)
101 | }
102 |
103 | /**
104 | A function that accepts CAAnimation objects and executes them on the
105 | view's backing layer.
106 | - Parameter animation: A CAAnimation instance.
107 | */
108 | func animate(_ animations: [CAAnimation]) {
109 | for animation in animations {
110 | if let a = animation as? CABasicAnimation {
111 | a.fromValue = (presentation() ?? self).value(forKeyPath: a.keyPath!)
112 | }
113 |
114 | updateModel(animation)
115 | if let a = animation as? CAPropertyAnimation {
116 | add(a, forKey: a.keyPath!)
117 | } else if let a = animation as? CAAnimationGroup {
118 | add(a, forKey: nil)
119 | } else if let a = animation as? CATransition {
120 | add(a, forKey: kCATransition)
121 | }
122 | }
123 | }
124 |
125 | /**
126 | A function that accepts a list of MotionAnimation values and executes them.
127 | - Parameter animations: A list of MotionAnimation values.
128 | */
129 | func animate(_ animations: MotionAnimation...) {
130 | animate(animations)
131 | }
132 |
133 | /**
134 | A function that accepts an Array of MotionAnimation values and executes them.
135 | - Parameter animations: An Array of MotionAnimation values.
136 | - Parameter completion: An optional completion block.
137 | */
138 | func animate(_ animations: [MotionAnimation], completion: (() -> Void)? = nil) {
139 | startAnimations(animations, completion: completion)
140 | }
141 | }
142 |
143 | fileprivate extension CALayer {
144 | /**
145 | A function that executes an Array of MotionAnimation values.
146 | - Parameter _ animations: An Array of MotionAnimations.
147 | - Parameter completion: An optional completion block.
148 | */
149 | func startAnimations(_ animations: [MotionAnimation], completion: (() -> Void)? = nil) {
150 | let ts = MotionAnimationState(animations: animations)
151 |
152 | Motion.delay(ts.delay) { [weak self,
153 | ts = ts,
154 | completion = completion] in
155 |
156 | guard let `self` = self else {
157 | return
158 | }
159 |
160 | var anims = [CABasicAnimation]()
161 | var duration = 0 == ts.duration ? 0.01 : ts.duration
162 |
163 | if let v = ts.backgroundColor {
164 | let a = MotionCAAnimation.background(color: UIColor(cgColor: v))
165 | a.fromValue = self.backgroundColor
166 | anims.append(a)
167 | }
168 |
169 | if let v = ts.borderColor {
170 | let a = MotionCAAnimation.border(color: UIColor(cgColor: v))
171 | a.fromValue = self.borderColor
172 | anims.append(a)
173 | }
174 |
175 | if let v = ts.borderWidth {
176 | let a = MotionCAAnimation.border(width: v)
177 | a.fromValue = NSNumber(floatLiteral: Double(self.borderWidth))
178 | anims.append(a)
179 | }
180 |
181 | if let v = ts.cornerRadius {
182 | let a = MotionCAAnimation.corner(radius: v)
183 | a.fromValue = NSNumber(floatLiteral: Double(self.cornerRadius))
184 | anims.append(a)
185 | }
186 |
187 | if let v = ts.transform {
188 | let a = MotionCAAnimation.transform(v)
189 | a.fromValue = NSValue(caTransform3D: self.transform)
190 | anims.append(a)
191 | }
192 |
193 | if let v = ts.spin {
194 | var a = MotionCAAnimation.spin(x: v.x)
195 | a.fromValue = NSNumber(floatLiteral: 0)
196 | anims.append(a)
197 |
198 | a = MotionCAAnimation.spin(y: v.y)
199 | a.fromValue = NSNumber(floatLiteral: 0)
200 | anims.append(a)
201 |
202 | a = MotionCAAnimation.spin(z: v.z)
203 | a.fromValue = NSNumber(floatLiteral: 0)
204 | anims.append(a)
205 | }
206 |
207 | if let v = ts.position {
208 | let a = MotionCAAnimation.position(v)
209 | a.fromValue = NSValue(cgPoint: self.position)
210 | anims.append(a)
211 | }
212 |
213 | if let v = ts.opacity {
214 | let a = MotionCAAnimation.fade(v)
215 | a.fromValue = self.value(forKeyPath: MotionAnimationKeyPath.opacity.rawValue) ?? NSNumber(floatLiteral: 1)
216 | anims.append(a)
217 | }
218 |
219 | if let v = ts.zPosition {
220 | let a = MotionCAAnimation.zPosition(v)
221 | a.fromValue = self.value(forKeyPath: MotionAnimationKeyPath.zPosition.rawValue) ?? NSNumber(floatLiteral: 0)
222 | anims.append(a)
223 | }
224 |
225 | if let v = ts.size {
226 | let a = MotionCAAnimation.size(v)
227 | a.fromValue = NSValue(cgSize: self.bounds.size)
228 | anims.append(a)
229 | }
230 |
231 | if let v = ts.shadowPath {
232 | let a = MotionCAAnimation.shadow(path: v)
233 | a.fromValue = self.shadowPath
234 | anims.append(a)
235 | }
236 |
237 | if let v = ts.shadowColor {
238 | let a = MotionCAAnimation.shadow(color: UIColor(cgColor: v))
239 | a.fromValue = self.shadowColor
240 | anims.append(a)
241 | }
242 |
243 | if let v = ts.shadowOffset {
244 | let a = MotionCAAnimation.shadow(offset: v)
245 | a.fromValue = NSValue(cgSize: self.shadowOffset)
246 | anims.append(a)
247 | }
248 |
249 | if let v = ts.shadowOpacity {
250 | let a = MotionCAAnimation.shadow(opacity: v)
251 | a.fromValue = NSNumber(floatLiteral: Double(self.shadowOpacity))
252 | anims.append(a)
253 | }
254 |
255 | if let v = ts.shadowRadius {
256 | let a = MotionCAAnimation.shadow(radius: v)
257 | a.fromValue = NSNumber(floatLiteral: Double(self.shadowRadius))
258 | anims.append(a)
259 | }
260 |
261 | if #available(iOS 9.0, *), let (stiffness, damping) = ts.spring {
262 | for i in 0.. duration {
273 | duration = a.settlingDuration
274 | }
275 | }
276 | }
277 |
278 | let g = Motion.animate(group: anims, timingFunction: ts.timingFunction, duration: duration)
279 | self.animate(g)
280 |
281 | if let v = ts.completion {
282 | Motion.delay(duration, execute: v)
283 | }
284 |
285 | if let v = completion {
286 | Motion.delay(duration, execute: v)
287 | }
288 | }
289 | }
290 | }
291 |
292 | private extension CALayer {
293 | /**
294 | Updates the model with values provided in animation.
295 | - Parameter animation: A CAAnimation.
296 | */
297 | func updateModel(_ animation: CAAnimation) {
298 | if let a = animation as? CABasicAnimation {
299 | setValue(a.toValue, forKeyPath: a.keyPath!)
300 | } else if let a = animation as? CAAnimationGroup {
301 | a.animations?.forEach {
302 | updateModel($0)
303 | }
304 | }
305 | }
306 | }
307 |
--------------------------------------------------------------------------------
/Sources/Extensions/Motion+CAMediaTimingFunction.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | public extension CAMediaTimingFunction {
29 | // Default
30 | static let linear = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
31 | static let easeIn = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
32 | static let easeOut = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
33 | static let easeInOut = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
34 |
35 | // Material
36 | static let standard = CAMediaTimingFunction(controlPoints: 0.4, 0.0, 0.2, 1.0)
37 | static let deceleration = CAMediaTimingFunction(controlPoints: 0.0, 0.0, 0.2, 1)
38 | static let acceleration = CAMediaTimingFunction(controlPoints: 0.4, 0.0, 1, 1)
39 | static let sharp = CAMediaTimingFunction(controlPoints: 0.4, 0.0, 0.6, 1)
40 |
41 | // Easing.net
42 | static let easeOutBack = CAMediaTimingFunction(controlPoints: 0.175, 0.885, 0.32, 1.75)
43 | }
44 |
--------------------------------------------------------------------------------
/Sources/Extensions/Motion+CG.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import MetalKit
27 |
28 | public extension CGSize {
29 | /// THe center point based on width and height.
30 | var center: CGPoint {
31 | return CGPoint(x: width / 2, y: height / 2)
32 | }
33 |
34 | /// Top left point based on the size.
35 | var topLeft: CGPoint {
36 | return .zero
37 | }
38 |
39 | /// Top right point based on the size.
40 | var topRight: CGPoint {
41 | return CGPoint(x: width, y: 0)
42 | }
43 |
44 | /// Bottom left point based on the size.
45 | var bottomLeftPoint: CGPoint {
46 | return CGPoint(x: 0, y: height)
47 | }
48 |
49 | /// Bottom right point based on the size.
50 | var bottomRight: CGPoint {
51 | return CGPoint(x: width, y: height)
52 | }
53 |
54 | /**
55 | Retrieves the size based on a given CGAffineTransform.
56 | - Parameter _ t: A CGAffineTransform.
57 | - Returns: A CGSize.
58 | */
59 | func transform(_ t: CGAffineTransform) -> CGSize {
60 | return applying(t)
61 | }
62 |
63 | /**
64 | Retrieves the size based on a given CATransform3D.
65 | - Parameter _ t: A CGAffineTransform.
66 | - Returns: A CGSize.
67 | */
68 | func transform(_ t: CATransform3D) -> CGSize {
69 | return applying(CATransform3DGetAffineTransform(t))
70 | }
71 | }
72 |
73 | public extension CGRect {
74 | /// A center point based on the origin and size values.
75 | var center: CGPoint {
76 | return CGPoint(x: origin.x + width / 2, y: origin.y + height / 2)
77 | }
78 |
79 | /// The bounding box size based from from the frame's rect.
80 | var bounds: CGRect {
81 | return CGRect(origin: .zero, size: size)
82 | }
83 |
84 | /**
85 | An initializer with a given point and size.
86 | - Parameter center: A CGPoint.
87 | - Parameter size: A CGSize.
88 | */
89 | init(center: CGPoint, size: CGSize) {
90 | self.init(x: center.x - size.width / 2, y: center.y - size.height / 2, width: size.width, height: size.height)
91 | }
92 | }
93 |
94 | public extension CGFloat {
95 | /**
96 | Calculates the limiting position to an area.
97 | - Parameter _ a: A CGFloat.
98 | - Parameter _ b: A CGFloat.
99 | - Returns: A CGFloat.
100 | */
101 | func clamp(_ a: CGFloat, _ b: CGFloat) -> CGFloat {
102 | return self < a ? a : self > b ? b : self
103 | }
104 | }
105 |
106 | public extension CGPoint {
107 | /**
108 | Calculates a translation point based on the origin value.
109 | - Parameter _ dx: A CGFloat.
110 | - Parameter _ dy: A CGFloat.
111 | - Returns: A CGPoint.
112 | */
113 | func translate(_ dx: CGFloat, dy: CGFloat) -> CGPoint {
114 | return CGPoint(x: x + dx, y: y + dy)
115 | }
116 |
117 | /**
118 | Calculates a transform point based on a given CGAffineTransform.
119 | - Parameter _ t: CGAffineTransform.
120 | - Returns: A CGPoint.
121 | */
122 | func transform(_ t: CGAffineTransform) -> CGPoint {
123 | return applying(t)
124 | }
125 |
126 | /**
127 | Calculates a transform point based on a given CATransform3D.
128 | - Parameter _ t: CATransform3D.
129 | - Returns: A CGPoint.
130 | */
131 | func transform(_ t: CATransform3D) -> CGPoint {
132 | return applying(CATransform3DGetAffineTransform(t))
133 | }
134 |
135 | /**
136 | Calculates the distance between the CGPoint and given CGPoint.
137 | - Parameter _ b: A CGPoint.
138 | - Returns: A CGFloat.
139 | */
140 | func distance(_ b: CGPoint) -> CGFloat {
141 | return sqrt(pow(x - b.x, 2) + pow(y - b.y, 2))
142 | }
143 | }
144 |
145 | /**
146 | A handler for the (+) operator.
147 | - Parameter left: A CGPoint.
148 | - Parameter right: A CGPoint.
149 | - Returns: A CGPoint.
150 | */
151 | public func + (left: CGPoint, right: CGPoint) -> CGPoint {
152 | return CGPoint(x: left.x + right.x, y: left.y + right.y)
153 | }
154 |
155 | /**
156 | A handler for the (-) operator.
157 | - Parameter left: A CGPoint.
158 | - Parameter right: A CGPoint.
159 | - Returns: A CGPoint.
160 | */
161 | public func - (left: CGPoint, right: CGPoint) -> CGPoint {
162 | return CGPoint(x: left.x - right.x, y: left.y - right.y)
163 | }
164 |
165 | /**
166 | A handler for the (/) operator.
167 | - Parameter left: A CGPoint.
168 | - Parameter right: A CGFloat.
169 | - Returns: A CGPoint.
170 | */
171 | public func / (left: CGPoint, right: CGFloat) -> CGPoint {
172 | return CGPoint(x: left.x / right, y: left.y / right)
173 | }
174 |
175 | /**
176 | A handler for the (/) operator.
177 | - Parameter left: A CGPoint.
178 | - Parameter right: A CGPoint.
179 | - Returns: A CGPoint.
180 | */
181 | public func / (left: CGPoint, right: CGPoint) -> CGPoint {
182 | return CGPoint(x: left.x / right.x, y: left.y / right.y)
183 | }
184 |
185 | /**
186 | A handler for the (/) operator.
187 | - Parameter left: A CGPoint.
188 | - Parameter right: A CGSize.
189 | - Returns: A CGPoint.
190 | */
191 | public func / (left: CGPoint, right: CGSize) -> CGPoint {
192 | return CGPoint(x: left.x / right.width, y: left.y / right.height)
193 | }
194 |
195 | /**
196 | A handler for the (/) operator.
197 | - Parameter left: A CGSize.
198 | - Parameter right: A CGSize.
199 | - Returns: A CGSize.
200 | */
201 | public func / (left: CGSize, right: CGSize) -> CGSize {
202 | return CGSize(width: left.width / right.width, height: left.height / right.height)
203 | }
204 |
205 | /**
206 | A handler for the (*) operator.
207 | - Parameter left: A CGPoint.
208 | - Parameter right: A CGFloat.
209 | - Returns: A CGPoint.
210 | */
211 | public func * (left: CGPoint, right: CGFloat) -> CGPoint {
212 | return CGPoint(x: left.x * right, y: left.y * right)
213 | }
214 |
215 | /**
216 | A handler for the (*) operator.
217 | - Parameter left: A CGPoint.
218 | - Parameter right: A CGSize.
219 | - Returns: A CGPoint.
220 | */
221 | public func * (left: CGPoint, right: CGSize) -> CGPoint {
222 | return CGPoint(x: left.x * right.width, y: left.y * right.width)
223 | }
224 |
225 | /**
226 | A handler for the (*) operator.
227 | - Parameter left: A CGFloat.
228 | - Parameter right: A CGPoint.
229 | - Returns: A CGPoint.
230 | */
231 | public func * (left: CGFloat, right: CGPoint) -> CGPoint {
232 | return right * left
233 | }
234 |
235 | /**
236 | A handler for the (*) operator.
237 | - Parameter left: A CGPoint.
238 | - Parameter right: A CGPoint.
239 | - Returns: A CGPoint.
240 | */
241 | public func * (left: CGPoint, right: CGPoint) -> CGPoint {
242 | return CGPoint(x: left.x * right.x, y: left.y * right.y)
243 | }
244 |
245 | /**
246 | A handler for the (*) prefix.
247 | - Parameter left: A CGSize.
248 | - Parameter right: A CGFloat.
249 | - Returns: A CGSize.
250 | */
251 | public func * (left: CGSize, right: CGFloat) -> CGSize {
252 | return CGSize(width: left.width * right, height: left.height * right)
253 | }
254 |
255 | /**
256 | A handler for the (*) prefix.
257 | - Parameter left: A CGSize.
258 | - Parameter right: A CGSize.
259 | - Returns: A CGSize.
260 | */
261 | public func * (left: CGSize, right: CGSize) -> CGSize {
262 | return CGSize(width: left.width * right.width, height: left.height * right.height)
263 | }
264 |
265 | /**
266 | A handler for the (==) operator.
267 | - Parameter lhs: A CATransform3D.
268 | - Parameter rhs: A CATransform3D.
269 | - Returns: A Bool.
270 | */
271 | public func == (lhs: CATransform3D, rhs: CATransform3D) -> Bool {
272 | var lhs = lhs
273 | var rhs = rhs
274 | return 0 == memcmp(&lhs, &rhs, MemoryLayout.size)
275 | }
276 |
277 | /**
278 | A handler for the (!=) operator.
279 | - Parameter lhs: A CATransform3D.
280 | - Parameter rhs: A CATransform3D.
281 | - Returns: A Bool.
282 | */
283 | public func != (lhs: CATransform3D, rhs: CATransform3D) -> Bool {
284 | return !(lhs == rhs)
285 | }
286 |
287 | /**
288 | A handler for the (-) prefix.
289 | - Parameter point: A CGPoint.
290 | - Returns: A CGPoint.
291 | */
292 | public prefix func - (point: CGPoint) -> CGPoint {
293 | return .zero - point
294 | }
295 |
296 | /**
297 | A handler for the (abs) function.
298 | - Parameter _ p: A CGPoint.
299 | - Returns: A CGPoint.
300 | */
301 | public func abs(_ p: CGPoint) -> CGPoint {
302 | return CGPoint(x: abs(p.x), y: abs(p.y))
303 | }
304 |
--------------------------------------------------------------------------------
/Sources/Extensions/Motion+Obj-C.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import ObjectiveC
27 |
28 | public struct AssociatedObject {
29 | /**
30 | Gets the Obj-C reference for the instance object within the UIView extension.
31 | - Parameter base: Base object.
32 | - Parameter key: Memory key pointer.
33 | - Parameter initializer: Object initializer.
34 | - Returns: The associated reference for the initializer object.
35 | */
36 | public static func get(base: Any, key: UnsafePointer, initializer: () -> T) -> T {
37 | if let v = objc_getAssociatedObject(base, key) as? T {
38 | return v
39 | }
40 |
41 | let v = initializer()
42 | objc_setAssociatedObject(base, key, v, .OBJC_ASSOCIATION_RETAIN)
43 | return v
44 | }
45 |
46 | /**
47 | Sets the Obj-C reference for the instance object within the UIView extension.
48 | - Parameter base: Base object.
49 | - Parameter key: Memory key pointer.
50 | - Parameter value: The object instance to set for the associated object.
51 | - Returns: The associated reference for the initializer object.
52 | */
53 | public static func set(base: Any, key: UnsafePointer, value: T) {
54 | objc_setAssociatedObject(base, key, value, .OBJC_ASSOCIATION_RETAIN)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Sources/Extensions/Motion+UIKit.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | fileprivate let parameterRegex = "(?:\\-?\\d+(\\.?\\d+)?)|\\w+"
29 | fileprivate let transitionsRegex = "(\\w+)(?:\\(([^\\)]*)\\))?"
30 |
31 | internal extension NSObject {
32 | /// Copies an object using NSKeyedArchiver.
33 | func copyWithArchiver() -> Any? {
34 | return NSKeyedUnarchiver.unarchiveObject(with: NSKeyedArchiver.archivedData(withRootObject: self))!
35 | }
36 | }
37 |
38 | internal extension UIColor {
39 | /// A tuple of the rgba components.
40 | var components: (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) {
41 | var r: CGFloat = 0
42 | var g: CGFloat = 0
43 | var b: CGFloat = 0
44 | var a: CGFloat = 0
45 |
46 | getRed(&r, green: &g, blue: &b, alpha: &a)
47 |
48 | return (r, g, b, a)
49 | }
50 |
51 | /// The alpha component value.
52 | var alphaComponent: CGFloat {
53 | return components.a
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Sources/Extensions/Motion+UIView.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | fileprivate var AssociatedInstanceKey: UInt8 = 0
29 |
30 | fileprivate struct AssociatedInstance {
31 | /// A boolean indicating whether Motion is enabled.
32 | fileprivate var isEnabled: Bool
33 |
34 | /// A boolean indicating whether Motion is enabled for subviews.
35 | fileprivate var isEnabledForSubviews: Bool
36 |
37 | /// An optional reference to the motion identifier.
38 | fileprivate var identifier: String?
39 |
40 | /// An optional reference to the motion animations.
41 | fileprivate var animations: [MotionAnimation]?
42 |
43 | /// An optional reference to the motion animation modifiers.
44 | fileprivate var modifiers: [MotionModifier]?
45 |
46 | /// An alpha value.
47 | fileprivate var alpha: CGFloat?
48 | }
49 |
50 | fileprivate extension UIView {
51 | /// AssociatedInstance reference.
52 | var associatedInstance: AssociatedInstance {
53 | get {
54 | return AssociatedObject.get(base: self, key: &AssociatedInstanceKey) {
55 | return AssociatedInstance(isEnabled: true, isEnabledForSubviews: true, identifier: nil, animations: nil, modifiers: nil, alpha: nil)
56 | }
57 | }
58 | set(value) {
59 | AssociatedObject.set(base: self, key: &AssociatedInstanceKey, value: value)
60 | }
61 | }
62 | }
63 |
64 | public extension UIView {
65 | /// A boolean that indicates whether motion is enabled.
66 | @IBInspectable
67 | var isMotionEnabled: Bool {
68 | get {
69 | return associatedInstance.isEnabled
70 | }
71 | set(value) {
72 | associatedInstance.isEnabled = value
73 | }
74 | }
75 |
76 | /// A boolean that indicates whether motion is enabled.
77 | @IBInspectable
78 | var isMotionEnabledForSubviews: Bool {
79 | get {
80 | return associatedInstance.isEnabledForSubviews
81 | }
82 | set(value) {
83 | associatedInstance.isEnabledForSubviews = value
84 | }
85 | }
86 |
87 | /// An identifier value used to connect views across UIViewControllers.
88 | @IBInspectable
89 | var motionIdentifier: String? {
90 | get {
91 | return associatedInstance.identifier
92 | }
93 | set(value) {
94 | associatedInstance.identifier = value
95 | }
96 | }
97 |
98 | /**
99 | A function that accepts CAAnimation objects and executes them on the
100 | view's backing layer.
101 | - Parameter animations: A list of CAAnimations.
102 | */
103 | func animate(_ animations: CAAnimation...) {
104 | layer.animate(animations)
105 | }
106 |
107 | /**
108 | A function that accepts an Array of CAAnimation objects and executes
109 | them on the view's backing layer.
110 | - Parameter animations: An Array of CAAnimations.
111 | */
112 | func animate(_ animations: [CAAnimation]) {
113 | layer.animate(animations)
114 | }
115 |
116 | /**
117 | A function that accepts a list of MotionAnimation values and executes
118 | them on the view's backing layer.
119 | - Parameter animations: A list of MotionAnimation values.
120 | */
121 | func animate(_ animations: MotionAnimation...) {
122 | layer.animate(animations)
123 | }
124 |
125 | /**
126 | A function that accepts an Array of MotionAnimation values and executes
127 | them on the view's backing layer.
128 | - Parameter animations: An Array of MotionAnimation values.
129 | - Parameter completion: An optional completion block.
130 | */
131 | func animate(_ animations: [MotionAnimation], completion: (() -> Void)? = nil) {
132 | layer.animate(animations, completion: completion)
133 | }
134 |
135 | /**
136 | A function that accepts a list of MotionTargetState values.
137 | - Parameter transitions: A list of MotionTargetState values.
138 | */
139 | func transition(_ modifiers: MotionModifier...) {
140 | transition(modifiers)
141 | }
142 |
143 | /**
144 | A function that accepts an Array of MotionTargetState values.
145 | - Parameter transitions: An Array of MotionTargetState values.
146 | */
147 | func transition(_ modifiers: [MotionModifier]) {
148 | motionModifiers = modifiers
149 | }
150 | }
151 |
152 | internal extension UIView {
153 | /// The animations to run while in transition.
154 | var motionModifiers: [MotionModifier]? {
155 | get {
156 | return associatedInstance.modifiers
157 | }
158 | set(value) {
159 | associatedInstance.modifiers = value
160 | }
161 | }
162 |
163 | /// The animations to run while in transition.
164 | var motionAlpha: CGFloat? {
165 | get {
166 | return associatedInstance.alpha
167 | }
168 | set(value) {
169 | associatedInstance.alpha = value
170 | }
171 | }
172 |
173 | /// Computes the rotate of the view.
174 | var motionRotationAngle: CGFloat {
175 | get {
176 | return CGFloat(atan2f(Float(transform.b), Float(transform.a))) * 180 / CGFloat(Double.pi)
177 | }
178 | set(value) {
179 | transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi) * value / 180)
180 | }
181 | }
182 |
183 | /// The global position of a view.
184 | var motionPosition: CGPoint {
185 | return superview?.convert(layer.position, to: nil) ?? layer.position
186 | }
187 |
188 | /// The layer.transform of a view.
189 | var motionTransform: CATransform3D {
190 | get {
191 | return layer.transform
192 | }
193 | set(value) {
194 | layer.transform = value
195 | }
196 | }
197 |
198 | /// Computes the scale X axis value of the view.
199 | var motionScaleX: CGFloat {
200 | return transform.a
201 | }
202 |
203 | /// Computes the scale Y axis value of the view.
204 | var motionScaleY: CGFloat {
205 | return transform.b
206 | }
207 | }
208 |
209 | internal class SnapshotWrapperView: UIView {
210 | /// A reference to the contentView.
211 | let contentView: UIView
212 |
213 | /**
214 | An initializer that takes in a contentView.
215 | - Parameter contentView: A UIView.
216 | */
217 | init(contentView: UIView) {
218 | self.contentView = contentView
219 | super.init(frame: contentView.frame)
220 | contentView.frame = bounds
221 | addSubview(contentView)
222 | }
223 |
224 | required init?(coder aDecoder: NSCoder) {
225 | fatalError("init(coder:) has not been implemented")
226 | }
227 |
228 | override func layoutSubviews() {
229 | super.layoutSubviews()
230 | contentView.frame = bounds
231 | }
232 | }
233 |
234 | internal extension UIView {
235 | /// Retrieves a single Array of UIViews that are in the view hierarchy.
236 | var flattenedViewHierarchy: [UIView] {
237 | guard isMotionEnabled else {
238 | return []
239 | }
240 |
241 | if #available(iOS 9.0, *), isHidden && (superview is UICollectionView || superview is UIStackView || self is UITableViewCell) {
242 | return []
243 |
244 | } else if isHidden && (superview is UICollectionView || self is UITableViewCell) {
245 | return []
246 |
247 | } else if isMotionEnabledForSubviews {
248 | return [self] + subviews.flatMap {
249 | $0.flattenedViewHierarchy
250 | }
251 | }
252 |
253 | return [self]
254 | }
255 |
256 | /**
257 | Retrieves the optimized duration based on the given parameters.
258 | - Parameter fromPosition: A CGPoint.
259 | - Parameter toPosition: An optional CGPoint.
260 | - Parameter size: An optional CGSize.
261 | - Parameter transform: An optional CATransform3D.
262 | - Returns: A TimeInterval.
263 | */
264 | func optimizedDuration(position: CGPoint?, size: CGSize?, transform: CATransform3D?) -> TimeInterval {
265 | let fromPos = (layer.presentation() ?? layer).position
266 | let toPos = position ?? fromPos
267 | let fromSize = (layer.presentation() ?? layer).bounds.size
268 | let toSize = size ?? fromSize
269 | let fromTransform = (layer.presentation() ?? layer).transform
270 | let toTransform = transform ?? fromTransform
271 |
272 | let realFromPos = CGPoint.zero.transform(fromTransform) + fromPos
273 | let realToPos = CGPoint.zero.transform(toTransform) + toPos
274 |
275 | let realFromSize = fromSize.transform(fromTransform)
276 | let realToSize = toSize.transform(toTransform)
277 |
278 | let movePoints = (realFromPos.distance(realToPos) + realFromSize.bottomRight.distance(realToSize.bottomRight))
279 |
280 | // duration is 0.2 @ 0 to 0.375 @ 500
281 | return 0.208 + Double(movePoints.clamp(0, 500)) / 3000
282 | }
283 |
284 | /**
285 | Calculates the optimized duration for a view.
286 | - Parameter targetState: A MotionTargetState.
287 | - Returns: A TimeInterval.
288 | */
289 | func optimizedDuration(targetState: MotionTargetState) -> TimeInterval {
290 | return optimizedDuration(position: targetState.position,
291 | size: targetState.size,
292 | transform: targetState.transform)
293 | }
294 |
295 | /**
296 | Takes a snapshot of a view usinag the UI graphics context.
297 | - Returns: A UIView with an embedded UIImageView.
298 | */
299 | func slowSnapshotView() -> UIView {
300 | UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
301 |
302 | guard let currentContext = UIGraphicsGetCurrentContext() else {
303 | UIGraphicsEndImageContext()
304 | return UIView()
305 | }
306 |
307 | layer.render(in: currentContext)
308 |
309 | let image = UIGraphicsGetImageFromCurrentImageContext()
310 | UIGraphicsEndImageContext()
311 |
312 | let imageView = UIImageView(image: image)
313 | imageView.frame = bounds
314 |
315 | return SnapshotWrapperView(contentView: imageView)
316 | }
317 |
318 | /**
319 | Returns a snapshot of the view itself.
320 | - Returns: An optional UIView.
321 | */
322 | func snapshotView() -> UIView? {
323 | let snapshot = snapshotView(afterScreenUpdates: true)
324 |
325 | if #available(iOS 11.0, *), let oldSnapshot = snapshot {
326 | // iOS 11 no longer contains a container view.
327 | return SnapshotWrapperView(contentView: oldSnapshot)
328 | }
329 |
330 | return snapshot
331 | }
332 | }
333 |
--------------------------------------------------------------------------------
/Sources/Extensions/Motion+UIViewController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | fileprivate var AssociatedInstanceKey: UInt8 = 0
29 |
30 | fileprivate struct AssociatedInstance {
31 | /// A reference to the modal animation.
32 | var modalTransitionType: MotionTransitionAnimationType
33 |
34 | /// A reference to the navigation animation.
35 | var navigationTransitionType: MotionTransitionAnimationType
36 |
37 | /// A reference to the tabBar animation.
38 | var tabBarTransitionType: MotionTransitionAnimationType
39 |
40 | /// A reference to the stored snapshot.
41 | var storedSnapshot: UIView?
42 |
43 | /**
44 | A reference to the previous navigation controller delegate
45 | before Motion was enabled.
46 | */
47 | weak var previousNavigationDelegate: UINavigationControllerDelegate?
48 |
49 | /**
50 | A reference to the previous tab bar controller delegate
51 | before Motion was enabled.
52 | */
53 | weak var previousTabBarDelegate: UITabBarControllerDelegate?
54 | }
55 |
56 | extension UIViewController {
57 | /// AssociatedInstance reference.
58 | fileprivate var associatedInstance: AssociatedInstance {
59 | get {
60 | return AssociatedObject.get(base: self, key: &AssociatedInstanceKey) {
61 | return AssociatedInstance(modalTransitionType: .auto,
62 | navigationTransitionType: .auto,
63 | tabBarTransitionType: .auto,
64 | storedSnapshot: nil,
65 | previousNavigationDelegate: nil,
66 | previousTabBarDelegate: nil)
67 | }
68 | }
69 | set(value) {
70 | AssociatedObject.set(base: self, key: &AssociatedInstanceKey, value: value)
71 | }
72 | }
73 |
74 | /// Default motion animation type for presenting & dismissing modally.
75 | public var motionTransitionType: MotionTransitionAnimationType {
76 | get {
77 | return associatedInstance.modalTransitionType
78 | }
79 | set(value) {
80 | associatedInstance.modalTransitionType = value
81 | }
82 | }
83 |
84 | /// Used for .overFullScreen presentation.
85 | internal var motionStoredSnapshot: UIView? {
86 | get {
87 | return associatedInstance.storedSnapshot
88 | }
89 | set(value) {
90 | associatedInstance.storedSnapshot = value
91 | }
92 | }
93 |
94 | /**
95 | A reference to the previous navigation controller delegate
96 | before Motion was enabled.
97 | */
98 | internal var previousNavigationDelegate: UINavigationControllerDelegate? {
99 | get {
100 | return associatedInstance.previousNavigationDelegate
101 | }
102 | set(value) {
103 | associatedInstance.previousNavigationDelegate = value
104 | }
105 | }
106 |
107 | /**
108 | A reference to the previous tab bar controller delegate
109 | before Motion was enabled.
110 | */
111 | internal var previousTabBarDelegate: UITabBarControllerDelegate? {
112 | get {
113 | return associatedInstance.previousTabBarDelegate
114 | }
115 | set(value) {
116 | associatedInstance.previousTabBarDelegate = value
117 | }
118 | }
119 |
120 | ///A boolean that indicates whether Motion is enabled or disabled.
121 | @IBInspectable
122 | public var isMotionEnabled: Bool {
123 | get {
124 | return transitioningDelegate is MotionTransition
125 | }
126 | set(value) {
127 | guard value != isMotionEnabled else {
128 | return
129 | }
130 |
131 | if value {
132 | transitioningDelegate = MotionTransition.shared
133 |
134 | if let v = self as? UINavigationController {
135 | previousNavigationDelegate = v.delegate
136 | v.delegate = MotionTransition.shared
137 | }
138 |
139 | if let v = self as? UITabBarController {
140 | previousTabBarDelegate = v.delegate
141 | v.delegate = MotionTransition.shared
142 | }
143 |
144 | } else {
145 | transitioningDelegate = nil
146 |
147 | if let v = self as? UINavigationController, v.delegate is MotionTransition {
148 | v.delegate = previousNavigationDelegate
149 | }
150 |
151 | if let v = self as? UITabBarController, v.delegate is MotionTransition {
152 | v.delegate = previousTabBarDelegate
153 | }
154 | }
155 | }
156 | }
157 | }
158 |
159 | extension UINavigationController {
160 | /// Default motion animation type for push and pop within the navigation controller.
161 | public var motionNavigationTransitionType: MotionTransitionAnimationType {
162 | get {
163 | return associatedInstance.navigationTransitionType
164 | }
165 | set(value) {
166 | associatedInstance.navigationTransitionType = value
167 | }
168 | }
169 | }
170 |
171 | extension UITabBarController {
172 | /// Default motion animation type for switching tabs within the tab bar controller.
173 | public var motionTabBarTransitionType: MotionTransitionAnimationType {
174 | get {
175 | return associatedInstance.tabBarTransitionType
176 | }
177 | set(value) {
178 | associatedInstance.tabBarTransitionType = value
179 | }
180 | }
181 | }
182 |
183 | extension UIViewController {
184 | /**
185 | Dismiss the current view controller with animation. Will perform a
186 | navigationController.popViewController if the current view controller
187 | is contained inside a navigationController
188 | */
189 | @IBAction
190 | public func motionDismissViewController() {
191 | if let v = navigationController, self != v.viewControllers.first {
192 | v.popViewController(animated: true)
193 | } else {
194 | dismiss(animated: true)
195 | }
196 | }
197 |
198 | /// Unwind to the root view controller using Motion.
199 | @IBAction
200 | public func motionUnwindToRootViewController() {
201 | motionUnwindToViewController {
202 | nil == $0.presentingViewController
203 | }
204 | }
205 |
206 | /// Unwind to a specific view controller using Motion.
207 | public func motionUnwindToViewController(_ toViewController: UIViewController) {
208 | motionUnwindToViewController {
209 | $0 == toViewController
210 | }
211 | }
212 |
213 | /// Unwind to a view controller that responds to the given selector using Motion.
214 | public func motionUnwindToViewController(withSelector: Selector) {
215 | motionUnwindToViewController {
216 | $0.responds(to: withSelector)
217 | }
218 | }
219 |
220 | /// Unwind to a view controller with given class using Motion
221 | public func motionUnwindToViewController(withClass: AnyClass) {
222 | motionUnwindToViewController {
223 | $0.isKind(of: withClass)
224 | }
225 | }
226 |
227 | /// Unwind to a view controller that the matchBlock returns true on.
228 | public func motionUnwindToViewController(withMatchBlock: (UIViewController) -> Bool) {
229 | var target: UIViewController? = nil
230 | var current: UIViewController? = self
231 |
232 | while nil == target && nil != current {
233 | if let childViewControllers = (current as? UINavigationController)?.children ?? current!.navigationController?.children {
234 |
235 | for vc in childViewControllers.reversed() {
236 | if self != vc, withMatchBlock(vc) {
237 | target = vc
238 | break
239 | }
240 | }
241 | }
242 |
243 | if nil == target {
244 | current = current!.presentingViewController
245 |
246 | if let vc = current, true == withMatchBlock(vc) {
247 | target = vc
248 | }
249 | }
250 | }
251 |
252 | guard let t = target else {
253 | return
254 | }
255 |
256 | guard nil != t.presentedViewController else {
257 | _ = t.navigationController?.popToViewController(t, animated: true)
258 | return
259 | }
260 |
261 | _ = t.navigationController?.popToViewController(t, animated: false)
262 |
263 | let fvc = navigationController ?? self
264 | let tvc = t.navigationController ?? t
265 |
266 | if t.presentedViewController != fvc {
267 | // UIKit's UIViewController.dismiss will jump to target.presentedViewController then perform the dismiss.
268 | // We overcome this behavior by inserting a snapshot into target.presentedViewController
269 | // And also force Hero to use the current VC as the fromViewController
270 | MotionTransition.shared.fromViewController = fvc
271 |
272 | let snapshotView = fvc.view.snapshotView(afterScreenUpdates: true)!
273 | let targetSuperview = tvc.presentedViewController!.view!
274 |
275 | if let visualEffectView = targetSuperview as? UIVisualEffectView {
276 | visualEffectView.contentView.addSubview(snapshotView)
277 | } else {
278 | targetSuperview.addSubview(snapshotView)
279 | }
280 | }
281 |
282 | tvc.dismiss(animated: true, completion: nil)
283 | }
284 |
285 | /**
286 | Replace the current view controller with another view controller within the
287 | navigation/modal stack.
288 | - Parameter with next: A UIViewController.
289 | */
290 | public func motionReplaceViewController(with next: UIViewController) {
291 | let motion = next.transitioningDelegate as? MotionTransition ?? MotionTransition.shared
292 |
293 | guard !motion.isTransitioning else {
294 | print("motionReplaceViewController cancelled because Motion was doing a transition. Use Motion.shared.cancel(animated: false) or Motion.shared.end(animated: false) to stop the transition first before calling motionReplaceViewController.")
295 | return
296 | }
297 |
298 | if let nc = navigationController {
299 | var v = nc.children
300 |
301 | if !v.isEmpty {
302 | v.removeLast()
303 | v.append(next)
304 | }
305 |
306 | if nc.isMotionEnabled {
307 | motion.forceNonInteractive = true
308 | }
309 |
310 | nc.setViewControllers(v, animated: true)
311 | } else if let container = view.superview {
312 | let presentingVC = presentingViewController
313 |
314 | motion.transition(from: self, to: next, in: container) { [weak self] (isFinishing) in
315 | guard isFinishing else {
316 | return
317 | }
318 |
319 | next.view.window?.addSubview(next.view)
320 |
321 | guard let pvc = presentingVC else {
322 | Application.shared.keyWindow?.rootViewController = next
323 | return
324 | }
325 |
326 | self?.dismiss(animated: false) {
327 | pvc.present(next, animated: false)
328 | }
329 | }
330 | }
331 | }
332 | }
333 |
--------------------------------------------------------------------------------
/Sources/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 | FMWK
17 | CFBundleShortVersionString
18 | $(MARKETING_VERSION)
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Sources/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (C) 2019, CosmicMind, Inc. .
4 | All rights reserved.
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/Sources/Motion.h:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | #import
27 |
28 | //! Project version number for Motion.
29 | FOUNDATION_EXPORT double MotionVersionNumber;
30 |
31 | //! Project version string for Motion.
32 | FOUNDATION_EXPORT const unsigned char MotionVersionString[];
33 |
34 | // In this header, you should import all the public headers of your framework using statements like #import
35 |
--------------------------------------------------------------------------------
/Sources/MotionAnimation.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | public class MotionAnimation {
29 | /// A reference to the callback that applies the MotionAnimationState.
30 | internal let apply: (inout MotionAnimationState) -> Void
31 |
32 | /**
33 | An initializer that accepts a given callback.
34 | - Parameter applyFunction: A given callback.
35 | */
36 | init(applyFunction: @escaping (inout MotionAnimationState) -> Void) {
37 | apply = applyFunction
38 | }
39 | }
40 |
41 | public extension MotionAnimation {
42 | /**
43 | Animates a view's current background color to the
44 | given color.
45 | - Parameter color: A UIColor.
46 | - Returns: A MotionAnimation.
47 | */
48 | static func background(color: UIColor) -> MotionAnimation {
49 | return MotionAnimation {
50 | $0.backgroundColor = color.cgColor
51 | }
52 | }
53 |
54 | /**
55 | Animates a view's current border color to the
56 | given color.
57 | - Parameter color: A UIColor.
58 | - Returns: A MotionAnimation.
59 | */
60 | static func border(color: UIColor) -> MotionAnimation {
61 | return MotionAnimation {
62 | $0.borderColor = color.cgColor
63 | }
64 | }
65 |
66 | /**
67 | Animates a view's current border width to the
68 | given width.
69 | - Parameter width: A CGFloat.
70 | - Returns: A MotionAnimation.
71 | */
72 | static func border(width: CGFloat) -> MotionAnimation {
73 | return MotionAnimation {
74 | $0.borderWidth = width
75 | }
76 | }
77 |
78 | /**
79 | Animates a view's current corner radius to the
80 | given radius.
81 | - Parameter radius: A CGFloat.
82 | - Returns: A MotionAnimation.
83 | */
84 | static func corner(radius: CGFloat) -> MotionAnimation {
85 | return MotionAnimation {
86 | $0.cornerRadius = radius
87 | }
88 | }
89 |
90 | /**
91 | Animates a view's current transform (perspective, scale, rotate)
92 | to the given one.
93 | - Parameter _ transform: A CATransform3D.
94 | - Returns: A MotionAnimation.
95 | */
96 | static func transform(_ transform: CATransform3D) -> MotionAnimation {
97 | return MotionAnimation {
98 | $0.transform = transform
99 | }
100 | }
101 |
102 | /**
103 | Animates a view's current rotation to the given x, y,
104 | and z values.
105 | - Parameter x: A CGFloat.
106 | - Parameter y: A CGFloat.
107 | - Parameter z: A CGFloat.
108 | - Returns: A MotionAnimation.
109 | */
110 | static func rotate(x: CGFloat = 0, y: CGFloat = 0, z: CGFloat = 0) -> MotionAnimation {
111 | return MotionAnimation {
112 | var t = $0.transform ?? CATransform3DIdentity
113 | t = CATransform3DRotate(t, CGFloat(Double.pi) * x / 180, 1, 0, 0)
114 | t = CATransform3DRotate(t, CGFloat(Double.pi) * y / 180, 0, 1, 0)
115 | $0.transform = CATransform3DRotate(t, CGFloat(Double.pi) * z / 180, 0, 0, 1)
116 | }
117 | }
118 |
119 | /**
120 | Animates a view's current rotation to the given point.
121 | - Parameter _ point: A CGPoint.
122 | - Parameter z: A CGFloat.
123 | - Returns: A MotionAnimation.
124 | */
125 | static func rotate(_ point: CGPoint, z: CGFloat = 0) -> MotionAnimation {
126 | return .rotate(x: point.x, y: point.y, z: z)
127 | }
128 |
129 | /**
130 | Rotate 2d.
131 | - Parameter _ z: A CGFloat.
132 | - Returns: A MotionAnimation.
133 | */
134 | static func rotate(_ z: CGFloat) -> MotionAnimation {
135 | return .rotate(z: z)
136 | }
137 |
138 | /**
139 | Animates a view's current spin to the given x, y,
140 | and z values.
141 | - Parameter x: A CGFloat.
142 | - Parameter y: A CGFloat.
143 | - Parameter z: A CGFloat.
144 | - Returns: A MotionAnimation.
145 | */
146 | static func spin(x: CGFloat = 0, y: CGFloat = 0, z: CGFloat = 0) -> MotionAnimation {
147 | return MotionAnimation {
148 | $0.spin = (x, y, z)
149 | }
150 | }
151 |
152 | /**
153 | Animates a view's current spin to the given point.
154 | - Parameter _ point: A CGPoint.
155 | - Parameter z: A CGFloat.
156 | - Returns: A MotionAnimation.
157 | */
158 | static func spin(_ point: CGPoint, z: CGFloat = 0) -> MotionAnimation {
159 | return .spin(x: point.x, y: point.y, z: z)
160 | }
161 |
162 | /**
163 | Spin 2d.
164 | - Parameter _ z: A CGFloat.
165 | - Returns: A MotionAnimation.
166 | */
167 | static func spin(_ z: CGFloat) -> MotionAnimation {
168 | return .spin(z: z)
169 | }
170 |
171 | /**
172 | Animates the view's current scale to the given x, y, and z scale values.
173 | - Parameter x: A CGFloat.
174 | - Parameter y: A CGFloat.
175 | - Parameter z: A CGFloat.
176 | - Returns: A MotionAnimation.
177 | */
178 | static func scale(x: CGFloat = 1, y: CGFloat = 1, z: CGFloat = 1) -> MotionAnimation {
179 | return MotionAnimation {
180 | $0.transform = CATransform3DScale($0.transform ?? CATransform3DIdentity, x, y, z)
181 | }
182 | }
183 |
184 | /**
185 | Animates the view's current x & y scale to the given scale value.
186 | - Parameter _ xy: A CGFloat.
187 | - Returns: A MotionAnimation.
188 | */
189 | static func scale(_ xy: CGFloat) -> MotionAnimation {
190 | return .scale(x: xy, y: xy)
191 | }
192 |
193 | /**
194 | Animates a view equal to the distance given by the x, y, and z values.
195 | - Parameter x: A CGFloat.
196 | - Parameter y: A CGFloat.
197 | - Parameter z: A CGFloat.
198 | - Returns: A MotionAnimation.
199 | */
200 | static func translate(x: CGFloat = 0, y: CGFloat = 0, z: CGFloat = 0) -> MotionAnimation {
201 | return MotionAnimation {
202 | $0.transform = CATransform3DTranslate($0.transform ?? CATransform3DIdentity, x, y, z)
203 | }
204 | }
205 |
206 | /**
207 | Animates a view equal to the distance given by a point and z value.
208 | - Parameter _ point: A CGPoint.
209 | - Parameter z: A CGFloat.
210 | - Returns: A MotionAnimation.
211 | */
212 | static func translate(_ point: CGPoint, z: CGFloat = 0) -> MotionAnimation {
213 | return .translate(x: point.x, y: point.y, z: z)
214 | }
215 |
216 | /**
217 | Animates a view's current position to the given point.
218 | - Parameter _ point: A CGPoint.
219 | - Returns: A MotionAnimation.
220 | */
221 | static func position(_ point: CGPoint) -> MotionAnimation {
222 | return MotionAnimation {
223 | $0.position = point
224 | }
225 | }
226 |
227 | /**
228 | Animates a view's current position to the given x and y values.
229 | - Parameter x: A CGloat.
230 | - Parameter y: A CGloat.
231 | - Returns: A MotionAnimation.
232 | */
233 | static func position(x: CGFloat, y: CGFloat) -> MotionAnimation {
234 | return .position(CGPoint(x: x, y: y))
235 | }
236 |
237 | /// Fades the view in during an animation.
238 | static var fadeIn = MotionAnimation.fade(1)
239 |
240 | /// Fades the view out during an animation.
241 | static var fadeOut = MotionAnimation.fade(0)
242 |
243 | /**
244 | Animates a view's current opacity to the given one.
245 | - Parameter _ opacity: A Double.
246 | - Returns: A MotionAnimation.
247 | */
248 | static func fade(_ opacity: Double) -> MotionAnimation {
249 | return MotionAnimation {
250 | $0.opacity = opacity
251 | }
252 | }
253 |
254 | /**
255 | Animates a view's current zPosition to the given position.
256 | - Parameter _ position: An Int.
257 | - Returns: A MotionAnimation.
258 | */
259 | static func zPosition(_ position: CGFloat) -> MotionAnimation {
260 | return MotionAnimation {
261 | $0.zPosition = position
262 | }
263 | }
264 |
265 | /**
266 | Animates a view's current size to the given one.
267 | - Parameter _ size: A CGSize.
268 | - Returns: A MotionAnimation.
269 | */
270 | static func size(_ size: CGSize) -> MotionAnimation {
271 | return MotionAnimation {
272 | $0.size = size
273 | }
274 | }
275 |
276 | /**
277 | Animates the view's current size to the given width and height.
278 | - Parameter width: A CGFloat.
279 | - Parameter height: A CGFloat.
280 | - Returns: A MotionAnimation.
281 | */
282 | static func size(width: CGFloat, height: CGFloat) -> MotionAnimation {
283 | return .size(CGSize(width: width, height: height))
284 | }
285 |
286 | /**
287 | Animates a view's current shadow path to the given one.
288 | - Parameter path: A CGPath.
289 | - Returns: A MotionAnimation.
290 | */
291 | static func shadow(path: CGPath) -> MotionAnimation {
292 | return MotionAnimation {
293 | $0.shadowPath = path
294 | }
295 | }
296 |
297 | /**
298 | Animates a view's current shadow color to the given one.
299 | - Parameter color: A UIColor.
300 | - Returns: A MotionAnimation.
301 | */
302 | static func shadow(color: UIColor) -> MotionAnimation {
303 | return MotionAnimation {
304 | $0.shadowColor = color.cgColor
305 | }
306 | }
307 |
308 | /**
309 | Animates a view's current shadow offset to the given one.
310 | - Parameter offset: A CGSize.
311 | - Returns: A MotionAnimation.
312 | */
313 | static func shadow(offset: CGSize) -> MotionAnimation {
314 | return MotionAnimation {
315 | $0.shadowOffset = offset
316 | }
317 | }
318 |
319 | /**
320 | Animates a view's current shadow opacity to the given one.
321 | - Parameter opacity: A Float.
322 | - Returns: A MotionAnimation.
323 | */
324 | static func shadow(opacity: Float) -> MotionAnimation {
325 | return MotionAnimation {
326 | $0.shadowOpacity = opacity
327 | }
328 | }
329 |
330 | /**
331 | Animates a view's current shadow radius to the given one.
332 | - Parameter radius: A CGFloat.
333 | - Returns: A MotionAnimation.
334 | */
335 | static func shadow(radius: CGFloat) -> MotionAnimation {
336 | return MotionAnimation {
337 | $0.shadowRadius = radius
338 | }
339 | }
340 |
341 | /**
342 | Animates the views shadow offset, opacity, and radius.
343 | - Parameter offset: A CGSize.
344 | - Parameter opacity: A Float.
345 | - Parameter radius: A CGFloat.
346 | */
347 | static func depth(offset: CGSize, opacity: Float, radius: CGFloat) -> MotionAnimation {
348 | return MotionAnimation {
349 | $0.shadowOffset = offset
350 | $0.shadowOpacity = opacity
351 | $0.shadowRadius = radius
352 | }
353 | }
354 |
355 | /**
356 | Animates the views shadow offset, opacity, and radius.
357 | - Parameter _ depth: A tuple (CGSize, FLoat, CGFloat).
358 | */
359 | static func depth(_ depth: (CGSize, Float, CGFloat)) -> MotionAnimation {
360 | return .depth(offset: depth.0, opacity: depth.1, radius: depth.2)
361 | }
362 |
363 | /**
364 | Available in iOS 9+, animates a view using the spring API,
365 | given a stiffness and damping.
366 | - Parameter stiffness: A CGFlloat.
367 | - Parameter damping: A CGFloat.
368 | - Returns: A MotionAnimation.
369 | */
370 | @available(iOS 9, *)
371 | static func spring(stiffness: CGFloat, damping: CGFloat) -> MotionAnimation {
372 | return MotionAnimation {
373 | $0.spring = (stiffness, damping)
374 | }
375 | }
376 |
377 | /**
378 | The duration of the view's animation. If a duration of 0 is used,
379 | the value will be converted to 0.01, to give a close to zero value.
380 | - Parameter _ duration: A TimeInterval.
381 | - Returns: A MotionAnimation.
382 | */
383 | static func duration(_ duration: TimeInterval) -> MotionAnimation {
384 | return MotionAnimation {
385 | $0.duration = duration
386 | }
387 | }
388 |
389 | /**
390 | Delays the animation of a given view.
391 | - Parameter _ time: TimeInterval.
392 | - Returns: A MotionAnimation.
393 | */
394 | static func delay(_ time: TimeInterval) -> MotionAnimation {
395 | return MotionAnimation {
396 | $0.delay = time
397 | }
398 | }
399 |
400 | /**
401 | Sets the view's timing function for the animation.
402 | - Parameter _ timingFunction: A CAMediaTimingFunction.
403 | - Returns: A MotionAnimation.
404 | */
405 | static func timingFunction(_ timingFunction: CAMediaTimingFunction) -> MotionAnimation {
406 | return MotionAnimation {
407 | $0.timingFunction = timingFunction
408 | }
409 | }
410 |
411 | /**
412 | Creates a completion block handler that is executed once the animation
413 | is done.
414 | - Parameter _ execute: A callback to execute once completed.
415 | */
416 | static func completion(_ execute: @escaping () -> Void) -> MotionAnimation {
417 | return MotionAnimation {
418 | $0.completion = execute
419 | }
420 | }
421 | }
422 |
--------------------------------------------------------------------------------
/Sources/MotionAnimationState.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | public struct MotionAnimationState {
29 | /// A reference to the position.
30 | public var position: CGPoint?
31 |
32 | /// A reference to the size.
33 | public var size: CGSize?
34 |
35 | /// A reference to the transform.
36 | public var transform: CATransform3D?
37 |
38 | /// A reference to the spin tuple.
39 | public var spin: (x: CGFloat, y: CGFloat, z: CGFloat)?
40 |
41 | /// A reference to the opacity.
42 | public var opacity: Double?
43 |
44 | /// A reference to the cornerRadius.
45 | public var cornerRadius: CGFloat?
46 |
47 | /// A reference to the backgroundColor.
48 | public var backgroundColor: CGColor?
49 |
50 | /// A reference to the zPosition.
51 | public var zPosition: CGFloat?
52 |
53 | /// A reference to the borderWidth.
54 | public var borderWidth: CGFloat?
55 |
56 | /// A reference to the borderColor.
57 | public var borderColor: CGColor?
58 |
59 | /// A reference to the shadowColor.
60 | public var shadowColor: CGColor?
61 |
62 | /// A reference to the shadowOpacity.
63 | public var shadowOpacity: Float?
64 |
65 | /// A reference to the shadowOffset.
66 | public var shadowOffset: CGSize?
67 |
68 | /// A reference to the shadowRadius.
69 | public var shadowRadius: CGFloat?
70 |
71 | /// A reference to the shadowPath.
72 | public var shadowPath: CGPath?
73 |
74 | /// A reference to the spring animation settings.
75 | public var spring: (CGFloat, CGFloat)?
76 |
77 | /// A time delay on starting the animation.
78 | public var delay: TimeInterval = 0
79 |
80 | /// The duration of the animation.
81 | public var duration: TimeInterval = 0.35
82 |
83 | /// The timing function value of the animation.
84 | public var timingFunction = CAMediaTimingFunction.easeInOut
85 |
86 | /// Custom target states.
87 | public var custom: [String: Any]?
88 |
89 | /// Completion block.
90 | public var completion: (() -> Void)?
91 |
92 | /**
93 | An initializer that accepts an Array of MotionAnimations.
94 | - Parameter animations: An Array of MotionAnimations.
95 | */
96 | init(animations: [MotionAnimation]) {
97 | append(contentsOf: animations)
98 | }
99 | }
100 |
101 | extension MotionAnimationState {
102 | /**
103 | Adds a MotionAnimation to the current state.
104 | - Parameter _ element: A MotionAnimation.
105 | */
106 | public mutating func append(_ element: MotionAnimation) {
107 | element.apply(&self)
108 | }
109 |
110 | /**
111 | Adds an Array of MotionAnimations to the current state.
112 | - Parameter contentsOf elements: An Array of MotionAnimations.
113 | */
114 | public mutating func append(contentsOf elements: [MotionAnimation]) {
115 | for v in elements {
116 | v.apply(&self)
117 | }
118 | }
119 |
120 | /**
121 | A subscript that returns a custom value for a specified key.
122 | - Parameter key: A String.
123 | - Returns: An optional Any value.
124 | */
125 | public subscript(key: String) -> Any? {
126 | get {
127 | return custom?[key]
128 | }
129 | set(value) {
130 | if nil == custom {
131 | custom = [:]
132 | }
133 |
134 | custom![key] = value
135 | }
136 | }
137 | }
138 |
139 | extension MotionAnimationState: ExpressibleByArrayLiteral {
140 | /**
141 | An initializer implementing the ExpressibleByArrayLiteral protocol.
142 | - Parameter arrayLiteral elements: A list of MotionAnimations.
143 | */
144 | public init(arrayLiteral elements: MotionAnimation...) {
145 | append(contentsOf: elements)
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/Sources/MotionCAAnimation.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | public enum MotionAnimationKeyPath: String {
29 | case backgroundColor
30 | case borderColor
31 | case borderWidth
32 | case cornerRadius
33 | case transform
34 | case rotate = "transform.rotation"
35 | case rotateX = "transform.rotation.x"
36 | case rotateY = "transform.rotation.y"
37 | case rotateZ = "transform.rotation.z"
38 | case scale = "transform.scale"
39 | case scaleX = "transform.scale.x"
40 | case scaleY = "transform.scale.y"
41 | case scaleZ = "transform.scale.z"
42 | case translation = "transform.translation"
43 | case translationX = "transform.translation.x"
44 | case translationY = "transform.translation.y"
45 | case translationZ = "transform.translation.z"
46 | case position
47 | case opacity
48 | case zPosition
49 | case width = "bounds.size.width"
50 | case height = "bounds.size.height"
51 | case size = "bounds.size"
52 | case shadowPath
53 | case shadowColor
54 | case shadowOffset
55 | case shadowOpacity
56 | case shadowRadius
57 | }
58 |
59 | extension CABasicAnimation {
60 | /**
61 | A convenience initializer that takes a given MotionAnimationKeyPath.
62 | - Parameter keyPath: An MotionAnimationKeyPath.
63 | */
64 | public convenience init(keyPath: MotionAnimationKeyPath) {
65 | self.init(keyPath: keyPath.rawValue)
66 | }
67 | }
68 |
69 | public struct MotionCAAnimation {}
70 |
71 | fileprivate extension MotionCAAnimation {
72 | /**
73 | Creates a CABasicAnimation.
74 | - Parameter keyPath: A MotionAnimationKeyPath.
75 | - Parameter toValue: An Any value that is the end state of the animation.
76 | */
77 | static func createAnimation(keyPath: MotionAnimationKeyPath, toValue: Any) -> CABasicAnimation {
78 | let a = CABasicAnimation(keyPath: keyPath)
79 | a.toValue = toValue
80 | return a
81 | }
82 | }
83 |
84 | @available(iOS 9.0, *)
85 | internal extension MotionCAAnimation {
86 | /**
87 | Converts a CABasicAnimation to a CASpringAnimation.
88 | - Parameter animation: A CABasicAnimation.
89 | - Parameter stiffness: A CGFloat.
90 | - Parameter damping: A CGFloat.
91 | */
92 | static func convert(animation: CABasicAnimation, stiffness: CGFloat, damping: CGFloat) -> CASpringAnimation {
93 | let a = CASpringAnimation(keyPath: animation.keyPath)
94 | a.fromValue = animation.fromValue
95 | a.toValue = animation.toValue
96 | a.stiffness = stiffness
97 | a.damping = damping
98 | return a
99 | }
100 | }
101 |
102 | public extension MotionCAAnimation {
103 | /**
104 | Creates a CABasicAnimation for the backgroundColor key path.
105 | - Parameter color: A UIColor.
106 | - Returns: A CABasicAnimation.
107 | */
108 | static func background(color: UIColor) -> CABasicAnimation {
109 | return createAnimation(keyPath: .backgroundColor, toValue: color.cgColor)
110 | }
111 |
112 | /**
113 | Creates a CABasicAnimation for the borderColor key path.
114 | - Parameter color: A UIColor.
115 | - Returns: A CABasicAnimation.
116 | */
117 | static func border(color: UIColor) -> CABasicAnimation {
118 | return createAnimation(keyPath: .borderColor, toValue: color.cgColor)
119 | }
120 |
121 | /**
122 | Creates a CABasicAnimation for the borderWidth key path.
123 | - Parameter width: A CGFloat.
124 | - Returns: A CABasicAnimation.
125 | */
126 | static func border(width: CGFloat) -> CABasicAnimation {
127 | return createAnimation(keyPath: .borderWidth, toValue: NSNumber(floatLiteral: Double(width)))
128 | }
129 |
130 | /**
131 | Creates a CABasicAnimation for the cornerRadius key path.
132 | - Parameter radius: A CGFloat.
133 | - Returns: A CABasicAnimation.
134 | */
135 | static func corner(radius: CGFloat) -> CABasicAnimation {
136 | return createAnimation(keyPath: .cornerRadius, toValue: NSNumber(floatLiteral: Double(radius)))
137 | }
138 |
139 | /**
140 | Creates a CABasicAnimation for the transform key path.
141 | - Parameter _ t: A CATransform3D object.
142 | - Returns: A CABasicAnimation.
143 | */
144 | static func transform(_ t: CATransform3D) -> CABasicAnimation {
145 | return createAnimation(keyPath: .transform, toValue: NSValue(caTransform3D: t))
146 | }
147 |
148 | /**
149 | Creates a CABasicAnimation for the transform.scale key path.
150 | - Parameter xyz: A CGFloat.
151 | - Returns: A CABasicAnimation.
152 | */
153 | static func scale(_ xyz: CGFloat) -> CABasicAnimation {
154 | let a = CABasicAnimation(keyPath: .scale)
155 | a.toValue = NSNumber(value: Double(xyz))
156 | return a
157 | }
158 |
159 | /**
160 | Creates a CABasicAnimation for the transform.scale.x key path.
161 | - Parameter x: A CGFloat.
162 | - Returns: A CABasicAnimation.
163 | */
164 | static func scale(x: CGFloat) -> CABasicAnimation {
165 | let a = CABasicAnimation(keyPath: .scaleX)
166 | a.toValue = NSNumber(value: Double(x))
167 | return a
168 | }
169 |
170 | /**
171 | Creates a CABasicAnimation for the transform.scale.y key path.
172 | - Parameter y: A CGFloat.
173 | - Returns: A CABasicAnimation.
174 | */
175 | static func scale(y: CGFloat) -> CABasicAnimation {
176 | let a = CABasicAnimation(keyPath: .scaleY)
177 | a.toValue = NSNumber(value: Double(y))
178 | return a
179 | }
180 |
181 | /**
182 | Creates a CABasicAnimation for the transform.scale.z key path.
183 | - Parameter z: A CGFloat.
184 | - Returns: A CABasicAnimation.
185 | */
186 | static func scale(z: CGFloat) -> CABasicAnimation {
187 | let a = CABasicAnimation(keyPath: .scaleZ)
188 | a.toValue = NSNumber(value: Double(z))
189 | return a
190 | }
191 |
192 | /**
193 | Creates a CABasicAnimation for the transform.rotate.x key path.
194 | - Parameter x: An optional CGFloat.
195 | - Returns: A CABasicAnimation.
196 | */
197 | static func spin(x: CGFloat) -> CABasicAnimation {
198 | return createAnimation(keyPath: .rotateX, toValue: NSNumber(value: Double(CGFloat(Double.pi) * 2 * x)))
199 | }
200 |
201 | /**
202 | Creates a CABasicAnimation for the transform.rotate.y key path.
203 | - Parameter y: An optional CGFloat.
204 | - Returns: A CABasicAnimation.
205 | */
206 | static func spin(y: CGFloat) -> CABasicAnimation {
207 | return createAnimation(keyPath: .rotateY, toValue: NSNumber(value: Double(CGFloat(Double.pi) * 2 * y)))
208 | }
209 |
210 | /**
211 | Creates a CABasicAnimation for the transform.rotate.z key path.
212 | - Parameter z: An optional CGFloat.
213 | - Returns: A CABasicAnimation.
214 | */
215 | static func spin(z: CGFloat) -> CABasicAnimation {
216 | return createAnimation(keyPath: .rotateZ, toValue: NSNumber(value: Double(CGFloat(Double.pi) * 2 * z)))
217 | }
218 |
219 | /**
220 | Creates a CABasicAnimation for the position key path.
221 | - Parameter _ point: A CGPoint.
222 | - Returns: A CABasicAnimation.
223 | */
224 | static func position(_ point: CGPoint) -> CABasicAnimation {
225 | return createAnimation(keyPath: .position, toValue: NSValue(cgPoint: point))
226 | }
227 |
228 | /**
229 | Creates a CABasicAnimation for the opacity key path.
230 | - Parameter _ opacity: A Double.
231 | - Returns: A CABasicAnimation.
232 | */
233 | static func fade(_ opacity: Double) -> CABasicAnimation {
234 | return createAnimation(keyPath: .opacity, toValue: NSNumber(floatLiteral: opacity))
235 | }
236 |
237 | /**
238 | Creates a CABasicaAnimation for the zPosition key path.
239 | - Parameter _ position: A CGFloat.
240 | - Returns: A CABasicAnimation.
241 | */
242 | static func zPosition(_ position: CGFloat) -> CABasicAnimation {
243 | return createAnimation(keyPath: .zPosition, toValue: NSNumber(value: Double(position)))
244 | }
245 |
246 | /**
247 | Creates a CABasicaAnimation for the width key path.
248 | - Parameter width: A CGFloat.
249 | - Returns: A CABasicAnimation.
250 | */
251 | static func width(_ width: CGFloat) -> CABasicAnimation {
252 | return createAnimation(keyPath: .width, toValue: NSNumber(floatLiteral: Double(width)))
253 | }
254 |
255 | /**
256 | Creates a CABasicaAnimation for the height key path.
257 | - Parameter height: A CGFloat.
258 | - Returns: A CABasicAnimation.
259 | */
260 | static func height(_ height: CGFloat) -> CABasicAnimation {
261 | return createAnimation(keyPath: .height, toValue: NSNumber(floatLiteral: Double(height)))
262 | }
263 |
264 | /**
265 | Creates a CABasicaAnimation for the height key path.
266 | - Parameter size: A CGSize.
267 | - Returns: A CABasicAnimation.
268 | */
269 | static func size(_ size: CGSize) -> CABasicAnimation {
270 | return createAnimation(keyPath: .size, toValue: NSValue(cgSize: size))
271 | }
272 |
273 | /**
274 | Creates a CABasicAnimation for the shadowPath key path.
275 | - Parameter path: A CGPath.
276 | - Returns: A CABasicAnimation.
277 | */
278 | static func shadow(path: CGPath) -> CABasicAnimation {
279 | return createAnimation(keyPath: .shadowPath, toValue: path)
280 | }
281 |
282 | /**
283 | Creates a CABasicAnimation for the shadowColor key path.
284 | - Parameter color: A UIColor.
285 | - Returns: A CABasicAnimation.
286 | */
287 | static func shadow(color: UIColor) -> CABasicAnimation {
288 | return createAnimation(keyPath: .shadowColor, toValue: color.cgColor)
289 | }
290 |
291 | /**
292 | Creates a CABasicAnimation for the shadowOffset key path.
293 | - Parameter offset: A CGSize.
294 | - Returns: A CABasicAnimation.
295 | */
296 | static func shadow(offset: CGSize) -> CABasicAnimation {
297 | return createAnimation(keyPath: .shadowOffset, toValue: NSValue(cgSize: offset))
298 | }
299 |
300 | /**
301 | Creates a CABasicAnimation for the shadowOpacity key path.
302 | - Parameter opacity: A Float.
303 | - Returns: A CABasicAnimation.
304 | */
305 | static func shadow(opacity: Float) -> CABasicAnimation {
306 | return createAnimation(keyPath: .shadowOpacity, toValue: NSNumber(floatLiteral: Double(opacity)))
307 | }
308 |
309 | /**
310 | Creates a CABasicAnimation for the shadowRadius key path.
311 | - Parameter radius: A CGFloat.
312 | - Returns: A CABasicAnimation.
313 | */
314 | static func shadow(radius: CGFloat) -> CABasicAnimation {
315 | return createAnimation(keyPath: .shadowRadius, toValue: NSNumber(floatLiteral: Double(radius)))
316 | }
317 | }
318 |
--------------------------------------------------------------------------------
/Sources/MotionCoordinateSpace.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | public enum MotionCoordinateSpace {
29 | case global
30 | case local
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/MotionPlugin.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | class MotionPlugin: MotionCorePreprocessor, MotionAnimator {
29 | /**
30 | Determines whether or not to receive `seekTo` callback on every frame.
31 |
32 | Default is false.
33 |
34 | When **requirePerFrameCallback** is **false**, the plugin needs to start its own animations inside `animate` & `resume`
35 | The `seekTo` method is only being called during an interactive transition.
36 |
37 | When **requirePerFrameCallback** is **true**, the plugin will receive `seekTo` callback on every animation frame. Hence it is possible for the plugin to do per-frame animations without implementing `animate` & `resume`
38 | */
39 | open var requirePerFrameCallback = false
40 |
41 | public override required init() {}
42 |
43 | /**
44 | Called before any animation.
45 | Override this method when you want to preprocess transitions for views
46 | - Parameters:
47 | - context: object holding all parsed and changed transitions,
48 | - fromViews: A flattened list of all views from source ViewController
49 | - toViews: A flattened list of all views from destination ViewController
50 |
51 | To check a view's transitions:
52 |
53 | context[view]
54 | context[view, "transitionName"]
55 |
56 | To set a view's transitions:
57 |
58 | context[view] = [("transition1", ["parameter1"]), ("transition2", [])]
59 | context[view, "transition1"] = ["parameter1", "parameter2"]
60 |
61 | */
62 | open override func process(fromViews: [UIView], toViews: [UIView]) {}
63 |
64 | /**
65 | - Returns: return true if the plugin can handle animating the view.
66 | - Parameters:
67 | - context: object holding all parsed and changed transitions,
68 | - view: the view to check whether or not the plugin can handle the animation
69 | - isAppearing: true if the view is isAppearing(i.e. a view in destination ViewController)
70 | If return true, Motion won't animate and won't let any other plugins animate this view.
71 | The view will also be hidden automatically during the animation.
72 | */
73 | open func canAnimate(view: UIView, isAppearing: Bool) -> Bool { return false }
74 |
75 | /**
76 | Perform the animation.
77 |
78 | Note: views in `fromViews` & `toViews` are hidden already. Unhide then if you need to take snapshots.
79 | - Parameters:
80 | - context: object holding all parsed and changed transitions,
81 | - fromViews: A flattened list of all views from source ViewController (filtered by `canAnimate`)
82 | - toViews: A flattened list of all views from destination ViewController (filtered by `canAnimate`)
83 | - Returns: The duration needed to complete the animation
84 | */
85 |
86 | open func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval { return 0 }
87 |
88 | /**
89 | Called when all animations are completed.
90 |
91 | Should perform cleanup and release any reference
92 | */
93 | open func clean() {}
94 |
95 | /**
96 | For supporting interactive animation only.
97 |
98 | This method is called when an interactive animation is in place
99 | The plugin should pause the animation, and seek to the given progress
100 | - Parameters:
101 | - progress: time of the animation to seek to.
102 | */
103 | open func seek(to progress: TimeInterval) {}
104 |
105 | /**
106 | For supporting interactive animation only.
107 |
108 | This method is called when an interactive animation is ended
109 | The plugin should resume the animation.
110 | - Parameters:
111 | - progress: will be the same value since last `seekTo`
112 | - reverse: a boolean value indicating whether or not the animation should reverse
113 | */
114 | open func resume(at progress: TimeInterval, isReversed: Bool) -> TimeInterval { return 0 }
115 |
116 | /**
117 | For supporting interactive animation only.
118 |
119 | This method is called when user wants to override animation transitions during an interactive animation
120 |
121 | - Parameters:
122 | - state: the target state to override
123 | - view: the view to override
124 | */
125 | open func apply(state: MotionTargetState, to view: UIView) {}
126 | }
127 |
128 | // methods for enable/disable the current plugin
129 | extension MotionPlugin {
130 | /// A boolean indicating whether plugins are available.
131 | public static var isEnabled: Bool {
132 | get {
133 | return MotionTransition.isEnabled(plugin: self)
134 | }
135 | set(value) {
136 | if value {
137 | enable()
138 | } else {
139 | disable()
140 | }
141 | }
142 | }
143 |
144 | /// Enable plugins.
145 | public static func enable() {
146 | MotionTransition.enable(plugin: self)
147 | }
148 |
149 | /// Disable plugins.
150 | public static func disable() {
151 | MotionTransition.disable(plugin: self)
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/Sources/MotionSnapshotType.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | public enum MotionSnapshotType {
29 | /**
30 | This setting will optimize for different types of views.
31 | For custom views or views with masking, .optimizedDefault might
32 | create snapshots that appear differently than the actual view.
33 | In that case, use .normal or .slowRender to disable the optimization.
34 | */
35 | case optimized
36 |
37 | /// snapshotView(afterScreenUpdates:)
38 | case normal
39 |
40 | /// layer.render(in: currentContext)
41 | case layerRender
42 |
43 | /**
44 | This setting will not create a snapshot. It will animate the view directly.
45 | This will mess up the view hierarchy, therefore, view controllers have to rebuild
46 | their view structure after the transition finishes.
47 | */
48 | case noSnapshot
49 | }
50 |
--------------------------------------------------------------------------------
/Sources/MotionTargetState.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | public struct MotionTargetState {
29 | /// The identifier value to match source and destination views.
30 | public var motionIdentifier: String?
31 |
32 | /// The initial state that the transition will start at.
33 | internal var beginState: [MotionModifier]?
34 |
35 | public var conditionalModifiers: [((MotionConditionalContext) -> Bool, [MotionModifier])]?
36 |
37 | /// A reference to the position.
38 | public var position: CGPoint?
39 |
40 | /// A reference to the size.
41 | public var size: CGSize?
42 |
43 | /// A reference to the transform.
44 | public var transform: CATransform3D?
45 |
46 | /// A reference to the opacity.
47 | public var opacity: Double?
48 |
49 | /// A reference to the cornerRadius.
50 | public var cornerRadius: CGFloat?
51 |
52 | /// A reference to the backgroundColor.
53 | public var backgroundColor: CGColor?
54 |
55 | /// A reference to the zPosition.
56 | public var zPosition: CGFloat?
57 |
58 | /// A reference to the contentsRect.
59 | public var contentsRect: CGRect?
60 |
61 | /// A reference to the contentsScale.
62 | public var contentsScale: CGFloat?
63 |
64 | /// A reference to the borderWidth.
65 | public var borderWidth: CGFloat?
66 |
67 | /// A reference to the borderColor.
68 | public var borderColor: CGColor?
69 |
70 | /// A reference to the shadowColor.
71 | public var shadowColor: CGColor?
72 |
73 | /// A reference to the shadowOpacity.
74 | public var shadowOpacity: Float?
75 |
76 | /// A reference to the shadowOffset.
77 | public var shadowOffset: CGSize?
78 |
79 | /// A reference to the shadowRadius.
80 | public var shadowRadius: CGFloat?
81 |
82 | /// A reference to the shadowPath.
83 | public var shadowPath: CGPath?
84 |
85 | /// A boolean for the masksToBounds state.
86 | public var masksToBounds: Bool?
87 |
88 | /// A boolean indicating whether to display a shadow or not.
89 | public var displayShadow = true
90 |
91 | /// A reference to the overlay settings.
92 | public var overlay: (color: CGColor, opacity: CGFloat)?
93 |
94 | /// A reference to the spring animation settings.
95 | public var spring: (CGFloat, CGFloat)?
96 |
97 | /// A time delay on starting the animation.
98 | public var delay: TimeInterval = 0
99 |
100 | /// The duration of the animation.
101 | public var duration: TimeInterval?
102 |
103 | /// The timing function value of the animation.
104 | public var timingFunction: CAMediaTimingFunction?
105 |
106 | /// The arc curve value.
107 | public var arc: CGFloat?
108 |
109 | /// The cascading animation settings.
110 | public var cascade: (TimeInterval, CascadeDirection, Bool)?
111 |
112 | /**
113 | A boolean indicating whether to ignore the subview transition
114 | animations or not.
115 | */
116 | public var ignoreSubviewTransitions: Bool?
117 |
118 | /// The coordinate space to transition views within.
119 | public var coordinateSpace: MotionCoordinateSpace?
120 |
121 | /// Change the size of a view based on a scale factor.
122 | public var useScaleBasedSizeChange: Bool?
123 |
124 | /// The type of snapshot to use.
125 | public var snapshotType: MotionSnapshotType?
126 |
127 | /// Do not fade the view when transitioning.
128 | public var nonFade = false
129 |
130 | /// Force an animation.
131 | public var forceAnimate = false
132 |
133 | /// Custom target states.
134 | public var custom: [String: Any]?
135 |
136 | /**
137 | An initializer that accepts an Array of MotionTargetStates.
138 | - Parameter transitions: An Array of MotionTargetStates.
139 | */
140 | init(modifiers: [MotionModifier]) {
141 | append(contentsOf: modifiers)
142 | }
143 | }
144 |
145 | extension MotionTargetState {
146 | /**
147 | Adds a MotionTargetState to the current state.
148 | - Parameter _ modifier: A MotionTargetState.
149 | */
150 | public mutating func append(_ modifier: MotionModifier) {
151 | modifier.apply(&self)
152 | }
153 |
154 | /**
155 | Adds an Array of MotionTargetStates to the current state.
156 | - Parameter contentsOf modifiers: An Array of MotionTargetStates.
157 | */
158 | public mutating func append(contentsOf modifiers: [MotionModifier]) {
159 | for v in modifiers {
160 | v.apply(&self)
161 | }
162 | }
163 |
164 | /**
165 | A subscript that returns a custom value for a specified key.
166 | - Parameter key: A String.
167 | - Returns: An optional Any value.
168 | */
169 | public subscript(key: String) -> Any? {
170 | get {
171 | return custom?[key]
172 | }
173 | set(value) {
174 | if nil == custom {
175 | custom = [:]
176 | }
177 |
178 | custom![key] = value
179 | }
180 | }
181 | }
182 |
183 | extension MotionTargetState: ExpressibleByArrayLiteral {
184 | /**
185 | An initializer implementing the ExpressibleByArrayLiteral protocol.
186 | - Parameter arrayLiteral elements: A list of MotionTargetStates.
187 | */
188 | public init(arrayLiteral elements: MotionModifier...) {
189 | append(contentsOf: elements)
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/Sources/MotionTransitionObserver.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import Foundation
27 |
28 | public protocol MotionTargetStateObserver {
29 | /**
30 | Executed when the elapsed time changes during a transition.
31 | - Parameter transitionObserver: A MotionTargetStateObserver.
32 | - Parameter didUpdateWith progress: A TimeInterval.
33 | */
34 | func motion(transitionObserver: MotionTargetStateObserver, didUpdateWith progress: TimeInterval)
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/MotionViewOrderStrategy.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import Foundation
27 |
28 | @objc(MotionViewOrderStrategy)
29 | public enum MotionViewOrderStrategy: Int {
30 | case auto
31 | case sourceViewOnTop
32 | case destinationViewOnTop
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/Sources/MotionViewTransition.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | private var MotionViewTransitionKey: UInt8 = 0
29 |
30 | public extension UIView {
31 | /// The MotionViewTransition instance associated with the view.
32 | var motionViewTransition: MotionViewTransition {
33 | get {
34 | return AssociatedObject.get(base: self, key: &MotionViewTransitionKey) {
35 | MotionViewTransition(self)
36 | }
37 | }
38 | }
39 | }
40 |
41 | open class MotionViewTransition {
42 | /// A MotionViewTransitionAnimator used during a transition.
43 | private let animator = MotionViewTransitionAnimator()
44 |
45 | /// A UIView whose subviews and itself will be animating during a transition.
46 | private weak var container: UIView?
47 |
48 | /// Maximum duration of the animations.
49 | open private(set) var totalDuration: TimeInterval = 0
50 |
51 | /// Progress of the current transition.
52 | open private(set) var progress: CGFloat = 0
53 |
54 | /// A Boolean to control if the models should be updated when animations end.
55 | open var shouldUpdateModels: Bool = true
56 |
57 | /// Current duration of the animations based on totalDuration and progress.
58 | private var currentDuration: TimeInterval {
59 | return totalDuration * Double(progress)
60 | }
61 |
62 | /**
63 | An initializer that accepts a container transition view.
64 | - Parameter container: A UIView.
65 | */
66 | fileprivate init(_ container: UIView) {
67 | self.container = container
68 | }
69 |
70 | /// Prepares the transition animations.
71 | open func start() {
72 | guard let v = container else {
73 | return
74 | }
75 |
76 | totalDuration = animator.animate(fromViews: v.flattenedViewHierarchy, toViews: [])
77 | update(0)
78 | }
79 |
80 | /**
81 | Updates the elapsed time for the transition.
82 | - Parameter progress: the current progress.
83 | */
84 | open func update(_ progress: CGFloat) {
85 | self.progress = progress.clamp(0, 1)
86 | animator.seek(to: currentDuration)
87 | }
88 |
89 | /**
90 | Cancels the interactive transition by animatin from the current state
91 | to the **beginning** state.
92 | - Parameter isAnimated: A boolean indicating if the completion is animated.
93 | */
94 | open func cancel(isAnimated: Bool = true) {
95 | end(isAnimated: isAnimated, isReversed: true)
96 | }
97 |
98 | /**
99 | Finishes the interactive transition by animating from the current state
100 | to the **end** state.
101 | - Parameter isAnimated: A Boolean indicating if the completion is animated.
102 | */
103 | open func finish(isAnimated: Bool = true) {
104 | end(isAnimated: isAnimated, isReversed: false)
105 | }
106 |
107 | deinit {
108 | clean()
109 | }
110 | }
111 |
112 | private extension MotionViewTransition {
113 | /**
114 | Ends the interactive transition by animating from the current state
115 | to the **beginning** or **end** state based on the value of isReversed.
116 | - Parameter isAnimated: A Boolean indicating if the completion is animated.
117 | - Parameter isReversed: A Boolean indicating the direction of completion.
118 | */
119 | func end(isAnimated: Bool, isReversed: Bool) {
120 | let duration = isAnimated ? currentDuration : isReversed ? 0 : totalDuration
121 |
122 | /// 0.00001 is to make sure that animator adds the animations on resume.
123 | let after = animator.resume(at: abs(duration - 0.00001), isReversed: isReversed)
124 | updateModels()
125 | if isAnimated {
126 | Motion.delay(after, execute: complete)
127 | } else {
128 | complete()
129 | }
130 | }
131 |
132 | /// Finalizes the transition.
133 | func complete() {
134 | removeAnimations()
135 | clean()
136 | }
137 |
138 | /// Resets the transition.
139 | func clean() {
140 | animator.clean()
141 | totalDuration = 0
142 | progress = 0
143 | }
144 | }
145 |
146 | private extension MotionViewTransition {
147 | /// Updates the layers with final values of animations.
148 | func updateModels() {
149 | guard shouldUpdateModels else {
150 | return
151 | }
152 |
153 | walkingThroughAnimations { layer, _, anim in
154 | /// bounds.size somehow is directly set on the layer.
155 | let toValue = anim.keyPath == "bounds.size" ? layer.bounds.size : anim.toValue
156 | layer.setValue(toValue, forKeyPath: anim.keyPath!)
157 | }
158 | }
159 |
160 | /// Removes added animations from layers.
161 | func removeAnimations() {
162 | guard shouldUpdateModels else {
163 | return
164 | }
165 |
166 | walkingThroughAnimations { layer, key, _ in
167 | layer.removeAnimation(forKey: key)
168 | }
169 | }
170 |
171 | /**
172 | Walks through each layer's animation and executes give closure with
173 | CALayer, animation key String, and CABasicAnimation.
174 | - Parameter execute: A closure accepting CALayer, String, and CABasicAnimation
175 | which is called for each layer's animation.
176 | */
177 | func walkingThroughAnimations(execute: (CALayer, String, CABasicAnimation) -> Void) {
178 | animator.viewToContexts.keys.forEach { v in
179 | v.layer.animations.forEach { key, animation in
180 | if let anim = animation as? CABasicAnimation {
181 | execute(v.layer, key, anim)
182 | }
183 | }
184 | }
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/Sources/Preprocessors/CascadePreprocessor.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | public enum CascadeDirection {
29 | case topToBottom
30 | case bottomToTop
31 | case leftToRight
32 | case rightToLeft
33 | case radial(center: CGPoint)
34 | case inverseRadial(center: CGPoint)
35 |
36 | /// Based on the cascade direction a comparator is set.
37 | var comparator: (UIView, UIView) -> Bool {
38 | switch self {
39 | case .topToBottom:
40 | return {
41 | return $0.frame.minY < $1.frame.minY
42 | }
43 |
44 | case .bottomToTop:
45 | return {
46 | return $0.frame.maxY == $1.frame.maxY ? $0.frame.maxX > $1.frame.maxX : $0.frame.maxY > $1.frame.maxY
47 | }
48 |
49 | case .leftToRight:
50 | return {
51 | return $0.frame.minX < $1.frame.minX
52 | }
53 |
54 | case .rightToLeft:
55 | return {
56 | return $0.frame.maxX > $1.frame.maxX
57 | }
58 |
59 | case .radial(let center):
60 | return {
61 | return $0.center.distance(center) < $1.center.distance(center)
62 | }
63 |
64 | case .inverseRadial(let center):
65 | return {
66 | return $0.center.distance(center) > $1.center.distance(center)
67 | }
68 | }
69 | }
70 | }
71 |
72 | class CascadePreprocessor: MotionCorePreprocessor {
73 | /**
74 | Processes the transitionary views.
75 | - Parameter fromViews: An Array of UIViews.
76 | - Parameter toViews: An Array of UIViews.
77 | */
78 | override func process(fromViews: [UIView], toViews: [UIView]) {
79 | process(views: fromViews)
80 | process(views: toViews)
81 | }
82 |
83 | /**
84 | Process an Array of views for the cascade animation.
85 | - Parameter views: An Array of UIViews.
86 | */
87 | func process(views: [UIView]) {
88 | for v in views {
89 | guard let (deltaTime, direction, delayMatchedViews) = context[v]?.cascade else {
90 | continue
91 | }
92 |
93 | var parentView = v
94 |
95 | if v is UITableView, let wrapperView = v.subviews.get(0) {
96 | parentView = wrapperView
97 | }
98 |
99 | let sortedSubviews = parentView.subviews.sorted(by: direction.comparator)
100 |
101 | let initialDelay = context[v]!.delay
102 | let finalDelay = TimeInterval(sortedSubviews.count) * deltaTime + initialDelay
103 |
104 | for (i, subview) in sortedSubviews.enumerated() {
105 | let delay = TimeInterval(i) * deltaTime + initialDelay
106 |
107 | func applyDelay(view: UIView) {
108 | if nil == context.pairedView(for: view) {
109 | context[view]?.delay = delay
110 |
111 | } else if delayMatchedViews, let paired = context.pairedView(for: view) {
112 | context[view]?.delay = finalDelay
113 | context[paired]?.delay = finalDelay
114 | }
115 |
116 | for subview in view.subviews {
117 | applyDelay(view: subview)
118 | }
119 | }
120 |
121 | applyDelay(view: subview)
122 | }
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/Sources/Preprocessors/ConditionalPreprocessor.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | public struct MotionConditionalContext {
29 | internal weak var motion: MotionTransition!
30 | public weak var view: UIView!
31 |
32 | public private(set) var isAppearing: Bool
33 |
34 | public var isPresenting: Bool {
35 | return motion.isPresenting
36 | }
37 |
38 | public var isTabBarController: Bool {
39 | return motion.isTabBarController
40 | }
41 |
42 | public var isNavigationController: Bool {
43 | return motion.isNavigationController
44 | }
45 |
46 | public var isMatched: Bool {
47 | return nil != matchedView
48 | }
49 |
50 | public var isAncestorViewMatched: Bool {
51 | return nil != matchedAncestorView
52 | }
53 |
54 | public var matchedView: UIView? {
55 | return motion.context.pairedView(for: view)
56 | }
57 |
58 | public var matchedAncestorView: (UIView, UIView)? {
59 | var current = view.superview
60 |
61 | while let ancestor = current, ancestor != motion.context.container {
62 | if let pairedView = motion.context.pairedView(for: ancestor) {
63 | return (ancestor, pairedView)
64 | }
65 |
66 | current = ancestor.superview
67 | }
68 |
69 | return nil
70 | }
71 |
72 | public var fromViewController: UIViewController {
73 | return motion.fromViewController!
74 | }
75 |
76 | public var toViewController: UIViewController {
77 | return motion.toViewController!
78 | }
79 |
80 | public var currentViewController: UIViewController {
81 | return isAppearing ? toViewController : fromViewController
82 | }
83 |
84 | public var otherViewController: UIViewController {
85 | return isAppearing ? fromViewController : toViewController
86 | }
87 | }
88 |
89 | class ConditionalPreprocessor: MotionCorePreprocessor {
90 | override func process(fromViews: [UIView], toViews: [UIView]) {
91 | process(views: fromViews, isAppearing: false)
92 | process(views: toViews, isAppearing: true)
93 | }
94 |
95 | func process(views: [UIView], isAppearing: Bool) {
96 | for v in views {
97 | guard let conditionalModifiers = context[v]?.conditionalModifiers else {
98 | continue
99 | }
100 |
101 | for (condition, modifiers) in conditionalModifiers {
102 | if condition(MotionConditionalContext(motion: motion, view: v, isAppearing: isAppearing)) {
103 | context[v]!.append(contentsOf: modifiers)
104 | }
105 | }
106 | }
107 | }
108 | }
109 |
110 |
--------------------------------------------------------------------------------
/Sources/Preprocessors/IgnoreSubviewModifiersPreprocessor.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | class IgnoreSubviewTransitionsPreprocessor: MotionCorePreprocessor {
29 | /**
30 | Processes the transitionary views.
31 | - Parameter fromViews: An Array of UIViews.
32 | - Parameter toViews: An Array of UIViews.
33 | */
34 | override func process(fromViews: [UIView], toViews: [UIView]) {
35 | process(views: fromViews)
36 | process(views: toViews)
37 | }
38 |
39 | /**
40 | Process an Array of views for the cascade animation.
41 | - Parameter views: An Array of UIViews.
42 | */
43 | func process(views: [UIView]) {
44 | for v in views {
45 | guard let recursive = context[v]?.ignoreSubviewTransitions else {
46 | continue
47 | }
48 |
49 | var parentView = v
50 |
51 | if v is UITableView, let wrapperView = v.subviews.get(0) {
52 | parentView = wrapperView
53 | }
54 |
55 | guard recursive else {
56 | for subview in parentView.subviews {
57 | context[subview] = nil
58 | }
59 |
60 | continue
61 | }
62 |
63 | cleanSubviewModifiers(for: parentView)
64 | }
65 | }
66 | }
67 |
68 | fileprivate extension IgnoreSubviewTransitionsPreprocessor {
69 | /**
70 | Clears the modifiers for a given view's subviews.
71 | - Parameter for view: A UIView.
72 | */
73 | func cleanSubviewModifiers(for view: UIView) {
74 | for v in view.subviews {
75 | context[v] = nil
76 | cleanSubviewModifiers(for: v)
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Sources/Preprocessors/MatchPreprocessor.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | class MatchPreprocessor: MotionCorePreprocessor {
29 | /**
30 | Processes the transitionary views.
31 | - Parameter fromViews: An Array of UIViews.
32 | - Parameter toViews: An Array of UIViews.
33 | */
34 | override func process(fromViews: [UIView], toViews: [UIView]) {
35 | for tv in toViews {
36 | guard let motionIdentifier = tv.motionIdentifier, let fv = context.sourceView(for: motionIdentifier) else {
37 | continue
38 | }
39 |
40 | var tvState = context[tv] ?? MotionTargetState()
41 | var fvState = context[fv] ?? MotionTargetState()
42 |
43 | // match is just a two-way source effect
44 | tvState.motionIdentifier = motionIdentifier
45 | fvState.motionIdentifier = motionIdentifier
46 |
47 | fvState.arc = tvState.arc
48 | fvState.duration = tvState.duration
49 | fvState.timingFunction = tvState.timingFunction
50 | fvState.delay = tvState.delay
51 | fvState.spring = tvState.spring
52 |
53 | let forceNonFade = tvState.nonFade || fvState.nonFade
54 | let isNonOpaque = !fv.isOpaque || fv.alpha < 1 || !tv.isOpaque || tv.alpha < 1
55 |
56 | if context.insertToViewFirst {
57 | fvState.opacity = 0
58 |
59 | if !forceNonFade && isNonOpaque {
60 | tvState.opacity = 0
61 |
62 | } else {
63 | tvState.opacity = nil
64 |
65 | if !tv.layer.masksToBounds && tvState.displayShadow {
66 | fvState.displayShadow = false
67 | }
68 | }
69 |
70 | } else {
71 | tvState.opacity = 0
72 |
73 | if !forceNonFade && isNonOpaque {
74 | // cross fade if from/toViews are not opaque
75 | fvState.opacity = 0
76 | } else {
77 | // no cross fade in this case, fromView is always displayed during the transition.
78 | fvState.opacity = nil
79 |
80 | // we dont want two shadows showing up. Therefore we disable toView's shadow when fromView is able to display its shadow
81 | if !fv.layer.masksToBounds && fvState.displayShadow {
82 | tvState.displayShadow = false
83 | }
84 | }
85 | }
86 |
87 | context[tv] = tvState
88 | context[fv] = fvState
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/Sources/Preprocessors/MotionCorePreprocessor.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | class MotionCorePreprocessor: MotionPreprocessor {
29 | weak public var motion: MotionTransition!
30 |
31 | /// A reference to the MotionContext.
32 | public var context: MotionContext! {
33 | return motion!.context
34 | }
35 |
36 | func process(fromViews: [UIView], toViews: [UIView]) {}
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/Sources/Preprocessors/MotionPreprocessor.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | public protocol MotionPreprocessor: class {
29 | /// A reference to Motion.
30 | var motion: MotionTransition! { get set }
31 |
32 | /**
33 | Processes the transitionary views.
34 | - Parameter fromViews: An Array of UIViews.
35 | - Parameter toViews: An Array of UIViews.
36 | */
37 | func process(fromViews: [UIView], toViews: [UIView])
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/Preprocessors/SourcePreprocessor.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | class SourcePreprocessor: MotionCorePreprocessor {
29 | /**
30 | Processes the transitionary views.
31 | - Parameter fromViews: An Array of UIViews.
32 | - Parameter toViews: An Array of UIViews.
33 | */
34 | override func process(fromViews: [UIView], toViews: [UIView]) {
35 | for fv in fromViews {
36 | guard let motionIdentifier = context[fv]?.motionIdentifier, let tv = context.destinationView(for: motionIdentifier) else {
37 | continue
38 | }
39 |
40 | prepare(view: fv, for: tv)
41 | }
42 |
43 | for tv in toViews {
44 | guard let motionIdentifier = context[tv]?.motionIdentifier, let fv = context.sourceView(for: motionIdentifier) else {
45 | continue
46 | }
47 |
48 | prepare(view: tv, for: fv)
49 | }
50 | }
51 | }
52 |
53 | fileprivate extension SourcePreprocessor {
54 | /**
55 | Prepares a given view for a target view.
56 | - Parameter view: A UIView.
57 | - Parameter for targetView: A UIView.
58 | */
59 | func prepare(view: UIView, for targetView: UIView) {
60 | let targetPos = context.container.convert(targetView.layer.position, from: targetView.superview!)
61 | let targetTransform = context.container.layer.flatTransformTo(layer: targetView.layer)
62 |
63 | var state = context[view]!
64 |
65 | /**
66 | Use global coordinate space since over target position is
67 | converted from the global container
68 | */
69 | state.coordinateSpace = .global
70 |
71 | state.position = targetPos
72 | state.transform = targetTransform
73 |
74 | // Remove incompatible options.
75 | state.size = nil
76 |
77 | if view.bounds.size != targetView.bounds.size {
78 | state.size = targetView.bounds.size
79 | }
80 |
81 | if view.layer.cornerRadius != targetView.layer.cornerRadius {
82 | state.cornerRadius = targetView.layer.cornerRadius
83 | }
84 |
85 | if view.layer.shadowColor != targetView.layer.shadowColor {
86 | state.shadowColor = targetView.layer.shadowColor
87 | }
88 |
89 | if view.layer.shadowOpacity != targetView.layer.shadowOpacity {
90 | state.shadowOpacity = targetView.layer.shadowOpacity
91 | }
92 |
93 | if view.layer.shadowOffset != targetView.layer.shadowOffset {
94 | state.shadowOffset = targetView.layer.shadowOffset
95 | }
96 |
97 | if view.layer.shadowRadius != targetView.layer.shadowRadius {
98 | state.shadowRadius = targetView.layer.shadowRadius
99 | }
100 |
101 | if view.layer.shadowPath != targetView.layer.shadowPath {
102 | state.shadowPath = targetView.layer.shadowPath
103 | }
104 |
105 | if view.layer.contentsRect != targetView.layer.contentsRect {
106 | state.contentsRect = targetView.layer.contentsRect
107 | }
108 |
109 | if view.layer.contentsScale != targetView.layer.contentsScale {
110 | state.contentsScale = targetView.layer.contentsScale
111 | }
112 |
113 | context[view] = state
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/Sources/Transition/MotionProgressRunner.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | protocol MotionProgressRunnerDelegate: class {
29 | func update(progress: TimeInterval)
30 | func complete(isFinishing: Bool)
31 | }
32 |
33 | class MotionProgressRunner {
34 | weak var delegate: MotionProgressRunnerDelegate?
35 |
36 | var isRunning: Bool {
37 | return nil != displayLink
38 | }
39 |
40 | internal var progress: TimeInterval = 0
41 | internal var duration: TimeInterval = 0
42 | internal var displayLink: CADisplayLink?
43 | internal var isReversed = false
44 |
45 | @objc
46 | func displayUpdate(_ link: CADisplayLink) {
47 | progress += isReversed ? -link.duration : link.duration
48 |
49 | if isReversed, progress <= 1.0 / 120 {
50 | delegate?.complete(isFinishing: false)
51 | stop()
52 | return
53 | }
54 |
55 | if !isReversed, progress > duration - 1.0 / 120 {
56 | delegate?.complete(isFinishing: true)
57 | stop()
58 | return
59 | }
60 |
61 | delegate?.update(progress: progress / duration)
62 | }
63 |
64 | func start(progress: TimeInterval, duration: TimeInterval, isReversed: Bool) {
65 | stop()
66 |
67 | self.progress = progress
68 | self.isReversed = isReversed
69 | self.duration = duration
70 |
71 | displayLink = CADisplayLink(target: self, selector: #selector(displayUpdate(_:)))
72 | displayLink!.add(to: RunLoop.main, forMode: RunLoop.Mode(rawValue: RunLoop.Mode.common.rawValue))
73 | }
74 |
75 | func stop() {
76 | displayLink?.isPaused = true
77 | displayLink?.remove(from: RunLoop.main, forMode: RunLoop.Mode(rawValue: RunLoop.Mode.common.rawValue))
78 | displayLink = nil
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Sources/Transition/MotionTransition+Animate.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | extension MotionTransition {
29 | /// Starts the transition animation.
30 | func animate() {
31 | guard .starting == state else {
32 | return
33 | }
34 |
35 | state = .animating
36 |
37 | if let tv = toView {
38 | context.unhide(view: tv)
39 | }
40 |
41 | for v in animatingFromViews {
42 | context.hide(view: v)
43 | }
44 |
45 | for v in animatingToViews {
46 | context.hide(view: v)
47 | }
48 |
49 | var t: TimeInterval = 0
50 | var animatorWantsInteractive = false
51 |
52 | if context.insertToViewFirst {
53 | for v in animatingToViews {
54 | context.snapshotView(for: v)
55 | }
56 |
57 | for v in animatingFromViews {
58 | context.snapshotView(for: v)
59 | }
60 |
61 | } else {
62 | for v in animatingFromViews {
63 | context.snapshotView(for: v)
64 | }
65 |
66 | for v in animatingToViews {
67 | context.snapshotView(for: v)
68 | }
69 | }
70 |
71 | // UIKit appears to set fromView setNeedLayout to be true.
72 | // We don't want fromView to layout after our animation starts.
73 | // Therefore we kick off the layout beforehand
74 | fromView?.layoutIfNeeded()
75 | for animator in animators {
76 | let duration = animator.animate(fromViews: animatingFromViews.filter {
77 | return animator.canAnimate(view: $0, isAppearing: false)
78 | }, toViews: animatingToViews.filter {
79 | return animator.canAnimate(view: $0, isAppearing: true)
80 | })
81 |
82 | if .infinity == duration {
83 | animatorWantsInteractive = true
84 | } else {
85 | t = max(t, duration)
86 | }
87 | }
88 |
89 | totalDuration = t
90 |
91 | if let forceFinishing = forceFinishing {
92 | complete(isFinishing: forceFinishing)
93 | } else if let startingProgress = startingProgress {
94 | update(startingProgress)
95 | } else if animatorWantsInteractive {
96 | update(0)
97 | } else {
98 | complete(after: totalDuration, isFinishing: true)
99 | }
100 |
101 | fullScreenSnapshot?.removeFromSuperview()
102 | }
103 | }
104 |
105 |
--------------------------------------------------------------------------------
/Sources/Transition/MotionTransition+Complete.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | extension MotionTransition {
29 | /**
30 | Complete the transition.
31 | - Parameter isFinishing: A Boolean indicating if the transition
32 | has completed.
33 | */
34 | @objc
35 | func complete(isFinishing: Bool) {
36 | if state == .notified {
37 | forceFinishing = isFinishing
38 | }
39 |
40 | guard .animating == state || .starting == state else {
41 | return
42 | }
43 |
44 | defer {
45 | transitionContext = nil
46 | fromViewController = nil
47 | toViewController = nil
48 | transitioningViewController = nil
49 | forceNonInteractive = false
50 | animatingToViews.removeAll()
51 | animatingFromViews.removeAll()
52 | transitionObservers = nil
53 | transitionContainer = nil
54 | completionCallback = nil
55 | forceFinishing = nil
56 | container = nil
57 | startingProgress = nil
58 | preprocessors.removeAll()
59 | animators.removeAll()
60 | plugins.removeAll()
61 | context = nil
62 | progress = 0
63 | totalDuration = 0
64 | state = .possible
65 | defaultAnimation = .auto
66 | containerBackgroundColor = .black
67 | isModalTransition = false
68 | }
69 |
70 | state = .completing
71 |
72 | progressRunner.stop()
73 | context.clean()
74 |
75 | if let tv = toView, let fv = fromView {
76 | if isFinishing && isPresenting && toOverFullScreen {
77 | // finished presenting a overFullScreen view controller.
78 | context.unhide(rootView: tv)
79 | context.removeSnapshots(rootView: tv)
80 | context.storeViewAlpha(rootView: fv)
81 |
82 | fromViewController!.motionStoredSnapshot = container
83 | fv.removeFromSuperview()
84 | fv.addSubview(container)
85 |
86 | } else if !isFinishing && !isPresenting && fromOverFullScreen {
87 | // Cancelled dismissing a overFullScreen view controller.
88 | context.unhide(rootView: fv)
89 | context.removeSnapshots(rootView: fv)
90 | context.storeViewAlpha(rootView: tv)
91 |
92 | toViewController!.motionStoredSnapshot = container
93 | container.superview?.addSubview(tv)
94 | tv.addSubview(container)
95 |
96 | } else {
97 | context.unhideAll()
98 | context.removeAllSnapshots()
99 | }
100 |
101 | // Move fromView & toView back from our container back to the one supplied by UIKit.
102 | if (toOverFullScreen && isFinishing) || (fromOverFullScreen && !isFinishing) {
103 | transitionContainer?.addSubview(isFinishing ? fv : tv)
104 | }
105 |
106 | transitionContainer?.addSubview(isFinishing ? tv : fv)
107 |
108 | if isPresenting != isFinishing, !isContainerController {
109 | // Only happens when present a .overFullScreen view controller.
110 | // bug: http://openradar.appspot.com/radar?id=5320103646199808
111 | Application.shared.keyWindow?.addSubview(isPresenting ? fv : tv)
112 | }
113 | }
114 |
115 | if container.superview == transitionContainer {
116 | container.removeFromSuperview()
117 | }
118 |
119 | for a in animators {
120 | a.clean()
121 | }
122 |
123 | transitionContainer?.isUserInteractionEnabled = true
124 | transitioningViewController?.view.isUserInteractionEnabled = true
125 |
126 | completionCallback?(isFinishing)
127 |
128 | if isFinishing {
129 | toViewController?.tabBarController?.tabBar.layer.removeAllAnimations()
130 | } else {
131 | fromViewController?.tabBarController?.tabBar.layer.removeAllAnimations()
132 | }
133 |
134 | let tContext = transitionContext
135 | let fvc = fromViewController
136 | let tvc = toViewController
137 |
138 | if isFinishing {
139 | processEndTransitionDelegation(transitionContext: tContext, fromViewController: fvc, toViewController: tvc)
140 | } else {
141 | processCancelTransitionDelegation(transitionContext: tContext, fromViewController: fvc, toViewController: tvc)
142 | }
143 |
144 | tContext?.completeTransition(isFinishing)
145 |
146 | let isModalDismissal = isModalTransition && !isPresenting
147 | if isModalDismissal {
148 | Application.shared.fixRootViewY()
149 | }
150 | }
151 | }
152 |
153 |
154 | private extension UIApplication {
155 | /**
156 | When in-call, hotspot, or recording status bar is enabled, just after (custom) modal
157 | dismissal transition animation ends `UITransitionView` is removed from the hierarchy
158 | and that removal was moving `rootViewController.view` 20 points upwards. This function
159 | should be called after transitioningContext.completeTransition(_:) upon modal dismissal
160 | transition. It applies the work that `UITransitionView` should ideally have done after
161 | custom modal dismissal. `UIKit` modal dismissals do not suffer from this.
162 |
163 | Fixes issue-44. See issue-44 for more info.
164 | */
165 | func fixRootViewY() {
166 | guard statusBarFrame.height == 40, let window = keyWindow, let vc = window.rootViewController else {
167 | return
168 | }
169 |
170 | if vc.view.frame.maxY + 20 == window.frame.height {
171 | vc.view.frame.origin.y += 20
172 | }
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/Sources/Transition/MotionTransition+CustomTransition.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | extension MotionTransition {
29 | /**
30 | A helper transition function.
31 | - Parameter from: A UIViewController.
32 | - Parameter to: A UIViewController.
33 | - Parameter in view: A UIView.
34 | - Parameter completion: An optional completion handler.
35 | */
36 | public func transition(from: UIViewController, to: UIViewController, in view: UIView, completion: ((Bool) -> Void)? = nil) {
37 | guard !isTransitioning else {
38 | return
39 | }
40 |
41 | state = .notified
42 | isPresenting = true
43 | transitionContainer = view
44 | fromViewController = from
45 | toViewController = to
46 |
47 | setCompletionCallbackForNextTransition(completion)
48 | start()
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/Transition/MotionTransition+Interactive.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | public extension MotionTransition {
29 | /**
30 | Updates the elapsed time for the interactive transition.
31 | - Parameter progress t: the current progress, must be between -1...1.
32 | */
33 | func update(_ percentageComplete: TimeInterval) {
34 | guard .animating == state else {
35 | startingProgress = percentageComplete
36 | return
37 | }
38 |
39 | progressRunner.stop()
40 | progress = Double(CGFloat(percentageComplete).clamp(0, 1))
41 | }
42 |
43 | /**
44 | Finish the interactive transition.
45 | Will stop the interactive transition and animate from the
46 | current state to the **end** state.
47 | - Parameter isAnimated: A Boolean.
48 | */
49 | func finish(isAnimated: Bool = true) {
50 | guard .animating == state || .notified == state || .starting == state else {
51 | return
52 | }
53 |
54 | guard isAnimated else {
55 | complete(isFinishing: true)
56 | return
57 | }
58 |
59 | var d: TimeInterval = 0
60 |
61 | for a in animators {
62 | d = max(d, a.resume(at: progress * totalDuration, isReversed: false))
63 | }
64 |
65 | complete(after: d, isFinishing: true)
66 | }
67 |
68 | /**
69 | Cancel the interactive transition.
70 | Will stop the interactive transition and animate from the
71 | current state to the **begining** state
72 | - Parameter isAnimated: A boolean indicating if the completion is animated.
73 | */
74 | func cancel(isAnimated: Bool = true) {
75 | guard .animating == state || .notified == state || .starting == state else {
76 | return
77 | }
78 |
79 | guard isAnimated else {
80 | complete(isFinishing: false)
81 | return
82 | }
83 |
84 | var d: TimeInterval = 0
85 |
86 | for a in animators {
87 | var t = progress
88 | if t < 0 {
89 | t = -t
90 | }
91 |
92 | d = max(d, a.resume(at: t * totalDuration, isReversed: true))
93 | }
94 |
95 | complete(after: d, isFinishing: false)
96 | }
97 |
98 | /**
99 | Override transition animations during an interactive animation.
100 |
101 | For example:
102 |
103 | Motion.shared.apply([.position(x:50, y:50)], to: view)
104 |
105 | will set the view's position to 50, 50
106 | - Parameter modifiers: An Array of MotionModifier.
107 | - Parameter to view: A UIView.
108 | */
109 | func apply(modifiers: [MotionModifier], to view: UIView) {
110 | guard .animating == state else {
111 | return
112 | }
113 |
114 | let targetState = MotionTargetState(modifiers: modifiers)
115 | if let otherView = context.pairedView(for: view) {
116 | for animator in animators {
117 | animator.apply(state: targetState, to: otherView)
118 | }
119 | }
120 |
121 | for animator in self.animators {
122 | animator.apply(state: targetState, to: view)
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/Sources/Transition/MotionTransition+Start.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | extension MotionTransition {
29 | /// Starts the transition animation.
30 | func start() {
31 | guard .notified == state else {
32 | return
33 | }
34 |
35 | state = .starting
36 |
37 | prepareViewFrame()
38 | prepareViewControllers()
39 | prepareSnapshotView()
40 | preparePlugins()
41 | preparePreprocessors()
42 | prepareAnimators()
43 |
44 | for v in plugins {
45 | preprocessors.append(v)
46 | animators.append(v)
47 | }
48 |
49 | prepareTransitionContainer()
50 | prepareContainer()
51 | prepareContext()
52 |
53 | prepareViewHierarchy()
54 | prepareAnimatingViews()
55 | prepareToView()
56 |
57 | processPreprocessors()
58 | processAnimation()
59 | }
60 | }
61 |
62 | fileprivate extension MotionTransition {
63 | /// Prepares the views frames.
64 | func prepareViewFrame() {
65 | guard let fv = fromView else {
66 | return
67 | }
68 |
69 | guard let tv = toView else {
70 | return
71 | }
72 |
73 | if let toViewController = toViewController, let transitionContext = transitionContext {
74 | tv.frame = transitionContext.finalFrame(for: toViewController)
75 | } else {
76 | tv.frame = fv.frame
77 | }
78 |
79 | tv.setNeedsLayout()
80 | tv.layoutIfNeeded()
81 | }
82 |
83 | /// Prepares the from and to view controllers.
84 | func prepareViewControllers() {
85 | processStartTransitionDelegation(transitionContext: transitionContext, fromViewController: fromViewController, toViewController: toViewController)
86 | }
87 |
88 | /// Prepares the snapshot view, which hides any flashing that may occur.
89 | func prepareSnapshotView() {
90 | fullScreenSnapshot = transitionContainer?.window?.snapshotView(afterScreenUpdates: false) ?? fromView?.snapshotView(afterScreenUpdates: false)
91 |
92 | if let v = fullScreenSnapshot {
93 | (transitionContainer?.window ?? transitionContainer)?.addSubview(v)
94 | }
95 |
96 | if let v = fromViewController?.motionStoredSnapshot {
97 | v.removeFromSuperview()
98 | fromViewController?.motionStoredSnapshot = nil
99 | }
100 |
101 | if let v = toViewController?.motionStoredSnapshot {
102 | v.removeFromSuperview()
103 | toViewController?.motionStoredSnapshot = nil
104 | }
105 | }
106 |
107 | /// Prepares the plugins.
108 | func preparePlugins() {
109 | plugins = MotionTransition.enabledPlugins.map {
110 | return $0.init()
111 | }
112 | }
113 |
114 | /// Prepares the preprocessors.
115 | func preparePreprocessors() {
116 | preprocessors = [IgnoreSubviewTransitionsPreprocessor(),
117 | ConditionalPreprocessor(),
118 | TransitionPreprocessor(),
119 | MatchPreprocessor(),
120 | SourcePreprocessor(),
121 | CascadePreprocessor()]
122 | }
123 |
124 | /// Prepares the animators.
125 | func prepareAnimators() {
126 | animators = [MotionCoreAnimator()]
127 |
128 | if #available(iOS 10, tvOS 10, *) {
129 | animators.append(MotionCoreAnimator())
130 | }
131 | }
132 |
133 | /// Prepares the transition container.
134 | func prepareTransitionContainer() {
135 | transitioningViewController?.view.isUserInteractionEnabled = isUserInteractionEnabled
136 | transitionContainer?.isUserInteractionEnabled = isUserInteractionEnabled
137 | }
138 |
139 | /// Prepares the view that holds all the animating views.
140 | func prepareContainer() {
141 | container = UIView(frame: transitionContainer?.bounds ?? .zero)
142 |
143 | if !toOverFullScreen && !fromOverFullScreen {
144 | container.backgroundColor = containerBackgroundColor
145 | }
146 |
147 | transitionContainer?.addSubview(container)
148 | }
149 |
150 | /// Prepares the MotionContext instance.
151 | func prepareContext() {
152 | context = MotionContext(container: container)
153 |
154 | for v in preprocessors {
155 | v.motion = self
156 | }
157 |
158 | for v in animators {
159 | v.motion = self
160 | }
161 |
162 | guard let tv = toView else {
163 | return
164 | }
165 |
166 | guard let fv = fromView else {
167 | return
168 | }
169 |
170 | context.loadViewAlpha(rootView: tv)
171 | container.addSubview(tv)
172 |
173 | context.loadViewAlpha(rootView: fv)
174 | container.addSubview(fv)
175 |
176 | tv.setNeedsUpdateConstraints()
177 | tv.updateConstraintsIfNeeded()
178 | tv.setNeedsLayout()
179 | tv.layoutIfNeeded()
180 |
181 | context.set(fromViews: fv.flattenedViewHierarchy, toViews: tv.flattenedViewHierarchy)
182 | }
183 |
184 | /// Prepares the view hierarchy.
185 | func prepareViewHierarchy() {
186 | if (.auto == viewOrderStrategy && !isPresenting && !isTabBarController) ||
187 | .sourceViewOnTop == viewOrderStrategy {
188 | context.insertToViewFirst = true
189 | }
190 |
191 | for v in preprocessors {
192 | v.process(fromViews: context.fromViews, toViews: context.toViews)
193 | }
194 | }
195 |
196 | /// Prepares the transition fromView & toView pairs.
197 | func prepareAnimatingViews() {
198 | animatingFromViews = context.fromViews.filter { (view) -> Bool in
199 | for animator in animators {
200 | if animator.canAnimate(view: view, isAppearing: false) {
201 | return true
202 | }
203 | }
204 | return false
205 | }
206 |
207 | animatingToViews = context.toViews.filter { (view) -> Bool in
208 | for animator in animators {
209 | if animator.canAnimate(view: view, isAppearing: true) {
210 | return true
211 | }
212 | }
213 | return false
214 | }
215 | }
216 |
217 | /// Prepares the to view.
218 | func prepareToView() {
219 | guard let tv = toView else {
220 | return
221 | }
222 |
223 | context.hide(view: tv)
224 | }
225 | }
226 |
227 | fileprivate extension MotionTransition {
228 | /// Executes the preprocessors' process function.
229 | func processPreprocessors() {
230 | for v in preprocessors {
231 | v.process(fromViews: context.fromViews, toViews: context.toViews)
232 | }
233 | }
234 |
235 | /// Processes the animations.
236 | func processAnimation() {
237 | #if os(tvOS)
238 | animate()
239 |
240 | #else
241 | if isNavigationController {
242 | // When animating within navigationController, we have to dispatch later into the main queue.
243 | // otherwise snapshots will be pure white. Possibly a bug with UIKit
244 | Motion.async { [weak self] in
245 | self?.animate()
246 | }
247 |
248 | } else {
249 | animate()
250 | }
251 |
252 | #endif
253 | }
254 | }
255 |
256 |
--------------------------------------------------------------------------------
/Sources/Transition/MotionTransition+UINavigationControllerDelegate.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | extension MotionTransition: UINavigationControllerDelegate {
29 | public func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
30 | guard !isTransitioning else {
31 | return nil
32 | }
33 |
34 | state = .notified
35 | isPresenting = .push == operation
36 | fromViewController = fromViewController ?? fromVC
37 | toViewController = toViewController ?? toVC
38 | transitioningViewController = navigationController
39 |
40 | return self
41 | }
42 |
43 | public func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
44 | return interactiveTransitioning
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Sources/Transition/MotionTransition+UITabBarControllerDelegate.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | extension MotionTransition: UITabBarControllerDelegate {
29 | public func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
30 |
31 | guard false != tabBarController.previousTabBarDelegate?.tabBarController?(tabBarController, shouldSelect: viewController) else {
32 | return false
33 | }
34 |
35 | if isTransitioning {
36 | cancel(isAnimated: false)
37 | }
38 |
39 | return true
40 | }
41 |
42 | public func tabBarController(_ tabBarController: UITabBarController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
43 | return interactiveTransitioning
44 | }
45 |
46 | public func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
47 | guard !isTransitioning else {
48 | return nil
49 | }
50 |
51 | state = .notified
52 |
53 | let fromVCIndex = tabBarController.children.firstIndex(of: fromVC)!
54 | let toVCIndex = tabBarController.children.firstIndex(of: toVC)!
55 |
56 | isPresenting = toVCIndex > fromVCIndex
57 | fromViewController = fromViewController ?? fromVC
58 | toViewController = toViewController ?? toVC
59 | transitioningViewController = tabBarController
60 |
61 | return self
62 | }
63 | }
64 |
65 |
--------------------------------------------------------------------------------
/Sources/Transition/MotionTransition+UIViewControllerTransitioningDelegate.swift:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (C) 2019, CosmicMind, Inc. .
5 | * All rights reserved.
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in
15 | * all copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 | * THE SOFTWARE.
24 | */
25 |
26 | import UIKit
27 |
28 | extension MotionTransition: UIViewControllerTransitioningDelegate {
29 | /// A reference to the interactive transitioning instance.
30 | var interactiveTransitioning: UIViewControllerInteractiveTransitioning? {
31 | return forceNonInteractive ? nil : self
32 | }
33 |
34 | public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
35 | guard !isTransitioning else {
36 | return nil
37 | }
38 |
39 | state = .notified
40 | isPresenting = true
41 | isModalTransition = true
42 | fromViewController = fromViewController ?? presenting
43 | toViewController = toViewController ?? presented
44 |
45 | return self
46 | }
47 |
48 | public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
49 | guard !isTransitioning else {
50 | return nil
51 | }
52 |
53 | state = .notified
54 | isPresenting = false
55 | isModalTransition = true
56 | fromViewController = fromViewController ?? dismissed
57 | return self
58 | }
59 |
60 | public func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
61 | return interactiveTransitioning
62 | }
63 |
64 | public func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
65 | return interactiveTransitioning
66 | }
67 | }
68 |
69 | extension MotionTransition: UIViewControllerAnimatedTransitioning {
70 | /**
71 | The animation method that is used to coordinate the transition.
72 | - Parameter using transitionContext: A UIViewControllerContextTransitioning.
73 | */
74 | public func animateTransition(using context: UIViewControllerContextTransitioning) {
75 | transitionContext = context
76 | fromViewController = fromViewController ?? context.viewController(forKey: .from)
77 | toViewController = toViewController ?? context.viewController(forKey: .to)
78 | transitionContainer = context.containerView
79 |
80 | start()
81 | }
82 |
83 | /**
84 | Returns the transition duration time interval.
85 | - Parameter using transitionContext: An optional UIViewControllerContextTransitioning.
86 | - Returns: A TimeInterval that is the total animation time including delays.
87 | */
88 | public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
89 | return 0 // Time will be updated dynamically.
90 | }
91 |
92 | public func animationEnded(_ transitionCompleted: Bool) {
93 | state = .possible
94 | }
95 | }
96 |
97 | extension MotionTransition: UIViewControllerInteractiveTransitioning {
98 | public var wantsInteractiveStart: Bool {
99 | return true
100 | }
101 |
102 | public func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) {
103 | animateTransition(using: transitionContext)
104 | }
105 | }
106 |
--------------------------------------------------------------------------------