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
}
}
...@@ -86,7 +86,275 @@ public func MotionAnimationTimingFunctionToValue(timingFunction: MotionAnimation ...@@ -86,7 +86,275 @@ 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.
...@@ -95,10 +363,9 @@ public struct Motion { ...@@ -95,10 +363,9 @@ 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: DispatchTime.now() + time, execute: completion) DispatchQueue.main.asyncAfter(deadline: .now() + time, execute: completion)
} }
var cancelable: MotionDelayCancelBlock? var cancelable: MotionDelayCancelBlock?
...@@ -124,35 +391,168 @@ public struct Motion { ...@@ -124,35 +391,168 @@ public struct Motion {
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)
Motion.delay(modifiedDelay) { [weak self] in
guard let s = self else {
return
} }
/** s.prepareContainerView()
Runs an animation with a specified duration. s.prepareTransitionSnapshot()
- Parameter duration: An animation duration time. s.prepareTransitionPairs()
- Parameter animations: An animation block. s.prepareTransitionView()
- Parameter timingFunction: An MotionAnimationTimingFunction value. s.prepareTransitionBackgroundView()
- Parameter completion: A completion block that is executed once s.prepareTransitionToView()
the animations have completed. s.prepareTransitionAnimation()
*/ }
public static func animate(duration: CFTimeInterval, timingFunction: MotionAnimationTimingFunction = .easeInEaseOut, animations: (() -> Void), completion: (() -> Void)? = nil) { }
CATransaction.begin()
CATransaction.setAnimationDuration(duration) @objc(transitionDuration:)
CATransaction.setCompletionBlock(completion) open func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
CATransaction.setAnimationTimingFunction(MotionAnimationTimingFunctionToValue(timingFunction: timingFunction)) return delay + duration
animations() }
CATransaction.commit() }
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.
...@@ -160,7 +560,7 @@ public struct Motion { ...@@ -160,7 +560,7 @@ public struct Motion {
- 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
...@@ -169,19 +569,92 @@ public struct Motion { ...@@ -169,19 +569,92 @@ public struct Motion {
group.timingFunction = MotionAnimationTimingFunctionToValue(timingFunction: timingFunction) group.timingFunction = MotionAnimationTimingFunctionToValue(timingFunction: timingFunction)
return group return group
} }
}
/** extension Motion {
Executes an animation block with a given delay and duration. fileprivate func motionDelay(animations: [MotionAnimation]) -> TimeInterval {
- Parameter delay time: A CFTimeInterval. var t: TimeInterval = 0
- Parameter duration: An animation duration time. for a in animations {
- Parameter animations: An animation block. switch a {
- Parameter completion: A completion block that is executed once case let .delay(time):
the animations have completed. if time > delay {
*/ delay = time
public static func animate(delay time: CFTimeInterval, duration: CFTimeInterval, animations: @escaping (() -> Void), completion: (() -> Void)? = nil) { }
delay(time) { t = time
animate(duration: duration, animations: animations, completion: completion) 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)
...@@ -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):
...@@ -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
...@@ -395,6 +414,17 @@ extension Motion { ...@@ -395,6 +414,17 @@ extension Motion {
} }
/** /**
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.
......
/*
* 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,8 +98,11 @@ open class NavigationBar: UINavigationBar { ...@@ -98,8 +98,11 @@ 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