Commit cb8adf42 by Daniel Dahan

development: added new Motion for Material 2.5.0

parent b77365ad
...@@ -51,8 +51,6 @@ ...@@ -51,8 +51,6 @@
96328B9F1E05C24E009A4C90 /* TableViewController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96328B981E05C0CE009A4C90 /* TableViewController.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 96328B9F1E05C24E009A4C90 /* TableViewController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96328B981E05C0CE009A4C90 /* TableViewController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96334EF61C8B84660083986B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 96334EF51C8B84660083986B /* Assets.xcassets */; }; 96334EF61C8B84660083986B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 96334EF51C8B84660083986B /* Assets.xcassets */; };
965E80C81DD4C50600D61E4B /* Motion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB76D1CB40DC500C806FE /* Motion.swift */; }; 965E80C81DD4C50600D61E4B /* Motion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB76D1CB40DC500C806FE /* Motion.swift */; };
965E80C91DD4C50600D61E4B /* MotionTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB78B1CB40DC500C806FE /* MotionTransition.swift */; };
965E80CA1DD4C50600D61E4B /* KeyframeMotion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB77E1CB40DC500C806FE /* KeyframeMotion.swift */; };
965E80CB1DD4C50600D61E4B /* MotionAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96E3C39D1D3A1D0C0086A024 /* MotionAnimation.swift */; }; 965E80CB1DD4C50600D61E4B /* MotionAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96E3C39D1D3A1D0C0086A024 /* MotionAnimation.swift */; };
965E80CC1DD4C50600D61E4B /* Bar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB7981CB40DC500C806FE /* Bar.swift */; }; 965E80CC1DD4C50600D61E4B /* Bar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB7981CB40DC500C806FE /* Bar.swift */; };
965E80CD1DD4C50600D61E4B /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB7701CB40DC500C806FE /* Button.swift */; }; 965E80CD1DD4C50600D61E4B /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB7701CB40DC500C806FE /* Button.swift */; };
...@@ -154,9 +152,7 @@ ...@@ -154,9 +152,7 @@
96BCB7FA1CB40DE900C806FE /* Roboto-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 96BCB7F11CB40DE900C806FE /* Roboto-Regular.ttf */; }; 96BCB7FA1CB40DE900C806FE /* Roboto-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 96BCB7F11CB40DE900C806FE /* Roboto-Regular.ttf */; };
96BCB7FB1CB40DE900C806FE /* Roboto-Thin.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 96BCB7F21CB40DE900C806FE /* Roboto-Thin.ttf */; }; 96BCB7FB1CB40DE900C806FE /* Roboto-Thin.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 96BCB7F21CB40DE900C806FE /* Roboto-Thin.ttf */; };
96BCB7FC1CB40DE900C806FE /* Roboto-Thin.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 96BCB7F21CB40DE900C806FE /* Roboto-Thin.ttf */; }; 96BCB7FC1CB40DE900C806FE /* Roboto-Thin.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 96BCB7F21CB40DE900C806FE /* Roboto-Thin.ttf */; };
96BCB8101CB4115200C806FE /* MotionTransition.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB78B1CB40DC500C806FE /* MotionTransition.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96BCB8111CB4115200C806FE /* Motion.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB76D1CB40DC500C806FE /* Motion.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 96BCB8111CB4115200C806FE /* Motion.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB76D1CB40DC500C806FE /* Motion.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96BCB8131CB4115200C806FE /* KeyframeMotion.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB77E1CB40DC500C806FE /* KeyframeMotion.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96BCB8141CB4115200C806FE /* PulseMotion.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB7821CB40DC500C806FE /* PulseMotion.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 96BCB8141CB4115200C806FE /* PulseMotion.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB7821CB40DC500C806FE /* PulseMotion.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96BCB8151CB4115200C806FE /* FABButton.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB75F1CB40DC500C806FE /* FABButton.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 96BCB8151CB4115200C806FE /* FABButton.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB75F1CB40DC500C806FE /* FABButton.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96BCB8161CB4115200C806FE /* FlatButton.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB7601CB40DC500C806FE /* FlatButton.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 96BCB8161CB4115200C806FE /* FlatButton.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB7601CB40DC500C806FE /* FlatButton.swift */; settings = {ATTRIBUTES = (Public, ); }; };
...@@ -276,7 +272,6 @@ ...@@ -276,7 +272,6 @@
96BCB77B1CB40DC500C806FE /* Font.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = "<group>"; }; 96BCB77B1CB40DC500C806FE /* Font.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = "<group>"; };
96BCB77C1CB40DC500C806FE /* Gravity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Gravity.swift; sourceTree = "<group>"; }; 96BCB77C1CB40DC500C806FE /* Gravity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Gravity.swift; sourceTree = "<group>"; };
96BCB77D1CB40DC500C806FE /* Icon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Icon.swift; sourceTree = "<group>"; }; 96BCB77D1CB40DC500C806FE /* Icon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Icon.swift; sourceTree = "<group>"; };
96BCB77E1CB40DC500C806FE /* KeyframeMotion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyframeMotion.swift; sourceTree = "<group>"; };
96BCB7801CB40DC500C806FE /* Layer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Layer.swift; sourceTree = "<group>"; }; 96BCB7801CB40DC500C806FE /* Layer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Layer.swift; sourceTree = "<group>"; };
96BCB7811CB40DC500C806FE /* Layout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Layout.swift; sourceTree = "<group>"; }; 96BCB7811CB40DC500C806FE /* Layout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Layout.swift; sourceTree = "<group>"; };
96BCB7821CB40DC500C806FE /* PulseMotion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PulseMotion.swift; sourceTree = "<group>"; }; 96BCB7821CB40DC500C806FE /* PulseMotion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PulseMotion.swift; sourceTree = "<group>"; };
...@@ -286,7 +281,6 @@ ...@@ -286,7 +281,6 @@
96BCB7871CB40DC500C806FE /* InterimSpace.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InterimSpace.swift; sourceTree = "<group>"; }; 96BCB7871CB40DC500C806FE /* InterimSpace.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InterimSpace.swift; sourceTree = "<group>"; };
96BCB7881CB40DC500C806FE /* Switch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Switch.swift; sourceTree = "<group>"; }; 96BCB7881CB40DC500C806FE /* Switch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Switch.swift; sourceTree = "<group>"; };
96BCB7891CB40DC500C806FE /* TableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewCell.swift; sourceTree = "<group>"; }; 96BCB7891CB40DC500C806FE /* TableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewCell.swift; sourceTree = "<group>"; };
96BCB78B1CB40DC500C806FE /* MotionTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionTransition.swift; sourceTree = "<group>"; };
96BCB78C1CB40DC500C806FE /* View.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = "<group>"; }; 96BCB78C1CB40DC500C806FE /* View.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = "<group>"; };
96BCB78E1CB40DC500C806FE /* Menu.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = Menu.swift; sourceTree = "<group>"; tabWidth = 4; }; 96BCB78E1CB40DC500C806FE /* Menu.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = Menu.swift; sourceTree = "<group>"; tabWidth = 4; };
96BCB7901CB40DC500C806FE /* NavigationBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBar.swift; sourceTree = "<group>"; }; 96BCB7901CB40DC500C806FE /* NavigationBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBar.swift; sourceTree = "<group>"; };
...@@ -752,8 +746,6 @@ ...@@ -752,8 +746,6 @@
children = ( children = (
96BCB76D1CB40DC500C806FE /* Motion.swift */, 96BCB76D1CB40DC500C806FE /* Motion.swift */,
96E3C39D1D3A1D0C0086A024 /* MotionAnimation.swift */, 96E3C39D1D3A1D0C0086A024 /* MotionAnimation.swift */,
96BCB78B1CB40DC500C806FE /* MotionTransition.swift */,
96BCB77E1CB40DC500C806FE /* KeyframeMotion.swift */,
96BCB7821CB40DC500C806FE /* PulseMotion.swift */, 96BCB7821CB40DC500C806FE /* PulseMotion.swift */,
961730351E0E156400A9A297 /* SpringMotion.swift */, 961730351E0E156400A9A297 /* SpringMotion.swift */,
); );
...@@ -832,9 +824,7 @@ ...@@ -832,9 +824,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
96D88C321C1328D800B91418 /* Material.h in Headers */, 96D88C321C1328D800B91418 /* Material.h in Headers */,
96BCB8101CB4115200C806FE /* MotionTransition.swift in Headers */,
96BCB8111CB4115200C806FE /* Motion.swift in Headers */, 96BCB8111CB4115200C806FE /* Motion.swift in Headers */,
96BCB8131CB4115200C806FE /* KeyframeMotion.swift in Headers */,
96BCB8141CB4115200C806FE /* PulseMotion.swift in Headers */, 96BCB8141CB4115200C806FE /* PulseMotion.swift in Headers */,
96BCB8151CB4115200C806FE /* FABButton.swift in Headers */, 96BCB8151CB4115200C806FE /* FABButton.swift in Headers */,
96BCB8161CB4115200C806FE /* FlatButton.swift in Headers */, 96BCB8161CB4115200C806FE /* FlatButton.swift in Headers */,
...@@ -1109,8 +1099,6 @@ ...@@ -1109,8 +1099,6 @@
965E80FE1DD4D59500D61E4B /* ToolbarController.swift in Sources */, 965E80FE1DD4D59500D61E4B /* ToolbarController.swift in Sources */,
96328B971E05C0BB009A4C90 /* TableView.swift in Sources */, 96328B971E05C0BB009A4C90 /* TableView.swift in Sources */,
965E80C81DD4C50600D61E4B /* Motion.swift in Sources */, 965E80C81DD4C50600D61E4B /* Motion.swift in Sources */,
965E80C91DD4C50600D61E4B /* MotionTransition.swift in Sources */,
965E80CA1DD4C50600D61E4B /* KeyframeMotion.swift in Sources */,
965E80F81DD4D59500D61E4B /* ImageCard.swift in Sources */, 965E80F81DD4D59500D61E4B /* ImageCard.swift in Sources */,
96328B991E05C0CE009A4C90 /* TableViewController.swift in Sources */, 96328B991E05C0CE009A4C90 /* TableViewController.swift in Sources */,
965E80F91DD4D59500D61E4B /* PresenterCard.swift in Sources */, 965E80F91DD4D59500D61E4B /* PresenterCard.swift in Sources */,
......
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
@objc(AnimationRotationMode)
public enum AnimationRotationMode: Int {
case `default`
case auto
case autoReverse
}
/**
Converts an AnimationRotationMode to a corresponding CAAnimationRotate key.
- Parameter mode: An AnimationRotationMode.
- Returns: An optional CAAnimationRotate key String.
*/
public func AnimationRotationModeToValue(mode: AnimationRotationMode) -> String? {
switch mode {
case .default:
return nil
case .auto:
return kCAAnimationRotateAuto
case .autoReverse:
return kCAAnimationRotateAutoReverse
}
}
extension Motion {
/**
Creates a CAKeyframeAnimation.
- Parameter bezierPath: A UIBezierPath.
- Parameter mode: An AnimationRotationMode.
- Parameter duration: An animation duration time.
- Returns: A CAKeyframeAnimation.
*/
public static func path(bezierPath: UIBezierPath, mode: AnimationRotationMode = .auto, duration: CFTimeInterval? = nil) -> CAKeyframeAnimation {
let animation = CAKeyframeAnimation()
animation.keyPath = MotionAnimationKeyPath.position.rawValue
animation.path = bezierPath.cgPath
animation.rotationMode = AnimationRotationModeToValue(mode: mode)
if let v = duration {
animation.duration = v
}
return animation
}
}
...@@ -32,10 +32,10 @@ import UIKit ...@@ -32,10 +32,10 @@ import UIKit
@objc(MotionAnimationFillMode) @objc(MotionAnimationFillMode)
public enum MotionAnimationFillMode: Int { public enum MotionAnimationFillMode: Int {
case forwards case forwards
case backwards case backwards
case both case both
case removed case removed
} }
/** /**
...@@ -43,16 +43,16 @@ public enum MotionAnimationFillMode: Int { ...@@ -43,16 +43,16 @@ public enum MotionAnimationFillMode: Int {
- Parameter mode: An MotionAnimationFillMode enum value. - Parameter mode: An MotionAnimationFillMode enum value.
*/ */
public func MotionAnimationFillModeToValue(mode: MotionAnimationFillMode) -> String { public func MotionAnimationFillModeToValue(mode: MotionAnimationFillMode) -> String {
switch mode { switch mode {
case .forwards: case .forwards:
return kCAFillModeForwards return kCAFillModeForwards
case .backwards: case .backwards:
return kCAFillModeBackwards return kCAFillModeBackwards
case .both: case .both:
return kCAFillModeBoth return kCAFillModeBoth
case .removed: case .removed:
return kCAFillModeRemoved return kCAFillModeRemoved
} }
} }
@objc(MotionAnimationTimingFunction) @objc(MotionAnimationTimingFunction)
...@@ -86,8 +86,276 @@ public func MotionAnimationTimingFunctionToValue(timingFunction: MotionAnimation ...@@ -86,8 +86,276 @@ public func MotionAnimationTimingFunctionToValue(timingFunction: MotionAnimation
public typealias MotionDelayCancelBlock = (Bool) -> Void public typealias MotionDelayCancelBlock = (Bool) -> Void
public struct Motion { fileprivate var MotionInstanceKey: UInt8 = 0
/** fileprivate var MotionInstanceControllerKey: UInt8 = 0
fileprivate struct MotionInstance {
fileprivate var identifier: String
fileprivate var animations: [MotionAnimation]
}
fileprivate struct MotionInstanceController {
fileprivate var isEnabled: Bool
fileprivate weak var delegate: MotionDelegate?
}
extension UIViewController: MotionDelegate, UIViewControllerTransitioningDelegate {
/// MotionInstanceController Reference.
fileprivate var motionInstanceController: MotionInstanceController {
get {
return AssociatedObject(base: self, key: &MotionInstanceControllerKey) {
return MotionInstanceController(isEnabled: false, delegate: nil)
}
}
set(value) {
AssociateObject(base: self, key: &MotionInstanceControllerKey, value: value)
}
}
open var isMotionEnabled: Bool {
get {
return motionInstanceController.isEnabled
}
set(value) {
if value {
modalPresentationStyle = .custom
transitioningDelegate = self
motionDelegate = self
}
motionInstanceController.isEnabled = value
}
}
open weak var motionDelegate: MotionDelegate? {
get {
return motionInstanceController.delegate
}
set(value) {
motionInstanceController.delegate = value
}
}
}
extension UIViewController {
open func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return isMotionEnabled ? Motion(isPresenting: true) : nil
}
open func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return isMotionEnabled ? Motion() : nil
}
open func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return isMotionEnabled ? MotionPresentationController(presentedViewController: presented, presenting: presenting) : nil
}
}
extension UIView {
/// MaterialTransitionItem Reference.
fileprivate var motionInstance: MotionInstance {
get {
return AssociatedObject(base: self, key: &MotionInstanceKey) {
return MotionInstance(identifier: "", animations: [])
}
}
set(value) {
AssociateObject(base: self, key: &MotionInstanceKey, value: value)
}
}
open var motionIdentifier: String {
get {
return motionInstance.identifier
}
set(value) {
motionInstance.identifier = value
}
}
open var motionAnimations: [MotionAnimation] {
get {
return motionInstance.animations
}
set(value) {
motionInstance.animations = value
}
}
open func transitionSnapshot(afterUpdates: Bool, shouldHide: Bool = true) -> UIView {
isHidden = false
let oldCornerRadius = cornerRadius
cornerRadius = 0
var oldBackgroundColor: UIColor?
if shouldHide {
oldBackgroundColor = backgroundColor
backgroundColor = .clear
}
let oldTransform = motionTransform
motionTransform = CATransform3DIdentity
let v = snapshotView(afterScreenUpdates: afterUpdates)!
cornerRadius = oldCornerRadius
if shouldHide {
backgroundColor = oldBackgroundColor
}
motionTransform = oldTransform
let contentView = v.subviews.first!
contentView.cornerRadius = cornerRadius
contentView.masksToBounds = true
v.motionIdentifier = motionIdentifier
v.position = motionPosition
v.bounds = bounds
v.cornerRadius = cornerRadius
v.zPosition = zPosition
v.opacity = opacity
v.isOpaque = isOpaque
v.anchorPoint = anchorPoint
v.masksToBounds = masksToBounds
v.borderColor = borderColor
v.borderWidth = borderWidth
v.shadowRadius = shadowRadius
v.shadowOpacity = shadowOpacity
v.shadowColor = shadowColor
v.shadowOffset = shadowOffset
v.contentMode = contentMode
v.motionTransform = motionTransform
v.backgroundColor = backgroundColor
isHidden = shouldHide
return v
}
}
open class MotionPresentationController: UIPresentationController {
open override func presentationTransitionWillBegin() {
guard nil != containerView else {
return
}
presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (context) in
print("Animating")
})
print("presentationTransitionWillBegin")
}
open override func presentationTransitionDidEnd(_ completed: Bool) {
print("presentationTransitionDidEnd")
}
open override func dismissalTransitionWillBegin() {
guard nil != containerView else {
return
}
presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (context) in
print("Animating")
})
print("dismissalTransitionWillBegin")
}
open override func dismissalTransitionDidEnd(_ completed: Bool) {
print("dismissalTransitionDidEnd")
}
open override var frameOfPresentedViewInContainerView: CGRect {
return containerView?.bounds ?? .zero
}
}
@objc(MotionDelegate)
public protocol MotionDelegate {
@objc
optional func motion(motion: Motion, willTransition fromView: UIView, toView: UIView)
@objc
optional func motion(motion: Motion, didTransition fromView: UIView, toView: UIView)
@objc
optional func motionModifyDelay(motion: Motion) -> TimeInterval
@objc
optional func motionTransitionAnimation(motion: Motion)
}
open class Motion: NSObject {
open var isPresenting: Bool
open fileprivate(set) var transitionPairs = [(UIView, UIView)]()
open var transitionSnapshot: UIView!
open let transitionBackgroundView = UIView()
open var toViewController: UIViewController!
open var fromViewController: UIViewController!
open var transitionContext: UIViewControllerContextTransitioning!
open var delay: TimeInterval = 0
open var duration: TimeInterval = 0.35
open var containerView: UIView!
open var transitionView = UIView()
fileprivate var modifiedDelay: TimeInterval {
return fromViewController?.motionDelegate?.motionModifyDelay?(motion: self) ?? 0
}
public override init() {
isPresenting = false
super.init()
}
public init(isPresenting: Bool) {
self.isPresenting = isPresenting
super.init()
}
open var toView: UIView {
return toViewController.view
}
open var toSubviews: [UIView] {
return Motion.subviews(of: toView)
}
open var fromView: UIView {
return fromViewController.view
}
open var fromSubviews: [UIView] {
return Motion.subviews(of: fromView)
}
open class func subviews(of view: UIView) -> [UIView] {
var views: [UIView] = []
Motion.subviews(of: view, views: &views)
return views
}
open class func subviews(of view: UIView, views: inout [UIView]) {
for v in view.subviews {
if 0 < v.motionIdentifier.utf16.count {
views.append(v)
}
subviews(of: v, views: &views)
}
}
/**
Executes a block of code after a time delay. Executes a block of code after a time delay.
- Parameter duration: An animation duration time. - Parameter duration: An animation duration time.
- Parameter animations: An animation block. - Parameter animations: An animation block.
...@@ -95,93 +363,298 @@ public struct Motion { ...@@ -95,93 +363,298 @@ public struct Motion {
the animations have completed. the animations have completed.
*/ */
@discardableResult @discardableResult
public static func delay(_ time: TimeInterval, execute block: @escaping () -> Void) -> MotionDelayCancelBlock? { open class func delay(_ time: TimeInterval, execute block: @escaping () -> Void) -> MotionDelayCancelBlock? {
func asyncAfter(completion: @escaping () -> Void) {
func asyncAfter(completion: @escaping () -> Void) { DispatchQueue.main.asyncAfter(deadline: .now() + time, execute: completion)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + time, execute: completion) }
}
var cancelable: MotionDelayCancelBlock?
var cancelable: MotionDelayCancelBlock?
let delayed: MotionDelayCancelBlock = {
let delayed: MotionDelayCancelBlock = { if !$0 {
if !$0 { DispatchQueue.main.async(execute: block)
DispatchQueue.main.async(execute: block) }
}
cancelable = nil cancelable = nil
} }
cancelable = delayed cancelable = delayed
asyncAfter { asyncAfter {
cancelable?(false) cancelable?(false)
} }
return cancelable return cancelable
} }
/** /**
Cancels the delayed MotionDelayCancelBlock. Cancels the delayed MotionDelayCancelBlock.
- Parameter delayed completion: An MotionDelayCancelBlock. - Parameter delayed completion: An MotionDelayCancelBlock.
*/ */
public static func cancel(delayed completion: MotionDelayCancelBlock) { open class func cancel(delayed completion: MotionDelayCancelBlock) {
completion(true) completion(true)
} }
}
/** extension Motion: UIViewControllerAnimatedTransitioning {
Disables the default animations set on CALayers. @objc(animateTransition:)
- Parameter animations: A callback that wraps the animations to disable. open func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
*/ self.transitionContext = transitionContext
public static func disable(animations: (() -> Void)) { prepareToViewController()
animate(duration: 0, animations: animations) prepareFromViewController()
}
fromViewController.motionDelegate?.motion?(motion: self, willTransition: fromView, toView: toView)
/**
Runs an animation with a specified duration. Motion.delay(modifiedDelay) { [weak self] in
- Parameter duration: An animation duration time. guard let s = self else {
- Parameter animations: An animation block. return
- Parameter timingFunction: An MotionAnimationTimingFunction value. }
- Parameter completion: A completion block that is executed once
the animations have completed. s.prepareContainerView()
*/ s.prepareTransitionSnapshot()
public static func animate(duration: CFTimeInterval, timingFunction: MotionAnimationTimingFunction = .easeInEaseOut, animations: (() -> Void), completion: (() -> Void)? = nil) { s.prepareTransitionPairs()
CATransaction.begin() s.prepareTransitionView()
CATransaction.setAnimationDuration(duration) s.prepareTransitionBackgroundView()
CATransaction.setCompletionBlock(completion) s.prepareTransitionToView()
CATransaction.setAnimationTimingFunction(MotionAnimationTimingFunctionToValue(timingFunction: timingFunction)) s.prepareTransitionAnimation()
animations() }
CATransaction.commit() }
}
@objc(transitionDuration:)
/** open func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return delay + duration
}
}
extension Motion {
fileprivate func prepareToViewController() {
guard let v = transitionContext.viewController(forKey: .to) else {
return
}
toViewController = v
}
fileprivate func prepareFromViewController() {
guard let v = transitionContext.viewController(forKey: .from) else {
return
}
fromViewController = v
}
fileprivate func prepareContainerView() {
containerView = transitionContext.containerView
}
fileprivate func prepareTransitionSnapshot() {
transitionSnapshot = fromView.transitionSnapshot(afterUpdates: true, shouldHide: false)
transitionSnapshot.frame = containerView.bounds
containerView.insertSubview(transitionSnapshot, aboveSubview: fromView)
}
fileprivate func prepareTransitionPairs() {
for from in fromSubviews {
for to in toSubviews {
guard to.motionIdentifier == from.motionIdentifier else {
continue
}
transitionPairs.append((from, to))
}
}
}
fileprivate func prepareTransitionView() {
transitionView.frame = containerView.bounds
transitionView.isUserInteractionEnabled = false
containerView.insertSubview(transitionView, belowSubview: transitionSnapshot)
}
fileprivate func prepareTransitionBackgroundView() {
transitionBackgroundView.backgroundColor = isPresenting ? .clear : fromView.backgroundColor ?? .clear
transitionBackgroundView.frame = transitionView.bounds
transitionView.addSubview(transitionBackgroundView)
}
fileprivate func prepareTransitionToView() {
toView.isHidden = isPresenting
containerView.insertSubview(toView, belowSubview: transitionView)
toView.updateConstraints()
toView.setNeedsLayout()
toView.layoutIfNeeded()
}
fileprivate func prepareTransitionAnimation() {
addTransitionAnimations()
addBackgroundMotionAnimation()
cleanupAnimation()
removeTransitionSnapshot()
}
}
extension Motion {
fileprivate func addTransitionAnimations() {
for (from, to) in transitionPairs {
var snapshotAnimations = [CABasicAnimation]()
var snapshotChildAnimations = [CABasicAnimation]()
let sizeAnimation = Motion.size(to.bounds.size)
let cornerRadiusAnimation = Motion.corner(radius: to.cornerRadius)
snapshotAnimations.append(sizeAnimation)
snapshotAnimations.append(cornerRadiusAnimation)
snapshotAnimations.append(Motion.position(to: to.motionPosition))
snapshotAnimations.append(Motion.transform(transform: to.motionTransform))
snapshotAnimations.append(Motion.background(color: to.backgroundColor ?? .clear))
snapshotChildAnimations.append(cornerRadiusAnimation)
snapshotChildAnimations.append(sizeAnimation)
snapshotChildAnimations.append(Motion.position(x: to.bounds.width / 2, y: to.bounds.height / 2))
let d = transitionDuration(animations: to.motionAnimations)
let snapshot = from.transitionSnapshot(afterUpdates: true)
transitionView.addSubview(snapshot)
Motion.delay(motionDelay(animations: to.motionAnimations)) { [weak self, weak to] in
guard let s = self else {
return
}
guard let v = to else {
return
}
let tf = s.motionTimingFunction(animations: v.motionAnimations)
let snapshotGroup = Motion.animate(group: snapshotAnimations, duration: d)
snapshotGroup.fillMode = MotionAnimationFillModeToValue(mode: .forwards)
snapshotGroup.isRemovedOnCompletion = false
snapshotGroup.timingFunction = MotionAnimationTimingFunctionToValue(timingFunction: tf)
let snapshotChildGroup = Motion.animate(group: snapshotChildAnimations, duration: d)
snapshotChildGroup.fillMode = MotionAnimationFillModeToValue(mode: .forwards)
snapshotChildGroup.isRemovedOnCompletion = false
snapshotChildGroup.timingFunction = MotionAnimationTimingFunctionToValue(timingFunction: tf)
snapshot.animate(snapshotGroup)
snapshot.subviews.first!.animate(snapshotChildGroup)
}
}
fromViewController.motionDelegate?.motionTransitionAnimation?(motion: self)
toViewController.motionDelegate?.motionTransitionAnimation?(motion: self)
}
fileprivate func addBackgroundMotionAnimation() {
transitionBackgroundView.motion(.backgroundColor(isPresenting ? toView.backgroundColor ?? .clear : .clear), .duration(transitionDuration(using: transitionContext)))
}
}
extension Motion {
/**
Creates a CAAnimationGroup. Creates a CAAnimationGroup.
- Parameter animations: An Array of CAAnimation objects. - Parameter animations: An Array of CAAnimation objects.
- Parameter timingFunction: An MotionAnimationTimingFunction value. - Parameter timingFunction: An MotionAnimationTimingFunction value.
- Parameter duration: An animation duration time for the group. - Parameter duration: An animation duration time for the group.
- Returns: A CAAnimationGroup. - Returns: A CAAnimationGroup.
*/ */
public static func animate(group animations: [CAAnimation], timingFunction: MotionAnimationTimingFunction = .easeInEaseOut, duration: CFTimeInterval = 0.5) -> CAAnimationGroup { internal class func animate(group animations: [CAAnimation], timingFunction: MotionAnimationTimingFunction = .easeInEaseOut, duration: CFTimeInterval = 0.5) -> CAAnimationGroup {
let group = CAAnimationGroup() let group = CAAnimationGroup()
group.fillMode = MotionAnimationFillModeToValue(mode: .forwards) group.fillMode = MotionAnimationFillModeToValue(mode: .forwards)
group.isRemovedOnCompletion = false group.isRemovedOnCompletion = false
group.animations = animations group.animations = animations
group.duration = duration group.duration = duration
group.timingFunction = MotionAnimationTimingFunctionToValue(timingFunction: timingFunction) group.timingFunction = MotionAnimationTimingFunctionToValue(timingFunction: timingFunction)
return group return group
} }
/**
Executes an animation block with a given delay and duration.
- Parameter delay time: A CFTimeInterval.
- Parameter duration: An animation duration time.
- Parameter animations: An animation block.
- Parameter completion: A completion block that is executed once
the animations have completed.
*/
public static func animate(delay time: CFTimeInterval, duration: CFTimeInterval, animations: @escaping (() -> Void), completion: (() -> Void)? = nil) {
delay(time) {
animate(duration: duration, animations: animations, completion: completion)
}
}
} }
extension Motion {
fileprivate func motionDelay(animations: [MotionAnimation]) -> TimeInterval {
var t: TimeInterval = 0
for a in animations {
switch a {
case let .delay(time):
if time > delay {
delay = time
}
t = time
default:break
}
}
return t
}
fileprivate func transitionDuration(animations: [MotionAnimation]) -> TimeInterval {
var t: TimeInterval = 0.35
for a in animations {
switch a {
case let .duration(time):
if time > duration {
duration = time
}
t = time
default:break
}
}
return t
}
fileprivate func motionTimingFunction(animations: [MotionAnimation]) -> MotionAnimationTimingFunction {
var t = MotionAnimationTimingFunction.easeInEaseOut
for a in animations {
switch a {
case let .timingFunction(timingFunction):
t = timingFunction
default:break
}
}
return t
}
}
extension Motion {
fileprivate func cleanupAnimation() {
Motion.delay(transitionDuration(using: transitionContext) + modifiedDelay) { [weak self] in
guard let s = self else {
return
}
s.showToSubviews()
s.clearTransitionView()
s.clearTransitionPairs()
s.completeTransition()
}
}
fileprivate func removeTransitionSnapshot() {
Motion.delay(delay) { [weak self] in
self?.transitionSnapshot.removeFromSuperview()
}
}
fileprivate func showToSubviews() {
toSubviews.forEach {
$0.isHidden = false
}
toView.isHidden = false
}
fileprivate func clearTransitionPairs() {
transitionPairs.removeAll()
}
fileprivate func clearTransitionView() {
transitionView.removeFromSuperview()
transitionView.subviews.forEach {
$0.removeFromSuperview()
}
}
fileprivate func completeTransition() {
toViewController.motionDelegate?.motion?(motion: self, didTransition: fromView, toView: toView)
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
...@@ -32,6 +32,7 @@ import UIKit ...@@ -32,6 +32,7 @@ import UIKit
public enum MotionAnimationKeyPath: String { public enum MotionAnimationKeyPath: String {
case backgroundColor case backgroundColor
case barTintColor
case cornerRadius case cornerRadius
case transform case transform
case rotation = "transform.rotation" case rotation = "transform.rotation"
...@@ -61,6 +62,7 @@ public enum MotionAnimation { ...@@ -61,6 +62,7 @@ public enum MotionAnimation {
case duration(TimeInterval) case duration(TimeInterval)
case custom(CABasicAnimation) case custom(CABasicAnimation)
case backgroundColor(UIColor) case backgroundColor(UIColor)
case barTintColor(UIColor)
case cornerRadius(CGFloat) case cornerRadius(CGFloat)
case transform(CATransform3D) case transform(CATransform3D)
case rotationAngle(CGFloat) case rotationAngle(CGFloat)
...@@ -92,7 +94,7 @@ public enum MotionAnimation { ...@@ -92,7 +94,7 @@ public enum MotionAnimation {
} }
extension CALayer { extension CALayer {
/** /**
A function that accepts CAAnimation objects and executes them on the A function that accepts CAAnimation objects and executes them on the
view's backing layer. view's backing layer.
...@@ -123,7 +125,7 @@ extension CALayer { ...@@ -123,7 +125,7 @@ extension CALayer {
} }
} }
} }
/** /**
A delegation function that is executed when the backing layer stops A delegation function that is executed when the backing layer stops
running an animation. running an animation.
...@@ -243,6 +245,8 @@ extension CALayer { ...@@ -243,6 +245,8 @@ extension CALayer {
a.append(animation) a.append(animation)
case let .backgroundColor(color): case let .backgroundColor(color):
a.append(Motion.background(color: color)) a.append(Motion.background(color: color))
case let .barTintColor(color):
a.append(Motion.barTint(color: color))
case let .cornerRadius(radius): case let .cornerRadius(radius):
a.append(Motion.corner(radius: radius)) a.append(Motion.corner(radius: radius))
case let .transform(transform): case let .transform(transform):
...@@ -300,7 +304,7 @@ extension CALayer { ...@@ -300,7 +304,7 @@ extension CALayer {
default:break default:break
} }
} }
let g = Motion.animate(group: a, duration: d) let g = Motion.animate(group: a, duration: d)
g.fillMode = MotionAnimationFillModeToValue(mode: .forwards) g.fillMode = MotionAnimationFillModeToValue(mode: .forwards)
g.isRemovedOnCompletion = false g.isRemovedOnCompletion = false
...@@ -325,6 +329,21 @@ extension UIView { ...@@ -325,6 +329,21 @@ extension UIView {
} }
} }
/// The global position of a view.
open var motionPosition: CGPoint {
return superview?.convert(position, to: nil) ?? position
}
/// The layer.transform of a view.
open var motionTransform: CATransform3D {
get {
return layer.transform
}
set(value) {
layer.transform = value
}
}
/// Computes the scale X axis value of the view. /// Computes the scale X axis value of the view.
open var motionScaleX: CGFloat { open var motionScaleX: CGFloat {
return transform.a return transform.a
...@@ -345,7 +364,7 @@ extension UIView { ...@@ -345,7 +364,7 @@ extension UIView {
} }
/** /**
A function that accepts an Array of CAAnimation objects and executes A function that accepts an Array of CAAnimation objects and executes
them on the view's backing layer. them on the view's backing layer.
- Parameter animations: An Array of CAAnimations. - Parameter animations: An Array of CAAnimations.
*/ */
...@@ -388,44 +407,55 @@ extension Motion { ...@@ -388,44 +407,55 @@ extension Motion {
- Parameter color: A UIColor. - Parameter color: A UIColor.
- Returns: A CABasicAnimation. - Returns: A CABasicAnimation.
*/ */
public static func background(color: UIColor) -> CABasicAnimation { public static func background(color: UIColor) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .backgroundColor) let animation = CABasicAnimation(keyPath: .backgroundColor)
animation.toValue = color.cgColor animation.toValue = color.cgColor
return animation return animation
} }
/**
Creates a CABasicAnimation for the barTintColor key path.
- Parameter color: A UIColor.
- Returns: A CABasicAnimation.
*/
public static func barTint(color: UIColor) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .barTintColor)
animation.toValue = color.cgColor
return animation
}
/** /**
Creates a CABasicAnimation for the cornerRadius key path. Creates a CABasicAnimation for the cornerRadius key path.
- Parameter radius: A CGFloat. - Parameter radius: A CGFloat.
- Returns: A CABasicAnimation. - Returns: A CABasicAnimation.
*/ */
public static func corner(radius: CGFloat) -> CABasicAnimation { public static func corner(radius: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .cornerRadius) let animation = CABasicAnimation(keyPath: .cornerRadius)
animation.toValue = radius animation.toValue = radius
return animation return animation
} }
/** /**
Creates a CABasicAnimation for the transform key path. Creates a CABasicAnimation for the transform key path.
- Parameter transform: A CATransform3D object. - Parameter transform: A CATransform3D object.
- Returns: A CABasicAnimation. - Returns: A CABasicAnimation.
*/ */
public static func transform(transform: CATransform3D) -> CABasicAnimation { public static func transform(transform: CATransform3D) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .transform) let animation = CABasicAnimation(keyPath: .transform)
animation.toValue = NSValue(caTransform3D: transform) animation.toValue = NSValue(caTransform3D: transform)
return animation return animation
} }
/** /**
Creates a CABasicAnimation for the transform.rotation key path. Creates a CABasicAnimation for the transform.rotation key path.
- Parameter angle: An optional CGFloat. - Parameter angle: An optional CGFloat.
- Returns: A CABasicAnimation. - Returns: A CABasicAnimation.
*/ */
public static func rotation(angle: CGFloat) -> CABasicAnimation { public static func rotation(angle: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotation) let animation = CABasicAnimation(keyPath: .rotation)
animation.toValue = NSNumber(value: Double(CGFloat(M_PI) * angle / 180)) animation.toValue = NSNumber(value: Double(CGFloat(M_PI) * angle / 180))
return animation return animation
} }
/** /**
Creates a CABasicAnimation for the transform.rotation.x key path. Creates a CABasicAnimation for the transform.rotation.x key path.
...@@ -433,32 +463,32 @@ extension Motion { ...@@ -433,32 +463,32 @@ extension Motion {
- Returns: A CABasicAnimation. - Returns: A CABasicAnimation.
*/ */
public static func rotationX(angle: CGFloat) -> CABasicAnimation { public static func rotationX(angle: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotationX) let animation = CABasicAnimation(keyPath: .rotationX)
animation.toValue = NSNumber(value: Double(CGFloat(M_PI) * angle / 180)) animation.toValue = NSNumber(value: Double(CGFloat(M_PI) * angle / 180))
return animation return animation
} }
/** /**
Creates a CABasicAnimation for the transform.rotation.y key path. Creates a CABasicAnimation for the transform.rotation.y key path.
- Parameter angle: An optional CGFloat. - Parameter angle: An optional CGFloat.
- Returns: A CABasicAnimation. - Returns: A CABasicAnimation.
*/ */
public static func rotationY(angle: CGFloat) -> CABasicAnimation { public static func rotationY(angle: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotationY) let animation = CABasicAnimation(keyPath: .rotationY)
animation.toValue = NSNumber(value: Double(CGFloat(M_PI) * angle / 180)) animation.toValue = NSNumber(value: Double(CGFloat(M_PI) * angle / 180))
return animation return animation
} }
/** /**
Creates a CABasicAnimation for the transform.rotation.z key path. Creates a CABasicAnimation for the transform.rotation.z key path.
- Parameter angle: An optional CGFloat. - Parameter angle: An optional CGFloat.
- Returns: A CABasicAnimation. - Returns: A CABasicAnimation.
*/ */
public static func rotationZ(angle: CGFloat) -> CABasicAnimation { public static func rotationZ(angle: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .rotationZ) let animation = CABasicAnimation(keyPath: .rotationZ)
animation.toValue = NSNumber(value: Double(CGFloat(M_PI) * angle / 180)) animation.toValue = NSNumber(value: Double(CGFloat(M_PI) * angle / 180))
return animation return animation
} }
/** /**
Creates a CABasicAnimation for the transform.rotation key path. Creates a CABasicAnimation for the transform.rotation key path.
...@@ -503,95 +533,95 @@ extension Motion { ...@@ -503,95 +533,95 @@ extension Motion {
animation.toValue = NSNumber(value: Double(CGFloat(M_PI) * 2 * rotations)) animation.toValue = NSNumber(value: Double(CGFloat(M_PI) * 2 * rotations))
return animation return animation
} }
/** /**
Creates a CABasicAnimation for the transform.scale key path. Creates a CABasicAnimation for the transform.scale key path.
- Parameter to scale: A CGFloat. - Parameter to scale: A CGFloat.
- Returns: A CABasicAnimation. - Returns: A CABasicAnimation.
*/ */
public static func scale(to scale: CGFloat) -> CABasicAnimation { public static func scale(to scale: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .scale) let animation = CABasicAnimation(keyPath: .scale)
animation.toValue = NSNumber(value: Double(scale)) animation.toValue = NSNumber(value: Double(scale))
return animation return animation
} }
/** /**
Creates a CABasicAnimation for the transform.scale.x key path. Creates a CABasicAnimation for the transform.scale.x key path.
- Parameter to scale: A CGFloat. - Parameter to scale: A CGFloat.
- Returns: A CABasicAnimation. - Returns: A CABasicAnimation.
*/ */
public static func scaleX(to scale: CGFloat) -> CABasicAnimation { public static func scaleX(to scale: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .scaleX) let animation = CABasicAnimation(keyPath: .scaleX)
animation.toValue = NSNumber(value: Double(scale)) animation.toValue = NSNumber(value: Double(scale))
return animation return animation
} }
/** /**
Creates a CABasicAnimation for the transform.scale.y key path. Creates a CABasicAnimation for the transform.scale.y key path.
- Parameter to scale: A CGFloat. - Parameter to scale: A CGFloat.
- Returns: A CABasicAnimation. - Returns: A CABasicAnimation.
*/ */
public static func scaleY(to scale: CGFloat) -> CABasicAnimation { public static func scaleY(to scale: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .scaleY) let animation = CABasicAnimation(keyPath: .scaleY)
animation.toValue = NSNumber(value: Double(scale)) animation.toValue = NSNumber(value: Double(scale))
return animation return animation
} }
/** /**
Creates a CABasicAnimation for the transform.scale.z key path. Creates a CABasicAnimation for the transform.scale.z key path.
- Parameter to scale: A CGFloat. - Parameter to scale: A CGFloat.
- Returns: A CABasicAnimation. - Returns: A CABasicAnimation.
*/ */
public static func scaleZ(to scale: CGFloat) -> CABasicAnimation { public static func scaleZ(to scale: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .scaleZ) let animation = CABasicAnimation(keyPath: .scaleZ)
animation.toValue = NSNumber(value: Double(scale)) animation.toValue = NSNumber(value: Double(scale))
return animation return animation
} }
/** /**
Creates a CABasicAnimation for the transform.translation key path. Creates a CABasicAnimation for the transform.translation key path.
- Parameter point: A CGPoint. - Parameter point: A CGPoint.
- Returns: A CABasicAnimation. - Returns: A CABasicAnimation.
*/ */
public static func translate(to point: CGPoint) -> CABasicAnimation { public static func translate(to point: CGPoint) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .translation) let animation = CABasicAnimation(keyPath: .translation)
animation.toValue = NSValue(cgPoint: point) animation.toValue = NSValue(cgPoint: point)
return animation return animation
} }
/** /**
Creates a CABasicAnimation for the transform.translation.x key path. Creates a CABasicAnimation for the transform.translation.x key path.
- Parameter to translation: A CGFloat. - Parameter to translation: A CGFloat.
- Returns: A CABasicAnimation. - Returns: A CABasicAnimation.
*/ */
public static func translateX(to translation: CGFloat) -> CABasicAnimation { public static func translateX(to translation: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .translationX) let animation = CABasicAnimation(keyPath: .translationX)
animation.toValue = NSNumber(value: Double(translation)) animation.toValue = NSNumber(value: Double(translation))
return animation return animation
} }
/** /**
Creates a CABasicAnimation for the transform.translation.y key path. Creates a CABasicAnimation for the transform.translation.y key path.
- Parameter to translation: A CGFloat. - Parameter to translation: A CGFloat.
- Returns: A CABasicAnimation. - Returns: A CABasicAnimation.
*/ */
public static func translateY(to translation: CGFloat) -> CABasicAnimation { public static func translateY(to translation: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .translationY) let animation = CABasicAnimation(keyPath: .translationY)
animation.toValue = NSNumber(value: Double(translation)) animation.toValue = NSNumber(value: Double(translation))
return animation return animation
} }
/** /**
Creates a CABasicAnimation for the transform.translation.z key path. Creates a CABasicAnimation for the transform.translation.z key path.
- Parameter to translation: A CGFloat. - Parameter to translation: A CGFloat.
- Returns: A CABasicAnimation. - Returns: A CABasicAnimation.
*/ */
public static func translateZ(to translation: CGFloat) -> CABasicAnimation { public static func translateZ(to translation: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .translationZ) let animation = CABasicAnimation(keyPath: .translationZ)
animation.toValue = NSNumber(value: Double(translation)) animation.toValue = NSNumber(value: Double(translation))
return animation return animation
} }
/** /**
Creates a CABasicAnimation for the position key path. Creates a CABasicAnimation for the position key path.
- Parameter x: A CGFloat. - Parameter x: A CGFloat.
...@@ -599,10 +629,10 @@ extension Motion { ...@@ -599,10 +629,10 @@ extension Motion {
- Returns: A CABasicAnimation. - Returns: A CABasicAnimation.
*/ */
public static func position(x: CGFloat, y: CGFloat) -> CABasicAnimation { public static func position(x: CGFloat, y: CGFloat) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .position) let animation = CABasicAnimation(keyPath: .position)
animation.toValue = NSValue(cgPoint: CGPoint(x: x, y: y)) animation.toValue = NSValue(cgPoint: CGPoint(x: x, y: y))
return animation return animation
} }
/** /**
Creates a CABasicAnimation for the position key path. Creates a CABasicAnimation for the position key path.
...@@ -614,17 +644,17 @@ extension Motion { ...@@ -614,17 +644,17 @@ extension Motion {
animation.toValue = NSValue(cgPoint: point) animation.toValue = NSValue(cgPoint: point)
return animation return animation
} }
/** /**
Creates a CABasicAnimation for the shadowPath key path. Creates a CABasicAnimation for the shadowPath key path.
- Parameter path: A CGPath. - Parameter path: A CGPath.
- Returns: A CABasicAnimation. - Returns: A CABasicAnimation.
*/ */
public static func shadow(path: CGPath) -> CABasicAnimation { public static func shadow(path: CGPath) -> CABasicAnimation {
let animation = CABasicAnimation(keyPath: .shadowPath) let animation = CABasicAnimation(keyPath: .shadowPath)
animation.toValue = path animation.toValue = path
return animation return animation
} }
/** /**
Creates a CABasicAnimation for the opacity key path. Creates a CABasicAnimation for the opacity key path.
......
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
fileprivate var MotionTransitionInstanceKey: UInt8 = 0
fileprivate var MotionTransitionInstanceControllerKey: UInt8 = 0
fileprivate struct MotionTransitionInstance {
fileprivate var identifier: String
fileprivate var animations: [MotionAnimation]
}
fileprivate struct MotionTransitionInstanceController {
fileprivate var isEnabled: Bool
}
extension UIViewController: UIViewControllerTransitioningDelegate {
/// MotionTransitionInstanceController Reference.
fileprivate var motionTransition: MotionTransitionInstanceController {
get {
return AssociatedObject(base: self, key: &MotionTransitionInstanceControllerKey) {
return MotionTransitionInstanceController(isEnabled: false)
}
}
set(value) {
AssociateObject(base: self, key: &MotionTransitionInstanceControllerKey, value: value)
}
}
open var isMotionTransitionEnabled: Bool {
get {
return motionTransition.isEnabled
}
set(value) {
if value {
modalPresentationStyle = .custom
transitioningDelegate = self
}
motionTransition.isEnabled = value
}
}
}
extension UIViewController {
open func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return isMotionTransitionEnabled ? MotionTransition(isPresenting: true) : nil
}
open func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return isMotionTransitionEnabled ? MotionTransition() : nil
}
open func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return isMotionTransitionEnabled ? MotionTransitionPresentationController(presentedViewController: presented, presenting: presenting) : nil
}
}
extension UIViewController {
open func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return isMotionTransitionEnabled ? MotionTransition(isPresenting: operation == .push) : nil
}
}
extension UIViewController {
open func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return isMotionTransitionEnabled ? MotionTransition() : nil
}
}
extension UIView {
/// The global position of a view.
open var motionPosition: CGPoint {
return superview?.convert(position, to: nil) ?? position
}
/// MaterialTransitionItem Reference.
fileprivate var motionTransition: MotionTransitionInstance {
get {
return AssociatedObject(base: self, key: &MotionTransitionInstanceKey) {
return MotionTransitionInstance(identifier: "", animations: [])
}
}
set(value) {
AssociateObject(base: self, key: &MotionTransitionInstanceKey, value: value)
}
}
open var motionTransitionIdentifier: String {
get {
return motionTransition.identifier
}
set(value) {
motionTransition.identifier = value
}
}
open var motionTransitionAnimations: [MotionAnimation] {
get {
return motionTransition.animations
}
set(value) {
motionTransition.animations = value
}
}
open func motionTransitionSnapshot(afterUpdates: Bool) -> UIView {
isHidden = false
(self as? Pulseable)?.pulse.pulseLayer?.isHidden = true
let oldCornerRadius = cornerRadius
cornerRadius = 0
let oldBackgroundColor = backgroundColor
backgroundColor = .clear
let oldTransform = transform
transform = .identity
let v = snapshotView(afterScreenUpdates: afterUpdates)!
cornerRadius = oldCornerRadius
backgroundColor = oldBackgroundColor
transform = oldTransform
let contentView = v.subviews.first!
contentView.cornerRadius = cornerRadius
contentView.masksToBounds = true
v.motionTransitionIdentifier = motionTransitionIdentifier
v.position = motionPosition
v.bounds = bounds
v.cornerRadius = cornerRadius
v.zPosition = zPosition
v.opacity = opacity
v.isOpaque = isOpaque
v.anchorPoint = anchorPoint
v.masksToBounds = masksToBounds
v.borderColor = borderColor
v.borderWidth = borderWidth
v.shadowRadius = shadowRadius
v.shadowOpacity = shadowOpacity
v.shadowColor = shadowColor
v.shadowOffset = shadowOffset
v.contentMode = contentMode
v.transform = transform
v.backgroundColor = backgroundColor
isHidden = true
(self as? Pulseable)?.pulse.pulseLayer?.isHidden = false
return v
}
}
open class MotionTransitionPresentationController: UIPresentationController {
open override func presentationTransitionWillBegin() {
guard nil != containerView else {
return
}
presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (context) in
print("Animating")
})
print("presentationTransitionWillBegin")
}
open override func presentationTransitionDidEnd(_ completed: Bool) {
print("presentationTransitionDidEnd")
}
open override func dismissalTransitionWillBegin() {
guard nil != containerView else {
return
}
presentedViewController.transitionCoordinator?.animate(alongsideTransition: { (context) in
print("Animating")
})
print("dismissalTransitionWillBegin")
}
open override func dismissalTransitionDidEnd(_ completed: Bool) {
print("dismissalTransitionDidEnd")
}
open override var frameOfPresentedViewInContainerView: CGRect {
return containerView?.bounds ?? .zero
}
}
open class MotionTransition: NSObject {
open var isPresenting: Bool
open fileprivate(set) var transitionPairs = [(UIView, UIView)]()
open var transitionSnapshot: UIView!
open let transitionBackgroundView = UIView()
open var toViewController: UIViewController!
open var fromViewController: UIViewController!
open var transitionContext: UIViewControllerContextTransitioning!
open var delay: TimeInterval = 0
open var duration: TimeInterval = 0.35
open var containerView: UIView!
open var transitionView = UIView()
public override init() {
isPresenting = false
super.init()
}
public init(isPresenting: Bool) {
self.isPresenting = isPresenting
super.init()
}
open var toView: UIView {
return toViewController.view
}
open var toSubviews: [UIView] {
return subviews(of: toView)
}
open var fromView: UIView {
return fromViewController.view
}
open var fromSubviews: [UIView] {
return subviews(of: fromView)
}
open func subviews(of view: UIView) -> [UIView] {
var views: [UIView] = []
subviews(of: view, views: &views)
return views
}
open func subviews(of view: UIView, views: inout [UIView]) {
for v in view.subviews {
if 0 < v.motionTransitionIdentifier.utf16.count {
views.append(v)
}
subviews(of: v, views: &views)
}
}
}
extension MotionTransition: UIViewControllerAnimatedTransitioning {
@objc(animateTransition:)
open func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
self.transitionContext = transitionContext
prepareToViewController()
prepareFromViewController()
prepareContainerView()
prepareTransitionSnapshot()
prepareTransitionPairs()
prepareTransitionView()
prepareTransitionBackgroundView()
prepareTransitionToView()
prepareTransitionAnimation()
}
@objc(transitionDuration:)
open func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return delay + duration
}
}
extension MotionTransition {
fileprivate func prepareToViewController() {
guard let v = transitionContext.viewController(forKey: .to) else {
return
}
toViewController = v
}
fileprivate func prepareFromViewController() {
guard let v = transitionContext.viewController(forKey: .from) else {
return
}
fromViewController = v
}
fileprivate func prepareContainerView() {
containerView = transitionContext.containerView
}
fileprivate func prepareTransitionSnapshot() {
transitionSnapshot = fromView.motionTransitionSnapshot(afterUpdates: true)
transitionSnapshot.frame = containerView.bounds
containerView.addSubview(transitionSnapshot)
}
fileprivate func prepareTransitionPairs() {
for from in fromSubviews {
guard 0 < from.motionTransitionIdentifier.utf16.count else {
continue
}
for to in toSubviews {
guard to.motionTransitionIdentifier == from.motionTransitionIdentifier else {
continue
}
transitionPairs.append((from, to))
}
}
}
fileprivate func prepareTransitionView() {
transitionView.frame = containerView.bounds
containerView.insertSubview(transitionView, belowSubview: transitionSnapshot)
}
fileprivate func prepareTransitionBackgroundView() {
transitionBackgroundView.backgroundColor = fromView.backgroundColor
transitionBackgroundView.frame = transitionView.bounds
transitionView.addSubview(transitionBackgroundView)
}
fileprivate func prepareTransitionToView() {
if isPresenting {
containerView.insertSubview(toView, belowSubview: transitionView)
}
toView.isHidden = false
toView.updateConstraints()
toView.setNeedsLayout()
toView.layoutIfNeeded()
}
fileprivate func prepareTransitionAnimation() {
addTransitionAnimations()
addBackgroundMotionAnimation()
cleanupAnimation()
hideFromView()
removeTransitionSnapshot()
}
}
extension MotionTransition {
fileprivate func addTransitionAnimations() {
for (from, to) in transitionPairs {
var snapshotAnimations = [CABasicAnimation]()
var snapshotChildAnimations = [CABasicAnimation]()
let sizeAnimation = Motion.size(to.bounds.size)
let cornerRadiusAnimation = Motion.corner(radius: to.cornerRadius)
snapshotAnimations.append(sizeAnimation)
snapshotAnimations.append(cornerRadiusAnimation)
snapshotAnimations.append(Motion.position(to: to.motionPosition))
snapshotAnimations.append(Motion.rotation(angle: to.motionRotationAngle))
snapshotAnimations.append(Motion.background(color: to.backgroundColor ?? .clear))
snapshotChildAnimations.append(cornerRadiusAnimation)
snapshotChildAnimations.append(sizeAnimation)
snapshotChildAnimations.append(Motion.position(x: to.bounds.width / 2, y: to.bounds.height / 2))
let d = motionDuration(animations: to.motionTransitionAnimations)
let snapshot = from.motionTransitionSnapshot(afterUpdates: true)
transitionView.addSubview(snapshot)
Motion.delay(motionDelay(animations: to.motionTransitionAnimations)) { [weak self, weak to] in
guard let s = self else {
return
}
guard let v = to else {
return
}
let tf = s.motionTimingFunction(animations: v.motionTransitionAnimations)
let snapshotGroup = Motion.animate(group: snapshotAnimations, duration: d)
snapshotGroup.fillMode = MotionAnimationFillModeToValue(mode: .forwards)
snapshotGroup.isRemovedOnCompletion = false
snapshotGroup.timingFunction = MotionAnimationTimingFunctionToValue(timingFunction: tf)
let snapshotChildGroup = Motion.animate(group: snapshotChildAnimations, duration: d)
snapshotChildGroup.fillMode = MotionAnimationFillModeToValue(mode: .forwards)
snapshotChildGroup.isRemovedOnCompletion = false
snapshotChildGroup.timingFunction = MotionAnimationTimingFunctionToValue(timingFunction: tf)
snapshot.animate(snapshotGroup)
snapshot.subviews.first!.animate(snapshotChildGroup)
}
}
}
fileprivate func addBackgroundMotionAnimation() {
transitionBackgroundView.motion(.backgroundColor(toView.backgroundColor ?? .clear), .duration(transitionDuration(using: transitionContext)))
}
}
extension MotionTransition {
fileprivate func motionDelay(animations: [MotionAnimation]) -> TimeInterval {
var t: TimeInterval = 0
for a in animations {
switch a {
case let .delay(time):
if time > delay {
delay = time
}
t = time
default:break
}
}
return t
}
fileprivate func motionDuration(animations: [MotionAnimation]) -> TimeInterval {
var t: TimeInterval = 0.35
for a in animations {
switch a {
case let .duration(time):
if time > duration {
duration = time
}
t = time
default:break
}
}
return t
}
fileprivate func motionTimingFunction(animations: [MotionAnimation]) -> MotionAnimationTimingFunction {
var t = MotionAnimationTimingFunction.easeInEaseOut
for a in animations {
switch a {
case let .timingFunction(timingFunction):
t = timingFunction
default:break
}
}
return t
}
}
extension MotionTransition {
fileprivate func cleanupAnimation() {
Motion.delay(transitionDuration(using: transitionContext)) { [weak self] in
guard let s = self else {
return
}
s.hideToSubviews()
s.clearTransitionView()
s.clearTransitionPairs()
s.completeTransition()
}
}
fileprivate func hideFromView() {
Motion.delay(delay) { [weak self] in
self?.fromView.isHidden = true
}
}
fileprivate func removeTransitionSnapshot() {
Motion.delay(delay) { [weak self] in
self?.transitionSnapshot.removeFromSuperview()
}
}
fileprivate func hideToSubviews() {
toSubviews.forEach {
$0.isHidden = false
}
}
fileprivate func clearTransitionPairs() {
transitionPairs.removeAll()
}
fileprivate func clearTransitionView() {
transitionView.removeFromSuperview()
transitionView.subviews.forEach {
$0.removeFromSuperview()
}
}
fileprivate func completeTransition() {
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
...@@ -98,9 +98,12 @@ open class NavigationBar: UINavigationBar { ...@@ -98,9 +98,12 @@ open class NavigationBar: UINavigationBar {
/// A property that accesses the backing layer's background /// A property that accesses the backing layer's background
@IBInspectable @IBInspectable
open override var backgroundColor: UIColor? { open override var backgroundColor: UIColor? {
didSet { get {
barTintColor = backgroundColor return barTintColor
} }
set(value) {
barTintColor = value
}
} }
/** /**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment