Commit 100bf243 by Daniel Dahan

updated to 2 space indentation

parent 654928d7
...@@ -29,47 +29,47 @@ ...@@ -29,47 +29,47 @@
import UIKit import UIKit
public protocol MotionAnimator: class { public protocol MotionAnimator: class {
/// A reference to Motion. /// A reference to Motion.
weak var motion: MotionTransition! { get set } weak var motion: MotionTransition! { get set }
/// Cleans the contexts. /// Cleans the contexts.
func clean() func clean()
/** /**
A function that determines if a view can be animated. A function that determines if a view can be animated.
- Parameter view: A UIView. - Parameter view: A UIView.
- Parameter isAppearing: A boolean that determines whether the - Parameter isAppearing: A boolean that determines whether the
view is appearing. view is appearing.
*/ */
func canAnimate(view: UIView, isAppearing: Bool) -> Bool func canAnimate(view: UIView, isAppearing: Bool) -> Bool
/** /**
Animates the fromViews to the toViews. Animates the fromViews to the toViews.
- Parameter fromViews: An Array of UIViews. - Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews. - Parameter toViews: An Array of UIViews.
- Returns: A TimeInterval. - Returns: A TimeInterval.
*/ */
func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval
/** /**
Moves the view's animation to the given elapsed time. Moves the view's animation to the given elapsed time.
- Parameter to progress: A TimeInterval. - Parameter to progress: A TimeInterval.
*/ */
func seek(to progress: TimeInterval) func seek(to progress: TimeInterval)
/** /**
Resumes the animation with a given elapsed time and Resumes the animation with a given elapsed time and
optional reversed boolean. optional reversed boolean.
- Parameter at progress: A TimeInterval. - Parameter at progress: A TimeInterval.
- Parameter isReversed: A boolean to reverse the animation - Parameter isReversed: A boolean to reverse the animation
or not. or not.
*/ */
func resume(at progress: TimeInterval, isReversed: Bool) -> TimeInterval func resume(at progress: TimeInterval, isReversed: Bool) -> TimeInterval
/** /**
Applies the given state to the given view. Applies the given state to the given view.
- Parameter state: A MotionModifier. - Parameter state: A MotionModifier.
- Parameter to view: A UIView. - Parameter to view: A UIView.
*/ */
func apply(state: MotionTargetState, to view: UIView) func apply(state: MotionTargetState, to view: UIView)
} }
...@@ -29,94 +29,94 @@ ...@@ -29,94 +29,94 @@
import UIKit import UIKit
internal class MotionAnimatorViewContext { internal class MotionAnimatorViewContext {
/// An optional reference to a MotionAnimator. /// An optional reference to a MotionAnimator.
var animator: MotionAnimator? var animator: MotionAnimator?
/// A reference to the snapshot UIView. /// A reference to the snapshot UIView.
var snapshot: UIView var snapshot: UIView
/// The animation target state. /// The animation target state.
var targetState: MotionTargetState var targetState: MotionTargetState
/// A boolean indicating if the view is appearing. /// A boolean indicating if the view is appearing.
var isAppearing: Bool var isAppearing: Bool
/// Animation duration time. /// Animation duration time.
var duration: TimeInterval = 0 var duration: TimeInterval = 0
/// The computed current time of the snapshot layer. /// The computed current time of the snapshot layer.
var currentTime: TimeInterval { var currentTime: TimeInterval {
return snapshot.layer.convertTime(CACurrentMediaTime(), from: nil) return snapshot.layer.convertTime(CACurrentMediaTime(), from: nil)
} }
/// A container view for the transition. /// A container view for the transition.
var container: UIView? { var container: UIView? {
return animator?.motion.context.container return animator?.motion.context.container
} }
/** /**
An initializer. An initializer.
- Parameter animator: A MotionAnimator. - Parameter animator: A MotionAnimator.
- Parameter snapshot: A UIView. - Parameter snapshot: A UIView.
- Parameter targetState: A MotionModifier. - Parameter targetState: A MotionModifier.
- Parameter isAppearing: A Boolean. - Parameter isAppearing: A Boolean.
*/ */
required init(animator: MotionAnimator, snapshot: UIView, targetState: MotionTargetState, isAppearing: Bool) { required init(animator: MotionAnimator, snapshot: UIView, targetState: MotionTargetState, isAppearing: Bool) {
self.animator = animator self.animator = animator
self.snapshot = snapshot self.snapshot = snapshot
self.targetState = targetState self.targetState = targetState
self.isAppearing = isAppearing self.isAppearing = isAppearing
} }
/// Cleans the context. /// Cleans the context.
func clean() { func clean() {
animator = nil animator = nil
} }
/** /**
A class function that determines if a view can be animated A class function that determines if a view can be animated
to a given state. to a given state.
- Parameter view: A UIView. - Parameter view: A UIView.
- Parameter state: A MotionModifier. - Parameter state: A MotionModifier.
- Parameter isAppearing: A boolean that determines whether the - Parameter isAppearing: A boolean that determines whether the
view is appearing. view is appearing.
*/ */
class func canAnimate(view: UIView, state: MotionTargetState, isAppearing: Bool) -> Bool { class func canAnimate(view: UIView, state: MotionTargetState, isAppearing: Bool) -> Bool {
return false return false
} }
/** /**
Resumes the animation with a given elapsed time and Resumes the animation with a given elapsed time and
optional reversed boolean. optional reversed boolean.
- Parameter at progress: A TimeInterval. - Parameter at progress: A TimeInterval.
- Parameter isReversed: A boolean to reverse the animation - Parameter isReversed: A boolean to reverse the animation
or not. or not.
- Returns: A TimeInterval. - Returns: A TimeInterval.
*/ */
@discardableResult @discardableResult
func resume(at progress: TimeInterval, isReversed: Bool) -> TimeInterval { func resume(at progress: TimeInterval, isReversed: Bool) -> TimeInterval {
return 0 return 0
} }
/** /**
Moves the animation to the given elapsed time. Moves the animation to the given elapsed time.
- Parameter to progress: A TimeInterval. - Parameter to progress: A TimeInterval.
*/ */
func seek(to progress: TimeInterval) {} func seek(to progress: TimeInterval) {}
/** /**
Applies the given state to the target state. Applies the given state to the target state.
- Parameter state: A MotionModifier. - Parameter state: A MotionModifier.
*/ */
func apply(state: MotionTargetState) {} func apply(state: MotionTargetState) {}
/** /**
Starts the animations with an appearing boolean flag. Starts the animations with an appearing boolean flag.
- Parameter isAppearing: A boolean value whether the view - Parameter isAppearing: A boolean value whether the view
is appearing or not. is appearing or not.
*/ */
@discardableResult @discardableResult
func startAnimations() -> TimeInterval { func startAnimations() -> TimeInterval {
return 0 return 0
} }
} }
...@@ -29,138 +29,138 @@ ...@@ -29,138 +29,138 @@
import UIKit import UIKit
internal class MotionCoreAnimator<T: MotionAnimatorViewContext>: MotionAnimator { internal class MotionCoreAnimator<T: MotionAnimatorViewContext>: MotionAnimator {
weak public var motion: MotionTransition! weak public var motion: MotionTransition!
/// A reference to the MotionContext.
public var context: MotionContext! {
return motion.context
}
/// An index of views to their corresponding animator context.
var viewToContexts = [UIView: T]()
/// Cleans the contexts.
func clean() {
for v in viewToContexts.values {
v.clean()
}
/// A reference to the MotionContext. viewToContexts.removeAll()
public var context: MotionContext! { }
return motion.context
/**
A function that determines if a view can be animated.
- Parameter view: A UIView.
- Parameter isAppearing: A boolean that determines whether the
view is appearing.
*/
func canAnimate(view: UIView, isAppearing: Bool) -> Bool {
guard let state = context[view] else {
return false
} }
/// An index of views to their corresponding animator context. return T.canAnimate(view: view, state: state, isAppearing: isAppearing)
var viewToContexts = [UIView: T]() }
/**
Animates the fromViews to the toViews.
- Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews.
- Returns: A TimeInterval.
*/
func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval {
var d: TimeInterval = 0
/// Cleans the contexts. for v in fromViews {
func clean() { createViewContext(view: v, isAppearing: false)
for v in viewToContexts.values {
v.clean()
}
viewToContexts.removeAll()
} }
/** for v in toViews {
A function that determines if a view can be animated. createViewContext(view: v, isAppearing: true)
- Parameter view: A UIView.
- Parameter isAppearing: A boolean that determines whether the
view is appearing.
*/
func canAnimate(view: UIView, isAppearing: Bool) -> Bool {
guard let state = context[view] else {
return false
}
return T.canAnimate(view: view, state: state, isAppearing: isAppearing)
} }
/** for v in viewToContexts.values {
Animates the fromViews to the toViews. if let duration = v.targetState.duration, .infinity != duration {
- Parameter fromViews: An Array of UIViews. v.duration = duration
- Parameter toViews: An Array of UIViews. d = max(d, duration)
- Returns: A TimeInterval.
*/
func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval {
var d: TimeInterval = 0
for v in fromViews { } else {
createViewContext(view: v, isAppearing: false) let duration = v.snapshot.optimizedDuration(targetState: v.targetState)
}
for v in toViews {
createViewContext(view: v, isAppearing: true)
}
for v in viewToContexts.values {
if let duration = v.targetState.duration, .infinity != duration {
v.duration = duration
d = max(d, duration)
} else {
let duration = v.snapshot.optimizedDuration(targetState: v.targetState)
if nil == v.targetState.duration {
v.duration = duration
}
d = max(d, duration)
}
}
for v in viewToContexts.values { if nil == v.targetState.duration {
if .infinity == v.targetState.duration { v.duration = duration
v.duration = d
}
d = max(d, v.startAnimations())
} }
return d d = max(d, duration)
}
} }
/** for v in viewToContexts.values {
Moves the view's animation to the given elapsed time. if .infinity == v.targetState.duration {
- Parameter to progress: A TimeInterval. v.duration = d
*/ }
func seek(to progress: TimeInterval) {
for v in viewToContexts.values { d = max(d, v.startAnimations())
v.seek(to: progress)
}
} }
/** return d
Resumes the animation with a given elapsed time and }
optional reversed boolean.
- Parameter at progress: A TimeInterval. /**
- Parameter isReversed: A boolean to reverse the animation Moves the view's animation to the given elapsed time.
or not. - Parameter to progress: A TimeInterval.
*/ */
func resume(at progress: TimeInterval, isReversed: Bool) -> TimeInterval { func seek(to progress: TimeInterval) {
var duration: TimeInterval = 0 for v in viewToContexts.values {
v.seek(to: progress)
for (_, v) in viewToContexts {
if nil == v.targetState.duration {
v.duration = max(v.duration, v.snapshot.optimizedDuration(targetState: v.targetState) + progress)
}
duration = max(duration, v.resume(at: progress, isReversed: isReversed))
}
return duration
} }
}
/**
Resumes the animation with a given elapsed time and
optional reversed boolean.
- Parameter at progress: A TimeInterval.
- Parameter isReversed: A boolean to reverse the animation
or not.
*/
func resume(at progress: TimeInterval, isReversed: Bool) -> TimeInterval {
var duration: TimeInterval = 0
/** for (_, v) in viewToContexts {
Applies the given state to the given view. if nil == v.targetState.duration {
- Parameter state: A MotionModifier. v.duration = max(v.duration, v.snapshot.optimizedDuration(targetState: v.targetState) + progress)
- Parameter to view: A UIView. }
*/
func apply(state: MotionTargetState, to view: UIView) { duration = max(duration, v.resume(at: progress, isReversed: isReversed))
guard let v = viewToContexts[view] else { }
return
} return duration
}
v.apply(state: state)
/**
Applies the given state to the given view.
- Parameter state: A MotionModifier.
- Parameter to view: A UIView.
*/
func apply(state: MotionTargetState, to view: UIView) {
guard let v = viewToContexts[view] else {
return
} }
v.apply(state: state)
}
} }
fileprivate extension MotionCoreAnimator { fileprivate extension MotionCoreAnimator {
/** /**
Creates a view context for a given view. Creates a view context for a given view.
- Parameter view: A UIView. - Parameter view: A UIView.
- Parameter isAppearing: A boolean that determines whether the - Parameter isAppearing: A boolean that determines whether the
view is appearing. view is appearing.
*/ */
func createViewContext(view: UIView, isAppearing: Bool) { func createViewContext(view: UIView, isAppearing: Bool) {
viewToContexts[view] = T(animator: self, snapshot: context.snapshotView(for: view), targetState: context[view]!, isAppearing: isAppearing) viewToContexts[view] = T(animator: self, snapshot: context.snapshotView(for: view), targetState: context[view]!, isAppearing: isAppearing)
} }
} }
...@@ -30,76 +30,76 @@ import UIKit ...@@ -30,76 +30,76 @@ import UIKit
@available(iOS 10, tvOS 10, *) @available(iOS 10, tvOS 10, *)
internal class MotionViewPropertyViewContext: MotionAnimatorViewContext { internal class MotionViewPropertyViewContext: MotionAnimatorViewContext {
/// A reference to the UIViewPropertyAnimator. /// A reference to the UIViewPropertyAnimator.
fileprivate var viewPropertyAnimator: UIViewPropertyAnimator! fileprivate var viewPropertyAnimator: UIViewPropertyAnimator!
/// Ending effect. /// Ending effect.
fileprivate var endEffect: UIVisualEffect? fileprivate var endEffect: UIVisualEffect?
/// Starting effect. /// Starting effect.
fileprivate var startEffect: UIVisualEffect? fileprivate var startEffect: UIVisualEffect?
override class func canAnimate(view: UIView, state: MotionTargetState, isAppearing: Bool) -> Bool { override class func canAnimate(view: UIView, state: MotionTargetState, isAppearing: Bool) -> Bool {
return view is UIVisualEffectView && nil != state.opacity return view is UIVisualEffectView && nil != state.opacity
}
override func resume(at progress: TimeInterval, isReversed: Bool) -> TimeInterval {
guard let visualEffectView = snapshot as? UIVisualEffectView else {
return 0
} }
override func resume(at progress: TimeInterval, isReversed: Bool) -> TimeInterval { if isReversed {
guard let visualEffectView = snapshot as? UIVisualEffectView else { viewPropertyAnimator?.stopAnimation(false)
return 0 viewPropertyAnimator?.finishAnimation(at: .current)
} viewPropertyAnimator = UIViewPropertyAnimator(duration: duration, curve: .linear) { [weak self] in
guard let `self` = self else {
if isReversed { return
viewPropertyAnimator?.stopAnimation(false)
viewPropertyAnimator?.finishAnimation(at: .current)
viewPropertyAnimator = UIViewPropertyAnimator(duration: duration, curve: .linear) { [weak self] in
guard let `self` = self else {
return
}
visualEffectView.effect = isReversed ? self.startEffect : self.endEffect
}
} }
viewPropertyAnimator.startAnimation() visualEffectView.effect = isReversed ? self.startEffect : self.endEffect
}
return duration
}
override func seek(to progress: TimeInterval) {
viewPropertyAnimator?.pauseAnimation()
viewPropertyAnimator?.fractionComplete = CGFloat(progress / duration)
} }
override func clean() { viewPropertyAnimator.startAnimation()
super.clean()
viewPropertyAnimator?.stopAnimation(false) return duration
viewPropertyAnimator?.finishAnimation(at: .current) }
viewPropertyAnimator = nil
override func seek(to progress: TimeInterval) {
viewPropertyAnimator?.pauseAnimation()
viewPropertyAnimator?.fractionComplete = CGFloat(progress / duration)
}
override func clean() {
super.clean()
viewPropertyAnimator?.stopAnimation(false)
viewPropertyAnimator?.finishAnimation(at: .current)
viewPropertyAnimator = nil
}
override func startAnimations() -> TimeInterval {
guard let visualEffectView = snapshot as? UIVisualEffectView else {
return 0
} }
override func startAnimations() -> TimeInterval { let appearedEffect = visualEffectView.effect
guard let visualEffectView = snapshot as? UIVisualEffectView else { let disappearedEffect = 0 == targetState.opacity ? nil : visualEffectView.effect
return 0
} startEffect = isAppearing ? disappearedEffect : appearedEffect
endEffect = isAppearing ? appearedEffect : disappearedEffect
let appearedEffect = visualEffectView.effect
let disappearedEffect = 0 == targetState.opacity ? nil : visualEffectView.effect visualEffectView.effect = startEffect
startEffect = isAppearing ? disappearedEffect : appearedEffect viewPropertyAnimator = UIViewPropertyAnimator(duration: duration, curve: .linear) { [weak self] in
endEffect = isAppearing ? appearedEffect : disappearedEffect guard let `self` = self else {
return
visualEffectView.effect = startEffect }
viewPropertyAnimator = UIViewPropertyAnimator(duration: duration, curve: .linear) { [weak self] in visualEffectView.effect = self.endEffect
guard let `self` = self else {
return
}
visualEffectView.effect = self.endEffect
}
viewPropertyAnimator.startAnimation()
return duration
} }
viewPropertyAnimator.startAnimation()
return duration
}
} }
...@@ -29,16 +29,16 @@ ...@@ -29,16 +29,16 @@
import UIKit import UIKit
internal extension Array { internal extension Array {
/** /**
Retrieves the element at the given index if it exists. Retrieves the element at the given index if it exists.
- Parameter _ index: An Int. - Parameter _ index: An Int.
- Returns: An optional Element value. - Returns: An optional Element value.
*/ */
func get(_ index: Int) -> Element? { func get(_ index: Int) -> Element? {
if index < count { if index < count {
return self[index] return self[index]
}
return nil
} }
return nil
}
} }
...@@ -29,18 +29,18 @@ ...@@ -29,18 +29,18 @@
import UIKit import UIKit
public extension CAMediaTimingFunction { public extension CAMediaTimingFunction {
// Default // Default
static let linear = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) static let linear = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
static let easeIn = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn) static let easeIn = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
static let easeOut = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) static let easeOut = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
static let easeInOut = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) static let easeInOut = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
// Material // Material
static let standard = CAMediaTimingFunction(controlPoints: 0.4, 0.0, 0.2, 1.0) static let standard = CAMediaTimingFunction(controlPoints: 0.4, 0.0, 0.2, 1.0)
static let deceleration = CAMediaTimingFunction(controlPoints: 0.0, 0.0, 0.2, 1) static let deceleration = CAMediaTimingFunction(controlPoints: 0.0, 0.0, 0.2, 1)
static let acceleration = CAMediaTimingFunction(controlPoints: 0.4, 0.0, 1, 1) static let acceleration = CAMediaTimingFunction(controlPoints: 0.4, 0.0, 1, 1)
static let sharp = CAMediaTimingFunction(controlPoints: 0.4, 0.0, 0.6, 1) static let sharp = CAMediaTimingFunction(controlPoints: 0.4, 0.0, 0.6, 1)
// Easing.net // Easing.net
static let easeOutBack = CAMediaTimingFunction(controlPoints: 0.175, 0.885, 0.32, 1.75) static let easeOutBack = CAMediaTimingFunction(controlPoints: 0.175, 0.885, 0.32, 1.75)
} }
...@@ -24,31 +24,31 @@ ...@@ -24,31 +24,31 @@
*/ */
public struct AssociatedObject { public struct AssociatedObject {
/** /**
Gets the Obj-C reference for the instance object within the UIView extension. Gets the Obj-C reference for the instance object within the UIView extension.
- Parameter base: Base object. - Parameter base: Base object.
- Parameter key: Memory key pointer. - Parameter key: Memory key pointer.
- Parameter initializer: Object initializer. - Parameter initializer: Object initializer.
- Returns: The associated reference for the initializer object. - Returns: The associated reference for the initializer object.
*/ */
public static func get<T: Any>(base: Any, key: UnsafePointer<UInt8>, initializer: () -> T) -> T { public static func get<T: Any>(base: Any, key: UnsafePointer<UInt8>, initializer: () -> T) -> T {
if let v = objc_getAssociatedObject(base, key) as? T { if let v = objc_getAssociatedObject(base, key) as? T {
return v return v
}
let v = initializer()
objc_setAssociatedObject(base, key, v, .OBJC_ASSOCIATION_RETAIN)
return v
} }
/** let v = initializer()
Sets the Obj-C reference for the instance object within the UIView extension. objc_setAssociatedObject(base, key, v, .OBJC_ASSOCIATION_RETAIN)
- Parameter base: Base object. return v
- Parameter key: Memory key pointer. }
- Parameter value: The object instance to set for the associated object.
- Returns: The associated reference for the initializer object. /**
*/ Sets the Obj-C reference for the instance object within the UIView extension.
public static func set<T: Any>(base: Any, key: UnsafePointer<UInt8>, value: T) { - Parameter base: Base object.
objc_setAssociatedObject(base, key, value, .OBJC_ASSOCIATION_RETAIN) - Parameter key: Memory key pointer.
} - Parameter value: The object instance to set for the associated object.
- Returns: The associated reference for the initializer object.
*/
public static func set<T: Any>(base: Any, key: UnsafePointer<UInt8>, value: T) {
objc_setAssociatedObject(base, key, value, .OBJC_ASSOCIATION_RETAIN)
}
} }
...@@ -32,27 +32,27 @@ fileprivate let parameterRegex = "(?:\\-?\\d+(\\.?\\d+)?)|\\w+" ...@@ -32,27 +32,27 @@ fileprivate let parameterRegex = "(?:\\-?\\d+(\\.?\\d+)?)|\\w+"
fileprivate let transitionsRegex = "(\\w+)(?:\\(([^\\)]*)\\))?" fileprivate let transitionsRegex = "(\\w+)(?:\\(([^\\)]*)\\))?"
internal extension NSObject { internal extension NSObject {
/// Copies an object using NSKeyedArchiver. /// Copies an object using NSKeyedArchiver.
func copyWithArchiver() -> Any? { func copyWithArchiver() -> Any? {
return NSKeyedUnarchiver.unarchiveObject(with: NSKeyedArchiver.archivedData(withRootObject: self))! return NSKeyedUnarchiver.unarchiveObject(with: NSKeyedArchiver.archivedData(withRootObject: self))!
} }
} }
internal extension UIColor { internal extension UIColor {
/// A tuple of the rgba components. /// A tuple of the rgba components.
var components: (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) { var components: (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) {
var r: CGFloat = 0 var r: CGFloat = 0
var g: CGFloat = 0 var g: CGFloat = 0
var b: CGFloat = 0 var b: CGFloat = 0
var a: CGFloat = 0 var a: CGFloat = 0
getRed(&r, green: &g, blue: &b, alpha: &a)
return (r, g, b, a)
}
/// The alpha component value. getRed(&r, green: &g, blue: &b, alpha: &a)
var alphaComponent: CGFloat {
return components.a return (r, g, b, a)
} }
/// The alpha component value.
var alphaComponent: CGFloat {
return components.a
}
} }
...@@ -27,10 +27,10 @@ import UIKit ...@@ -27,10 +27,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
} }
/** /**
...@@ -38,14 +38,14 @@ public enum MotionAnimationFillMode: Int { ...@@ -38,14 +38,14 @@ 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
} }
} }
...@@ -29,122 +29,122 @@ ...@@ -29,122 +29,122 @@
import UIKit import UIKit
public struct MotionAnimationState { public struct MotionAnimationState {
/// A reference to the position. /// A reference to the position.
public var position: CGPoint? public var position: CGPoint?
/// A reference to the size. /// A reference to the size.
public var size: CGSize? public var size: CGSize?
/// A reference to the transform. /// A reference to the transform.
public var transform: CATransform3D? public var transform: CATransform3D?
/// A reference to the spin tuple. /// A reference to the spin tuple.
public var spin: (x: CGFloat, y: CGFloat, z: CGFloat)? public var spin: (x: CGFloat, y: CGFloat, z: CGFloat)?
/// A reference to the opacity. /// A reference to the opacity.
public var opacity: Double? public var opacity: Double?
/// A reference to the cornerRadius. /// A reference to the cornerRadius.
public var cornerRadius: CGFloat? public var cornerRadius: CGFloat?
/// A reference to the backgroundColor. /// A reference to the backgroundColor.
public var backgroundColor: CGColor? public var backgroundColor: CGColor?
/// A reference to the zPosition. /// A reference to the zPosition.
public var zPosition: CGFloat? public var zPosition: CGFloat?
/// A reference to the borderWidth. /// A reference to the borderWidth.
public var borderWidth: CGFloat? public var borderWidth: CGFloat?
/// A reference to the borderColor. /// A reference to the borderColor.
public var borderColor: CGColor? public var borderColor: CGColor?
/// A reference to the shadowColor. /// A reference to the shadowColor.
public var shadowColor: CGColor? public var shadowColor: CGColor?
/// A reference to the shadowOpacity. /// A reference to the shadowOpacity.
public var shadowOpacity: Float? public var shadowOpacity: Float?
/// A reference to the shadowOffset. /// A reference to the shadowOffset.
public var shadowOffset: CGSize? public var shadowOffset: CGSize?
/// A reference to the shadowRadius. /// A reference to the shadowRadius.
public var shadowRadius: CGFloat? public var shadowRadius: CGFloat?
/// A reference to the shadowPath. /// A reference to the shadowPath.
public var shadowPath: CGPath? public var shadowPath: CGPath?
/// A reference to the spring animation settings. /// A reference to the spring animation settings.
public var spring: (CGFloat, CGFloat)? public var spring: (CGFloat, CGFloat)?
/// A time delay on starting the animation. /// A time delay on starting the animation.
public var delay: TimeInterval = 0 public var delay: TimeInterval = 0
/// The duration of the animation. /// The duration of the animation.
public var duration: TimeInterval = 0.35 public var duration: TimeInterval = 0.35
/// The timing function value of the animation. /// The timing function value of the animation.
public var timingFunction = CAMediaTimingFunction.easeInOut public var timingFunction = CAMediaTimingFunction.easeInOut
/// Custom target states. /// Custom target states.
public var custom: [String: Any]? public var custom: [String: Any]?
/// Completion block. /// Completion block.
public var completion: (() -> Void)? public var completion: (() -> Void)?
/** /**
An initializer that accepts an Array of MotionAnimations. An initializer that accepts an Array of MotionAnimations.
- Parameter animations: An Array of MotionAnimations. - Parameter animations: An Array of MotionAnimations.
*/ */
init(animations: [MotionAnimation]) { init(animations: [MotionAnimation]) {
append(contentsOf: animations) append(contentsOf: animations)
} }
} }
extension MotionAnimationState { extension MotionAnimationState {
/** /**
Adds a MotionAnimation to the current state. Adds a MotionAnimation to the current state.
- Parameter _ element: A MotionAnimation. - Parameter _ element: A MotionAnimation.
*/ */
public mutating func append(_ element: MotionAnimation) { public mutating func append(_ element: MotionAnimation) {
element.apply(&self) element.apply(&self)
}
/**
Adds an Array of MotionAnimations to the current state.
- Parameter contentsOf elements: An Array of MotionAnimations.
*/
public mutating func append(contentsOf elements: [MotionAnimation]) {
for v in elements {
v.apply(&self)
} }
}
/**
Adds an Array of MotionAnimations to the current state. /**
- Parameter contentsOf elements: An Array of MotionAnimations. A subscript that returns a custom value for a specified key.
*/ - Parameter key: A String.
public mutating func append(contentsOf elements: [MotionAnimation]) { - Returns: An optional Any value.
for v in elements { */
v.apply(&self) public subscript(key: String) -> Any? {
} get {
return custom?[key]
} }
set(value) {
/** if nil == custom {
A subscript that returns a custom value for a specified key. custom = [:]
- Parameter key: A String. }
- Returns: An optional Any value.
*/ custom![key] = value
public subscript(key: String) -> Any? {
get {
return custom?[key]
}
set(value) {
if nil == custom {
custom = [:]
}
custom![key] = value
}
} }
}
} }
extension MotionAnimationState: ExpressibleByArrayLiteral { extension MotionAnimationState: ExpressibleByArrayLiteral {
/** /**
An initializer implementing the ExpressibleByArrayLiteral protocol. An initializer implementing the ExpressibleByArrayLiteral protocol.
- Parameter arrayLiteral elements: A list of MotionAnimations. - Parameter arrayLiteral elements: A list of MotionAnimations.
*/ */
public init(arrayLiteral elements: MotionAnimation...) { public init(arrayLiteral elements: MotionAnimation...) {
append(contentsOf: elements) append(contentsOf: elements)
} }
} }
...@@ -29,6 +29,6 @@ ...@@ -29,6 +29,6 @@
import UIKit import UIKit
public enum MotionCoordinateSpace { public enum MotionCoordinateSpace {
case global case global
case local case local
} }
...@@ -30,84 +30,84 @@ import UIKit ...@@ -30,84 +30,84 @@ import UIKit
class MotionPlugin: MotionCorePreprocessor, MotionAnimator { class MotionPlugin: MotionCorePreprocessor, MotionAnimator {
/** /**
Determines whether or not to receive `seekTo` callback on every frame. Determines whether or not to receive `seekTo` callback on every frame.
Default is false. Default is false.
When **requirePerFrameCallback** is **false**, the plugin needs to start its own animations inside `animate` & `resume` When **requirePerFrameCallback** is **false**, the plugin needs to start its own animations inside `animate` & `resume`
The `seekTo` method is only being called during an interactive transition. The `seekTo` method is only being called during an interactive transition.
When **requirePerFrameCallback** is **true**, the plugin will receive `seekTo` callback on every animation frame. Hence it is possible for the plugin to do per-frame animations without implementing `animate` & `resume` When **requirePerFrameCallback** is **true**, the plugin will receive `seekTo` callback on every animation frame. Hence it is possible for the plugin to do per-frame animations without implementing `animate` & `resume`
*/ */
open var requirePerFrameCallback = false open var requirePerFrameCallback = false
public override required init() {} public override required init() {}
/** /**
Called before any animation. Called before any animation.
Override this method when you want to preprocess transitions for views Override this method when you want to preprocess transitions for views
- Parameters: - Parameters:
- context: object holding all parsed and changed transitions, - context: object holding all parsed and changed transitions,
- fromViews: A flattened list of all views from source ViewController - fromViews: A flattened list of all views from source ViewController
- toViews: A flattened list of all views from destination ViewController - toViews: A flattened list of all views from destination ViewController
To check a view's transitions: To check a view's transitions:
context[view] context[view]
context[view, "transitionName"] context[view, "transitionName"]
To set a view's transitions: To set a view's transitions:
context[view] = [("transition1", ["parameter1"]), ("transition2", [])] context[view] = [("transition1", ["parameter1"]), ("transition2", [])]
context[view, "transition1"] = ["parameter1", "parameter2"] context[view, "transition1"] = ["parameter1", "parameter2"]
*/ */
open override func process(fromViews: [UIView], toViews: [UIView]) {} open override func process(fromViews: [UIView], toViews: [UIView]) {}
/** /**
- Returns: return true if the plugin can handle animating the view. - Returns: return true if the plugin can handle animating the view.
- Parameters: - Parameters:
- context: object holding all parsed and changed transitions, - context: object holding all parsed and changed transitions,
- view: the view to check whether or not the plugin can handle the animation - view: the view to check whether or not the plugin can handle the animation
- isAppearing: true if the view is isAppearing(i.e. a view in destination ViewController) - isAppearing: true if the view is isAppearing(i.e. a view in destination ViewController)
If return true, Motion won't animate and won't let any other plugins animate this view. If return true, Motion won't animate and won't let any other plugins animate this view.
The view will also be hidden automatically during the animation. The view will also be hidden automatically during the animation.
*/ */
open func canAnimate(view: UIView, isAppearing: Bool) -> Bool { return false } open func canAnimate(view: UIView, isAppearing: Bool) -> Bool { return false }
/** /**
Perform the animation. Perform the animation.
Note: views in `fromViews` & `toViews` are hidden already. Unhide then if you need to take snapshots. Note: views in `fromViews` & `toViews` are hidden already. Unhide then if you need to take snapshots.
- Parameters: - Parameters:
- context: object holding all parsed and changed transitions, - context: object holding all parsed and changed transitions,
- fromViews: A flattened list of all views from source ViewController (filtered by `canAnimate`) - fromViews: A flattened list of all views from source ViewController (filtered by `canAnimate`)
- toViews: A flattened list of all views from destination ViewController (filtered by `canAnimate`) - toViews: A flattened list of all views from destination ViewController (filtered by `canAnimate`)
- Returns: The duration needed to complete the animation - Returns: The duration needed to complete the animation
*/ */
open func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval { return 0 } open func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval { return 0 }
/** /**
Called when all animations are completed. Called when all animations are completed.
Should perform cleanup and release any reference Should perform cleanup and release any reference
*/ */
open func clean() {} open func clean() {}
/** /**
For supporting interactive animation only. For supporting interactive animation only.
This method is called when an interactive animation is in place This method is called when an interactive animation is in place
The plugin should pause the animation, and seek to the given progress The plugin should pause the animation, and seek to the given progress
- Parameters: - Parameters:
- progress: time of the animation to seek to. - progress: time of the animation to seek to.
*/ */
open func seek(to progress: TimeInterval) {} open func seek(to progress: TimeInterval) {}
/** /**
For supporting interactive animation only. For supporting interactive animation only.
This method is called when an interactive animation is ended This method is called when an interactive animation is ended
The plugin should resume the animation. The plugin should resume the animation.
- Parameters: - Parameters:
...@@ -115,42 +115,42 @@ class MotionPlugin: MotionCorePreprocessor, MotionAnimator { ...@@ -115,42 +115,42 @@ class MotionPlugin: MotionCorePreprocessor, MotionAnimator {
- reverse: a boolean value indicating whether or not the animation should reverse - reverse: a boolean value indicating whether or not the animation should reverse
*/ */
open func resume(at progress: TimeInterval, isReversed: Bool) -> TimeInterval { return 0 } open func resume(at progress: TimeInterval, isReversed: Bool) -> TimeInterval { return 0 }
/** /**
For supporting interactive animation only. For supporting interactive animation only.
This method is called when user wants to override animation transitions during an interactive animation This method is called when user wants to override animation transitions during an interactive animation
- Parameters: - Parameters:
- state: the target state to override - state: the target state to override
- view: the view to override - view: the view to override
*/ */
open func apply(state: MotionTargetState, to view: UIView) {} open func apply(state: MotionTargetState, to view: UIView) {}
} }
// methods for enable/disable the current plugin // methods for enable/disable the current plugin
extension MotionPlugin { extension MotionPlugin {
/// A boolean indicating whether plugins are available. /// A boolean indicating whether plugins are available.
public static var isEnabled: Bool { public static var isEnabled: Bool {
get { get {
return MotionTransition.isEnabled(plugin: self) return MotionTransition.isEnabled(plugin: self)
}
set(value) {
if value {
enable()
} else {
disable()
}
}
}
/// Enable plugins.
public static func enable() {
MotionTransition.enable(plugin: self)
} }
set(value) {
/// Disable plugins. if value {
public static func disable() { enable()
MotionTransition.disable(plugin: self) } else {
disable()
}
} }
}
/// Enable plugins.
public static func enable() {
MotionTransition.enable(plugin: self)
}
/// Disable plugins.
public static func disable() {
MotionTransition.disable(plugin: self)
}
} }
...@@ -29,24 +29,24 @@ ...@@ -29,24 +29,24 @@
import UIKit import UIKit
public enum MotionSnapshotType { public enum MotionSnapshotType {
/** /**
This setting will optimize for different types of views. This setting will optimize for different types of views.
For custom views or views with masking, .optimizedDefault might For custom views or views with masking, .optimizedDefault might
create snapshots that appear differently than the actual view. create snapshots that appear differently than the actual view.
In that case, use .normal or .slowRender to disable the optimization. In that case, use .normal or .slowRender to disable the optimization.
*/ */
case optimized case optimized
/// snapshotView(afterScreenUpdates:) /// snapshotView(afterScreenUpdates:)
case normal case normal
/// layer.render(in: currentContext) /// layer.render(in: currentContext)
case layerRender case layerRender
/** /**
This setting will not create a snapshot. It will animate the view directly. This setting will not create a snapshot. It will animate the view directly.
This will mess up the view hierarchy, therefore, view controllers have to rebuild This will mess up the view hierarchy, therefore, view controllers have to rebuild
their view structure after the transition finishes. their view structure after the transition finishes.
*/ */
case noSnapshot case noSnapshot
} }
...@@ -29,166 +29,166 @@ ...@@ -29,166 +29,166 @@
import UIKit import UIKit
public struct MotionTargetState { public struct MotionTargetState {
/// The identifier value to match source and destination views. /// The identifier value to match source and destination views.
public var motionIdentifier: String? public var motionIdentifier: String?
/// The initial state that the transition will start at. /// The initial state that the transition will start at.
internal var beginState: [MotionModifier]? internal var beginState: [MotionModifier]?
public var conditionalModifiers: [((MotionConditionalContext) -> Bool, [MotionModifier])]? public var conditionalModifiers: [((MotionConditionalContext) -> Bool, [MotionModifier])]?
/// A reference to the position. /// A reference to the position.
public var position: CGPoint? public var position: CGPoint?
/// A reference to the size. /// A reference to the size.
public var size: CGSize? public var size: CGSize?
/// A reference to the transform. /// A reference to the transform.
public var transform: CATransform3D? public var transform: CATransform3D?
/// A reference to the opacity. /// A reference to the opacity.
public var opacity: Double? public var opacity: Double?
/// A reference to the cornerRadius. /// A reference to the cornerRadius.
public var cornerRadius: CGFloat? public var cornerRadius: CGFloat?
/// A reference to the backgroundColor. /// A reference to the backgroundColor.
public var backgroundColor: CGColor? public var backgroundColor: CGColor?
/// A reference to the zPosition. /// A reference to the zPosition.
public var zPosition: CGFloat? public var zPosition: CGFloat?
/// A reference to the contentsRect. /// A reference to the contentsRect.
public var contentsRect: CGRect? public var contentsRect: CGRect?
/// A reference to the contentsScale. /// A reference to the contentsScale.
public var contentsScale: CGFloat? public var contentsScale: CGFloat?
/// A reference to the borderWidth. /// A reference to the borderWidth.
public var borderWidth: CGFloat? public var borderWidth: CGFloat?
/// A reference to the borderColor. /// A reference to the borderColor.
public var borderColor: CGColor? public var borderColor: CGColor?
/// A reference to the shadowColor. /// A reference to the shadowColor.
public var shadowColor: CGColor? public var shadowColor: CGColor?
/// A reference to the shadowOpacity. /// A reference to the shadowOpacity.
public var shadowOpacity: Float? public var shadowOpacity: Float?
/// A reference to the shadowOffset. /// A reference to the shadowOffset.
public var shadowOffset: CGSize? public var shadowOffset: CGSize?
/// A reference to the shadowRadius. /// A reference to the shadowRadius.
public var shadowRadius: CGFloat? public var shadowRadius: CGFloat?
/// A reference to the shadowPath. /// A reference to the shadowPath.
public var shadowPath: CGPath? public var shadowPath: CGPath?
/// A boolean for the masksToBounds state. /// A boolean for the masksToBounds state.
public var masksToBounds: Bool? public var masksToBounds: Bool?
/// A boolean indicating whether to display a shadow or not. /// A boolean indicating whether to display a shadow or not.
public var displayShadow = true public var displayShadow = true
/// A reference to the overlay settings. /// A reference to the overlay settings.
public var overlay: (color: CGColor, opacity: CGFloat)? public var overlay: (color: CGColor, opacity: CGFloat)?
/// A reference to the spring animation settings. /// A reference to the spring animation settings.
public var spring: (CGFloat, CGFloat)? public var spring: (CGFloat, CGFloat)?
/// A time delay on starting the animation. /// A time delay on starting the animation.
public var delay: TimeInterval = 0 public var delay: TimeInterval = 0
/// The duration of the animation. /// The duration of the animation.
public var duration: TimeInterval? public var duration: TimeInterval?
/// The timing function value of the animation. /// The timing function value of the animation.
public var timingFunction: CAMediaTimingFunction? public var timingFunction: CAMediaTimingFunction?
/// The arc curve value. /// The arc curve value.
public var arc: CGFloat? public var arc: CGFloat?
/// The cascading animation settings. /// The cascading animation settings.
public var cascade: (TimeInterval, CascadeDirection, Bool)? public var cascade: (TimeInterval, CascadeDirection, Bool)?
/** /**
A boolean indicating whether to ignore the subview transition A boolean indicating whether to ignore the subview transition
animations or not. animations or not.
*/ */
public var ignoreSubviewTransitions: Bool? public var ignoreSubviewTransitions: Bool?
/// The coordinate space to transition views within. /// The coordinate space to transition views within.
public var coordinateSpace: MotionCoordinateSpace? public var coordinateSpace: MotionCoordinateSpace?
/// Change the size of a view based on a scale factor. /// Change the size of a view based on a scale factor.
public var useScaleBasedSizeChange: Bool? public var useScaleBasedSizeChange: Bool?
/// The type of snapshot to use. /// The type of snapshot to use.
public var snapshotType: MotionSnapshotType? public var snapshotType: MotionSnapshotType?
/// Do not fade the view when transitioning. /// Do not fade the view when transitioning.
public var nonFade = false public var nonFade = false
/// Force an animation. /// Force an animation.
public var forceAnimate = false public var forceAnimate = false
/// Custom target states. /// Custom target states.
public var custom: [String: Any]? public var custom: [String: Any]?
/** /**
An initializer that accepts an Array of MotionTargetStates. An initializer that accepts an Array of MotionTargetStates.
- Parameter transitions: An Array of MotionTargetStates. - Parameter transitions: An Array of MotionTargetStates.
*/ */
init(modifiers: [MotionModifier]) { init(modifiers: [MotionModifier]) {
append(contentsOf: modifiers) append(contentsOf: modifiers)
} }
} }
extension MotionTargetState { extension MotionTargetState {
/** /**
Adds a MotionTargetState to the current state. Adds a MotionTargetState to the current state.
- Parameter _ modifier: A MotionTargetState. - Parameter _ modifier: A MotionTargetState.
*/ */
public mutating func append(_ modifier: MotionModifier) { public mutating func append(_ modifier: MotionModifier) {
modifier.apply(&self) modifier.apply(&self)
}
/**
Adds an Array of MotionTargetStates to the current state.
- Parameter contentsOf modifiers: An Array of MotionTargetStates.
*/
public mutating func append(contentsOf modifiers: [MotionModifier]) {
for v in modifiers {
v.apply(&self)
} }
}
/**
Adds an Array of MotionTargetStates to the current state. /**
- Parameter contentsOf modifiers: An Array of MotionTargetStates. A subscript that returns a custom value for a specified key.
*/ - Parameter key: A String.
public mutating func append(contentsOf modifiers: [MotionModifier]) { - Returns: An optional Any value.
for v in modifiers { */
v.apply(&self) public subscript(key: String) -> Any? {
} get {
return custom?[key]
} }
set(value) {
/** if nil == custom {
A subscript that returns a custom value for a specified key. custom = [:]
- Parameter key: A String. }
- Returns: An optional Any value.
*/ custom![key] = value
public subscript(key: String) -> Any? {
get {
return custom?[key]
}
set(value) {
if nil == custom {
custom = [:]
}
custom![key] = value
}
} }
}
} }
extension MotionTargetState: ExpressibleByArrayLiteral { extension MotionTargetState: ExpressibleByArrayLiteral {
/** /**
An initializer implementing the ExpressibleByArrayLiteral protocol. An initializer implementing the ExpressibleByArrayLiteral protocol.
- Parameter arrayLiteral elements: A list of MotionTargetStates. - Parameter arrayLiteral elements: A list of MotionTargetStates.
*/ */
public init(arrayLiteral elements: MotionModifier...) { public init(arrayLiteral elements: MotionModifier...) {
append(contentsOf: elements) append(contentsOf: elements)
} }
} }
...@@ -29,10 +29,10 @@ ...@@ -29,10 +29,10 @@
import Foundation import Foundation
public protocol MotionTargetStateObserver { public protocol MotionTargetStateObserver {
/** /**
Executed when the elapsed time changes during a transition. Executed when the elapsed time changes during a transition.
- Parameter transitionObserver: A MotionTargetStateObserver. - Parameter transitionObserver: A MotionTargetStateObserver.
- Parameter didUpdateWith progress: A TimeInterval. - Parameter didUpdateWith progress: A TimeInterval.
*/ */
func motion(transitionObserver: MotionTargetStateObserver, didUpdateWith progress: TimeInterval) func motion(transitionObserver: MotionTargetStateObserver, didUpdateWith progress: TimeInterval)
} }
...@@ -28,8 +28,8 @@ ...@@ -28,8 +28,8 @@
@objc(MotionViewOrderStrategy) @objc(MotionViewOrderStrategy)
public enum MotionViewOrderStrategy: Int { public enum MotionViewOrderStrategy: Int {
case auto case auto
case sourceViewOnTop case sourceViewOnTop
case destinationViewOnTop case destinationViewOnTop
} }
...@@ -29,100 +29,100 @@ ...@@ -29,100 +29,100 @@
import UIKit import UIKit
public enum CascadeDirection { public enum CascadeDirection {
case topToBottom case topToBottom
case bottomToTop case bottomToTop
case leftToRight case leftToRight
case rightToLeft case rightToLeft
case radial(center: CGPoint) case radial(center: CGPoint)
case inverseRadial(center: CGPoint) case inverseRadial(center: CGPoint)
/// Based on the cascade direction a comparator is set. /// Based on the cascade direction a comparator is set.
var comparator: (UIView, UIView) -> Bool { var comparator: (UIView, UIView) -> Bool {
switch self { switch self {
case .topToBottom: case .topToBottom:
return { return {
return $0.frame.minY < $1.frame.minY return $0.frame.minY < $1.frame.minY
} }
case .bottomToTop: case .bottomToTop:
return { return {
return $0.frame.maxY == $1.frame.maxY ? $0.frame.maxX > $1.frame.maxX : $0.frame.maxY > $1.frame.maxY return $0.frame.maxY == $1.frame.maxY ? $0.frame.maxX > $1.frame.maxX : $0.frame.maxY > $1.frame.maxY
} }
case .leftToRight: case .leftToRight:
return { return {
return $0.frame.minX < $1.frame.minX return $0.frame.minX < $1.frame.minX
} }
case .rightToLeft: case .rightToLeft:
return { return {
return $0.frame.maxX > $1.frame.maxX return $0.frame.maxX > $1.frame.maxX
} }
case .radial(let center): case .radial(let center):
return { return {
return $0.center.distance(center) < $1.center.distance(center) return $0.center.distance(center) < $1.center.distance(center)
} }
case .inverseRadial(let center): case .inverseRadial(let center):
return { return {
return $0.center.distance(center) > $1.center.distance(center) return $0.center.distance(center) > $1.center.distance(center)
} }
}
} }
}
} }
class CascadePreprocessor: MotionCorePreprocessor { class CascadePreprocessor: MotionCorePreprocessor {
/** /**
Processes the transitionary views. Processes the transitionary views.
- Parameter fromViews: An Array of UIViews. - Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews. - Parameter toViews: An Array of UIViews.
*/ */
override func process(fromViews: [UIView], toViews: [UIView]) { override func process(fromViews: [UIView], toViews: [UIView]) {
process(views: fromViews) process(views: fromViews)
process(views: toViews) process(views: toViews)
} }
/** /**
Process an Array of views for the cascade animation. Process an Array of views for the cascade animation.
- Parameter views: An Array of UIViews. - Parameter views: An Array of UIViews.
*/ */
func process(views: [UIView]) { func process(views: [UIView]) {
for v in views { for v in views {
guard let (deltaTime, direction, delayMatchedViews) = context[v]?.cascade else { guard let (deltaTime, direction, delayMatchedViews) = context[v]?.cascade else {
continue continue
} }
var parentView = v var parentView = v
if v is UITableView, let wrapperView = v.subviews.get(0) { if v is UITableView, let wrapperView = v.subviews.get(0) {
parentView = wrapperView parentView = wrapperView
} }
let sortedSubviews = parentView.subviews.sorted(by: direction.comparator) let sortedSubviews = parentView.subviews.sorted(by: direction.comparator)
let initialDelay = context[v]!.delay let initialDelay = context[v]!.delay
let finalDelay = TimeInterval(sortedSubviews.count) * deltaTime + initialDelay let finalDelay = TimeInterval(sortedSubviews.count) * deltaTime + initialDelay
for (i, subview) in sortedSubviews.enumerated() {
let delay = TimeInterval(i) * deltaTime + initialDelay
func applyDelay(view: UIView) {
if nil == context.pairedView(for: view) {
context[view]?.delay = delay
for (i, subview) in sortedSubviews.enumerated() { } else if delayMatchedViews, let paired = context.pairedView(for: view) {
let delay = TimeInterval(i) * deltaTime + initialDelay context[view]?.delay = finalDelay
context[paired]?.delay = finalDelay
func applyDelay(view: UIView) { }
if nil == context.pairedView(for: view) {
context[view]?.delay = delay for subview in view.subviews {
applyDelay(view: subview)
} else if delayMatchedViews, let paired = context.pairedView(for: view) { }
context[view]?.delay = finalDelay
context[paired]?.delay = finalDelay
}
for subview in view.subviews {
applyDelay(view: subview)
}
}
applyDelay(view: subview)
}
} }
applyDelay(view: subview)
}
} }
}
} }
...@@ -29,84 +29,84 @@ ...@@ -29,84 +29,84 @@
import UIKit import UIKit
public struct MotionConditionalContext { public struct MotionConditionalContext {
internal weak var motion: MotionTransition! internal weak var motion: MotionTransition!
public weak var view: UIView! public weak var view: UIView!
public private(set) var isAppearing: Bool
public var isPresenting: Bool {
return motion.isPresenting
}
public var isTabBarController: Bool {
return motion.isTabBarController
}
public var isNavigationController: Bool {
return motion.isNavigationController
}
public var isMatched: Bool {
return nil != matchedView
}
public var isAncestorViewMatched: Bool {
return nil != matchedAncestorView
}
public var matchedView: UIView? {
return motion.context.pairedView(for: view)
}
public var matchedAncestorView: (UIView, UIView)? {
var current = view.superview
public private(set) var isAppearing: Bool while let ancestor = current, ancestor != motion.context.container {
if let pairedView = motion.context.pairedView(for: ancestor) {
public var isPresenting: Bool { return (ancestor, pairedView)
return motion.isPresenting }
}
current = ancestor.superview
public var isTabBarController: Bool {
return motion.isTabBarController
}
public var isNavigationController: Bool {
return motion.isNavigationController
}
public var isMatched: Bool {
return nil != matchedView
}
public var isAncestorViewMatched: Bool {
return nil != matchedAncestorView
} }
public var matchedView: UIView? { return nil
return motion.context.pairedView(for: view) }
}
public var fromViewController: UIViewController {
public var matchedAncestorView: (UIView, UIView)? { return motion.fromViewController!
var current = view.superview }
while let ancestor = current, ancestor != motion.context.container { public var toViewController: UIViewController {
if let pairedView = motion.context.pairedView(for: ancestor) { return motion.toViewController!
return (ancestor, pairedView) }
}
public var currentViewController: UIViewController {
current = ancestor.superview return isAppearing ? toViewController : fromViewController
} }
return nil public var otherViewController: UIViewController {
} return isAppearing ? fromViewController : toViewController
}
public var fromViewController: UIViewController {
return motion.fromViewController!
}
public var toViewController: UIViewController {
return motion.toViewController!
}
public var currentViewController: UIViewController {
return isAppearing ? toViewController : fromViewController
}
public var otherViewController: UIViewController {
return isAppearing ? fromViewController : toViewController
}
} }
class ConditionalPreprocessor: MotionCorePreprocessor { class ConditionalPreprocessor: MotionCorePreprocessor {
override func process(fromViews: [UIView], toViews: [UIView]) { override func process(fromViews: [UIView], toViews: [UIView]) {
process(views: fromViews, isAppearing: false) process(views: fromViews, isAppearing: false)
process(views: toViews, isAppearing: true) process(views: toViews, isAppearing: true)
} }
func process(views: [UIView], isAppearing: Bool) { func process(views: [UIView], isAppearing: Bool) {
for v in views { for v in views {
guard let conditionalModifiers = context[v]?.conditionalModifiers else { guard let conditionalModifiers = context[v]?.conditionalModifiers else {
continue continue
} }
for (condition, modifiers) in conditionalModifiers { for (condition, modifiers) in conditionalModifiers {
if condition(MotionConditionalContext(motion: motion, view: v, isAppearing: isAppearing)) { if condition(MotionConditionalContext(motion: motion, view: v, isAppearing: isAppearing)) {
context[v]!.append(contentsOf: modifiers) context[v]!.append(contentsOf: modifiers)
}
}
} }
}
} }
}
} }
...@@ -29,54 +29,54 @@ ...@@ -29,54 +29,54 @@
import UIKit import UIKit
class IgnoreSubviewTransitionsPreprocessor: MotionCorePreprocessor { class IgnoreSubviewTransitionsPreprocessor: MotionCorePreprocessor {
/** /**
Processes the transitionary views. Processes the transitionary views.
- Parameter fromViews: An Array of UIViews. - Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews. - Parameter toViews: An Array of UIViews.
*/ */
override func process(fromViews: [UIView], toViews: [UIView]) { override func process(fromViews: [UIView], toViews: [UIView]) {
process(views: fromViews) process(views: fromViews)
process(views: toViews) process(views: toViews)
} }
/** /**
Process an Array of views for the cascade animation. Process an Array of views for the cascade animation.
- Parameter views: An Array of UIViews. - Parameter views: An Array of UIViews.
*/ */
func process(views: [UIView]) { func process(views: [UIView]) {
for v in views { for v in views {
guard let recursive = context[v]?.ignoreSubviewTransitions else { guard let recursive = context[v]?.ignoreSubviewTransitions else {
continue continue
} }
var parentView = v var parentView = v
if v is UITableView, let wrapperView = v.subviews.get(0) { if v is UITableView, let wrapperView = v.subviews.get(0) {
parentView = wrapperView parentView = wrapperView
} }
guard recursive else { guard recursive else {
for subview in parentView.subviews { for subview in parentView.subviews {
context[subview] = nil context[subview] = nil
}
continue
}
cleanSubviewModifiers(for: parentView)
} }
continue
}
cleanSubviewModifiers(for: parentView)
} }
}
} }
fileprivate extension IgnoreSubviewTransitionsPreprocessor { fileprivate extension IgnoreSubviewTransitionsPreprocessor {
/** /**
Clears the modifiers for a given view's subviews. Clears the modifiers for a given view's subviews.
- Parameter for view: A UIView. - Parameter for view: A UIView.
*/ */
func cleanSubviewModifiers(for view: UIView) { func cleanSubviewModifiers(for view: UIView) {
for v in view.subviews { for v in view.subviews {
context[v] = nil context[v] = nil
cleanSubviewModifiers(for: v) cleanSubviewModifiers(for: v)
}
} }
}
} }
...@@ -29,66 +29,66 @@ ...@@ -29,66 +29,66 @@
import UIKit import UIKit
class MatchPreprocessor: MotionCorePreprocessor { class MatchPreprocessor: MotionCorePreprocessor {
/** /**
Processes the transitionary views. Processes the transitionary views.
- Parameter fromViews: An Array of UIViews. - Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews. - Parameter toViews: An Array of UIViews.
*/ */
override func process(fromViews: [UIView], toViews: [UIView]) { override func process(fromViews: [UIView], toViews: [UIView]) {
for tv in toViews { for tv in toViews {
guard let motionIdentifier = tv.motionIdentifier, let fv = context.sourceView(for: motionIdentifier) else { guard let motionIdentifier = tv.motionIdentifier, let fv = context.sourceView(for: motionIdentifier) else {
continue continue
} }
var tvState = context[tv] ?? MotionTargetState() var tvState = context[tv] ?? MotionTargetState()
var fvState = context[fv] ?? MotionTargetState() var fvState = context[fv] ?? MotionTargetState()
// match is just a two-way source effect // match is just a two-way source effect
tvState.motionIdentifier = motionIdentifier tvState.motionIdentifier = motionIdentifier
fvState.motionIdentifier = motionIdentifier fvState.motionIdentifier = motionIdentifier
fvState.arc = tvState.arc fvState.arc = tvState.arc
fvState.duration = tvState.duration fvState.duration = tvState.duration
fvState.timingFunction = tvState.timingFunction fvState.timingFunction = tvState.timingFunction
fvState.delay = tvState.delay fvState.delay = tvState.delay
fvState.spring = tvState.spring fvState.spring = tvState.spring
let forceNonFade = tvState.nonFade || fvState.nonFade let forceNonFade = tvState.nonFade || fvState.nonFade
let isNonOpaque = !fv.isOpaque || fv.alpha < 1 || !tv.isOpaque || tv.alpha < 1 let isNonOpaque = !fv.isOpaque || fv.alpha < 1 || !tv.isOpaque || tv.alpha < 1
if context.insertToViewFirst { if context.insertToViewFirst {
fvState.opacity = 0 fvState.opacity = 0
if !forceNonFade && isNonOpaque { if !forceNonFade && isNonOpaque {
tvState.opacity = 0 tvState.opacity = 0
} else { } else {
tvState.opacity = nil tvState.opacity = nil
if !tv.layer.masksToBounds && tvState.displayShadow { if !tv.layer.masksToBounds && tvState.displayShadow {
fvState.displayShadow = false fvState.displayShadow = false
} }
}
} else {
tvState.opacity = 0
if !forceNonFade && isNonOpaque {
// cross fade if from/toViews are not opaque
fvState.opacity = 0
} else {
// no cross fade in this case, fromView is always displayed during the transition.
fvState.opacity = nil
// we dont want two shadows showing up. Therefore we disable toView's shadow when fromView is able to display its shadow
if !fv.layer.masksToBounds && fvState.displayShadow {
tvState.displayShadow = false
}
}
}
context[tv] = tvState
context[fv] = fvState
} }
} else {
tvState.opacity = 0
if !forceNonFade && isNonOpaque {
// cross fade if from/toViews are not opaque
fvState.opacity = 0
} else {
// no cross fade in this case, fromView is always displayed during the transition.
fvState.opacity = nil
// we dont want two shadows showing up. Therefore we disable toView's shadow when fromView is able to display its shadow
if !fv.layer.masksToBounds && fvState.displayShadow {
tvState.displayShadow = false
}
}
}
context[tv] = tvState
context[fv] = fvState
} }
}
} }
...@@ -29,13 +29,13 @@ ...@@ -29,13 +29,13 @@
import UIKit import UIKit
class MotionCorePreprocessor: MotionPreprocessor { class MotionCorePreprocessor: MotionPreprocessor {
weak public var motion: MotionTransition! weak public var motion: MotionTransition!
/// A reference to the MotionContext. /// A reference to the MotionContext.
public var context: MotionContext! { public var context: MotionContext! {
return motion!.context return motion!.context
} }
func process(fromViews: [UIView], toViews: [UIView]) {} func process(fromViews: [UIView], toViews: [UIView]) {}
} }
...@@ -29,13 +29,13 @@ ...@@ -29,13 +29,13 @@
import UIKit import UIKit
public protocol MotionPreprocessor: class { public protocol MotionPreprocessor: class {
/// A reference to Motion. /// A reference to Motion.
weak var motion: MotionTransition! { get set } weak var motion: MotionTransition! { get set }
/** /**
Processes the transitionary views. Processes the transitionary views.
- Parameter fromViews: An Array of UIViews. - Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews. - Parameter toViews: An Array of UIViews.
*/ */
func process(fromViews: [UIView], toViews: [UIView]) func process(fromViews: [UIView], toViews: [UIView])
} }
...@@ -29,90 +29,90 @@ ...@@ -29,90 +29,90 @@
import UIKit import UIKit
class SourcePreprocessor: MotionCorePreprocessor { class SourcePreprocessor: MotionCorePreprocessor {
/** /**
Processes the transitionary views. Processes the transitionary views.
- Parameter fromViews: An Array of UIViews. - Parameter fromViews: An Array of UIViews.
- Parameter toViews: An Array of UIViews. - Parameter toViews: An Array of UIViews.
*/ */
override func process(fromViews: [UIView], toViews: [UIView]) { override func process(fromViews: [UIView], toViews: [UIView]) {
for fv in fromViews { for fv in fromViews {
guard let motionIdentifier = context[fv]?.motionIdentifier, let tv = context.destinationView(for: motionIdentifier) else { guard let motionIdentifier = context[fv]?.motionIdentifier, let tv = context.destinationView(for: motionIdentifier) else {
continue continue
} }
prepare(view: fv, for: tv) prepare(view: fv, for: tv)
}
for tv in toViews {
guard let motionIdentifier = context[tv]?.motionIdentifier, let fv = context.sourceView(for: motionIdentifier) else {
continue
}
prepare(view: tv, for: fv)
}
} }
for tv in toViews {
guard let motionIdentifier = context[tv]?.motionIdentifier, let fv = context.sourceView(for: motionIdentifier) else {
continue
}
prepare(view: tv, for: fv)
}
}
} }
fileprivate extension SourcePreprocessor { fileprivate extension SourcePreprocessor {
/**
Prepares a given view for a target view.
- Parameter view: A UIView.
- Parameter for targetView: A UIView.
*/
func prepare(view: UIView, for targetView: UIView) {
let targetPos = context.container.convert(targetView.layer.position, from: targetView.superview!)
let targetTransform = context.container.layer.flatTransformTo(layer: targetView.layer)
var state = context[view]!
/** /**
Prepares a given view for a target view. Use global coordinate space since over target position is
- Parameter view: A UIView. converted from the global container
- Parameter for targetView: A UIView.
*/ */
func prepare(view: UIView, for targetView: UIView) { state.coordinateSpace = .global
let targetPos = context.container.convert(targetView.layer.position, from: targetView.superview!)
let targetTransform = context.container.layer.flatTransformTo(layer: targetView.layer) state.position = targetPos
state.transform = targetTransform
var state = context[view]!
// Remove incompatible options.
/** state.size = nil
Use global coordinate space since over target position is
converted from the global container if view.bounds.size != targetView.bounds.size {
*/ state.size = targetView.bounds.size
state.coordinateSpace = .global }
state.position = targetPos if view.layer.cornerRadius != targetView.layer.cornerRadius {
state.transform = targetTransform state.cornerRadius = targetView.layer.cornerRadius
}
// Remove incompatible options.
state.size = nil if view.layer.shadowColor != targetView.layer.shadowColor {
state.shadowColor = targetView.layer.shadowColor
if view.bounds.size != targetView.bounds.size { }
state.size = targetView.bounds.size
} if view.layer.shadowOpacity != targetView.layer.shadowOpacity {
state.shadowOpacity = targetView.layer.shadowOpacity
if view.layer.cornerRadius != targetView.layer.cornerRadius { }
state.cornerRadius = targetView.layer.cornerRadius
} if view.layer.shadowOffset != targetView.layer.shadowOffset {
state.shadowOffset = targetView.layer.shadowOffset
if view.layer.shadowColor != targetView.layer.shadowColor { }
state.shadowColor = targetView.layer.shadowColor
} if view.layer.shadowRadius != targetView.layer.shadowRadius {
state.shadowRadius = targetView.layer.shadowRadius
if view.layer.shadowOpacity != targetView.layer.shadowOpacity { }
state.shadowOpacity = targetView.layer.shadowOpacity
} if view.layer.shadowPath != targetView.layer.shadowPath {
state.shadowPath = targetView.layer.shadowPath
if view.layer.shadowOffset != targetView.layer.shadowOffset { }
state.shadowOffset = targetView.layer.shadowOffset
} if view.layer.contentsRect != targetView.layer.contentsRect {
state.contentsRect = targetView.layer.contentsRect
if view.layer.shadowRadius != targetView.layer.shadowRadius { }
state.shadowRadius = targetView.layer.shadowRadius
} if view.layer.contentsScale != targetView.layer.contentsScale {
state.contentsScale = targetView.layer.contentsScale
if view.layer.shadowPath != targetView.layer.shadowPath {
state.shadowPath = targetView.layer.shadowPath
}
if view.layer.contentsRect != targetView.layer.contentsRect {
state.contentsRect = targetView.layer.contentsRect
}
if view.layer.contentsScale != targetView.layer.contentsScale {
state.contentsScale = targetView.layer.contentsScale
}
context[view] = state
} }
context[view] = state
}
} }
...@@ -29,55 +29,55 @@ ...@@ -29,55 +29,55 @@
import UIKit import UIKit
protocol MotionProgressRunnerDelegate: class { protocol MotionProgressRunnerDelegate: class {
func update(progress: TimeInterval) func update(progress: TimeInterval)
func complete(isFinishing: Bool) func complete(isFinishing: Bool)
} }
class MotionProgressRunner { class MotionProgressRunner {
weak var delegate: MotionProgressRunnerDelegate? weak var delegate: MotionProgressRunnerDelegate?
var isRunning: Bool {
return nil != displayLink
}
internal var progress: TimeInterval = 0
internal var duration: TimeInterval = 0
internal var displayLink: CADisplayLink?
internal var isReversed: Bool = false
@objc
func displayUpdate(_ link: CADisplayLink) {
progress += isReversed ? -link.duration : link.duration
var isRunning: Bool { if isReversed, progress <= 1.0 / 120 {
return nil != displayLink delegate?.complete(isFinishing: false)
stop()
return
} }
internal var progress: TimeInterval = 0 if !isReversed, progress > duration - 1.0 / 120 {
internal var duration: TimeInterval = 0 delegate?.complete(isFinishing: true)
internal var displayLink: CADisplayLink? stop()
internal var isReversed: Bool = false return
@objc
func displayUpdate(_ link: CADisplayLink) {
progress += isReversed ? -link.duration : link.duration
if isReversed, progress <= 1.0 / 120 {
delegate?.complete(isFinishing: false)
stop()
return
}
if !isReversed, progress > duration - 1.0 / 120 {
delegate?.complete(isFinishing: true)
stop()
return
}
delegate?.update(progress: progress / duration)
} }
func start(progress: TimeInterval, duration: TimeInterval, isReversed: Bool) { delegate?.update(progress: progress / duration)
stop() }
self.progress = progress func start(progress: TimeInterval, duration: TimeInterval, isReversed: Bool) {
self.isReversed = isReversed stop()
self.duration = duration
displayLink = CADisplayLink(target: self, selector: #selector(displayUpdate(_:)))
displayLink!.add(to: RunLoop.main, forMode: RunLoopMode(rawValue: RunLoopMode.commonModes.rawValue))
}
func stop() { self.progress = progress
displayLink?.isPaused = true self.isReversed = isReversed
displayLink?.remove(from: RunLoop.main, forMode: RunLoopMode(rawValue: RunLoopMode.commonModes.rawValue)) self.duration = duration
displayLink = nil
} displayLink = CADisplayLink(target: self, selector: #selector(displayUpdate(_:)))
displayLink!.add(to: RunLoop.main, forMode: RunLoopMode(rawValue: RunLoopMode.commonModes.rawValue))
}
func stop() {
displayLink?.isPaused = true
displayLink?.remove(from: RunLoop.main, forMode: RunLoopMode(rawValue: RunLoopMode.commonModes.rawValue))
displayLink = nil
}
} }
...@@ -29,79 +29,79 @@ ...@@ -29,79 +29,79 @@
import UIKit import UIKit
extension MotionTransition { extension MotionTransition {
/// Starts the transition animation. /// Starts the transition animation.
func animate() { func animate() {
guard .starting == state else { guard .starting == state else {
return return
}
state = .animating
if let tv = toView {
context.unhide(view: tv)
}
for v in animatingFromViews {
context.hide(view: v)
}
for v in animatingToViews {
context.hide(view: v)
}
var t: TimeInterval = 0
var animatorWantsInteractive = false
if context.insertToViewFirst {
for v in animatingToViews {
context.snapshotView(for: v)
}
for v in animatingFromViews {
context.snapshotView(for: v)
}
} else {
for v in animatingFromViews {
context.snapshotView(for: v)
}
for v in animatingToViews {
context.snapshotView(for: v)
}
}
// UIKit appears to set fromView setNeedLayout to be true.
// We don't want fromView to layout after our animation starts.
// Therefore we kick off the layout beforehand
fromView?.layoutIfNeeded()
for animator in animators {
let duration = animator.animate(fromViews: animatingFromViews.filter {
return animator.canAnimate(view: $0, isAppearing: false)
}, toViews: animatingToViews.filter {
return animator.canAnimate(view: $0, isAppearing: true)
})
if .infinity == duration {
animatorWantsInteractive = true
} else {
t = max(t, duration)
}
}
totalDuration = t
if let forceFinishing = forceFinishing {
complete(isFinishing: forceFinishing)
} else if let startingProgress = startingProgress {
update(startingProgress)
} else if animatorWantsInteractive {
update(0)
} else {
complete(after: totalDuration, isFinishing: true)
}
fullScreenSnapshot?.removeFromSuperview()
} }
state = .animating
if let tv = toView {
context.unhide(view: tv)
}
for v in animatingFromViews {
context.hide(view: v)
}
for v in animatingToViews {
context.hide(view: v)
}
var t: TimeInterval = 0
var animatorWantsInteractive = false
if context.insertToViewFirst {
for v in animatingToViews {
context.snapshotView(for: v)
}
for v in animatingFromViews {
context.snapshotView(for: v)
}
} else {
for v in animatingFromViews {
context.snapshotView(for: v)
}
for v in animatingToViews {
context.snapshotView(for: v)
}
}
// UIKit appears to set fromView setNeedLayout to be true.
// We don't want fromView to layout after our animation starts.
// Therefore we kick off the layout beforehand
fromView?.layoutIfNeeded()
for animator in animators {
let duration = animator.animate(fromViews: animatingFromViews.filter {
return animator.canAnimate(view: $0, isAppearing: false)
}, toViews: animatingToViews.filter {
return animator.canAnimate(view: $0, isAppearing: true)
})
if .infinity == duration {
animatorWantsInteractive = true
} else {
t = max(t, duration)
}
}
totalDuration = t
if let forceFinishing = forceFinishing {
complete(isFinishing: forceFinishing)
} else if let startingProgress = startingProgress {
update(startingProgress)
} else if animatorWantsInteractive {
update(0)
} else {
complete(after: totalDuration, isFinishing: true)
}
fullScreenSnapshot?.removeFromSuperview()
}
} }
...@@ -29,119 +29,119 @@ ...@@ -29,119 +29,119 @@
import UIKit import UIKit
extension MotionTransition { extension MotionTransition {
/** /**
Complete the transition. Complete the transition.
- Parameter isFinishing: A Boolean indicating if the transition - Parameter isFinishing: A Boolean indicating if the transition
has completed. has completed.
*/ */
@objc @objc
func complete(isFinishing: Bool) { func complete(isFinishing: Bool) {
if state == .notified { if state == .notified {
forceFinishing = isFinishing forceFinishing = isFinishing
} }
guard .animating == state || .starting == state else { guard .animating == state || .starting == state else {
return return
} }
defer { defer {
transitionContext = nil transitionContext = nil
fromViewController = nil fromViewController = nil
toViewController = nil toViewController = nil
isNavigationController = false isNavigationController = false
isTabBarController = false isTabBarController = false
forceNonInteractive = false forceNonInteractive = false
animatingToViews.removeAll() animatingToViews.removeAll()
animatingFromViews.removeAll() animatingFromViews.removeAll()
transitionObservers = nil transitionObservers = nil
transitionContainer = nil transitionContainer = nil
completionCallback = nil completionCallback = nil
forceFinishing = nil forceFinishing = nil
container = nil container = nil
startingProgress = nil startingProgress = nil
preprocessors.removeAll() preprocessors.removeAll()
animators.removeAll() animators.removeAll()
plugins.removeAll() plugins.removeAll()
context = nil context = nil
progress = 0 progress = 0
totalDuration = 0 totalDuration = 0
state = .possible state = .possible
} }
state = .completing state = .completing
progressRunner.stop() progressRunner.stop()
context.clean() context.clean()
if let tv = toView, let fv = fromView { if let tv = toView, let fv = fromView {
if isFinishing && isPresenting && toOverFullScreen { if isFinishing && isPresenting && toOverFullScreen {
// finished presenting a overFullScreen view controller. // finished presenting a overFullScreen view controller.
context.unhide(rootView: tv) context.unhide(rootView: tv)
context.removeSnapshots(rootView: tv) context.removeSnapshots(rootView: tv)
context.storeViewAlpha(rootView: fv) context.storeViewAlpha(rootView: fv)
fromViewController!.motionStoredSnapshot = container
fv.removeFromSuperview()
fv.addSubview(container)
} else if !isFinishing && !isPresenting && fromOverFullScreen {
// Cancelled dismissing a overFullScreen view controller.
context.unhide(rootView: fv)
context.removeSnapshots(rootView: fv)
context.storeViewAlpha(rootView: tv)
toViewController!.motionStoredSnapshot = container
container.superview?.addSubview(tv)
tv.addSubview(container)
} else {
context.unhideAll()
context.removeAllSnapshots()
}
// Move fromView & toView back from our container back to the one supplied by UIKit.
if (toOverFullScreen && isFinishing) || (fromOverFullScreen && !isFinishing) {
transitionContainer?.addSubview(isFinishing ? fv : tv)
}
transitionContainer?.addSubview(isFinishing ? tv : fv)
if isPresenting != isFinishing, !isContainerController {
// Only happens when present a .overFullScreen view controller.
// bug: http://openradar.appspot.com/radar?id=5320103646199808
UIApplication.shared.keyWindow?.addSubview(isPresenting ? fv : tv)
}
}
if container.superview == transitionContainer {
container.removeFromSuperview()
}
for a in animators {
a.clean()
}
transitionContainer?.isUserInteractionEnabled = true
completionCallback?(isFinishing)
if isFinishing { fromViewController!.motionStoredSnapshot = container
toViewController?.tabBarController?.tabBar.layer.removeAllAnimations() fv.removeFromSuperview()
} else { fv.addSubview(container)
fromViewController?.tabBarController?.tabBar.layer.removeAllAnimations()
}
let tContext = transitionContext } else if !isFinishing && !isPresenting && fromOverFullScreen {
let fvc = fromViewController // Cancelled dismissing a overFullScreen view controller.
let tvc = toViewController context.unhide(rootView: fv)
context.removeSnapshots(rootView: fv)
context.storeViewAlpha(rootView: tv)
if isFinishing { toViewController!.motionStoredSnapshot = container
processEndTransitionDelegation(transitionContext: tContext, fromViewController: fvc, toViewController: tvc) container.superview?.addSubview(tv)
} else { tv.addSubview(container)
processCancelTransitionDelegation(transitionContext: tContext, fromViewController: fvc, toViewController: tvc)
tContext?.cancelInteractiveTransition()
}
tContext?.completeTransition(isFinishing) } else {
context.unhideAll()
context.removeAllSnapshots()
}
// Move fromView & toView back from our container back to the one supplied by UIKit.
if (toOverFullScreen && isFinishing) || (fromOverFullScreen && !isFinishing) {
transitionContainer?.addSubview(isFinishing ? fv : tv)
}
transitionContainer?.addSubview(isFinishing ? tv : fv)
if isPresenting != isFinishing, !isContainerController {
// Only happens when present a .overFullScreen view controller.
// bug: http://openradar.appspot.com/radar?id=5320103646199808
UIApplication.shared.keyWindow?.addSubview(isPresenting ? fv : tv)
}
}
if container.superview == transitionContainer {
container.removeFromSuperview()
}
for a in animators {
a.clean()
}
transitionContainer?.isUserInteractionEnabled = true
completionCallback?(isFinishing)
if isFinishing {
toViewController?.tabBarController?.tabBar.layer.removeAllAnimations()
} else {
fromViewController?.tabBarController?.tabBar.layer.removeAllAnimations()
}
let tContext = transitionContext
let fvc = fromViewController
let tvc = toViewController
if isFinishing {
processEndTransitionDelegation(transitionContext: tContext, fromViewController: fvc, toViewController: tvc)
} else {
processCancelTransitionDelegation(transitionContext: tContext, fromViewController: fvc, toViewController: tvc)
tContext?.cancelInteractiveTransition()
} }
tContext?.completeTransition(isFinishing)
}
} }
...@@ -29,34 +29,34 @@ ...@@ -29,34 +29,34 @@
import UIKit import UIKit
extension MotionTransition { extension MotionTransition {
/** /**
A helper transition function. A helper transition function.
- Parameter from: A UIViewController. - Parameter from: A UIViewController.
- Parameter to: A UIViewController. - Parameter to: A UIViewController.
- Parameter in view: A UIView. - Parameter in view: A UIView.
- Parameter completion: An optional completion handler. - Parameter completion: An optional completion handler.
*/ */
public func transition(from: UIViewController, to: UIViewController, in view: UIView, completion: ((Bool) -> Void)? = nil) { public func transition(from: UIViewController, to: UIViewController, in view: UIView, completion: ((Bool) -> Void)? = nil) {
guard !isTransitioning else { guard !isTransitioning else {
return return
}
state = .notified
isPresenting = true
transitionContainer = view
fromViewController = from
toViewController = to
completionCallback = { [weak self] in
guard let `self` = self else {
return
}
completion?($0)
self.state = .possible
}
start()
} }
state = .notified
isPresenting = true
transitionContainer = view
fromViewController = from
toViewController = to
completionCallback = { [weak self] in
guard let `self` = self else {
return
}
completion?($0)
self.state = .possible
}
start()
}
} }
...@@ -29,100 +29,100 @@ ...@@ -29,100 +29,100 @@
import UIKit import UIKit
extension MotionTransition { extension MotionTransition {
/** /**
Updates the elapsed time for the interactive transition. Updates the elapsed time for the interactive transition.
- Parameter progress t: the current progress, must be between -1...1. - Parameter progress t: the current progress, must be between -1...1.
*/ */
func update(_ percentageComplete: TimeInterval) { func update(_ percentageComplete: TimeInterval) {
guard .animating == state else { guard .animating == state else {
startingProgress = percentageComplete startingProgress = percentageComplete
return return
}
progressRunner.stop()
progress = Double(CGFloat(percentageComplete).clamp(0, 1))
} }
/** progressRunner.stop()
Finish the interactive transition. progress = Double(CGFloat(percentageComplete).clamp(0, 1))
Will stop the interactive transition and animate from the }
current state to the **end** state.
- Parameter isAnimated: A Boolean. /**
*/ Finish the interactive transition.
func finish(isAnimated: Bool = true) { Will stop the interactive transition and animate from the
guard .animating == state || .notified == state || .starting == state else { current state to the **end** state.
return - Parameter isAnimated: A Boolean.
} */
func finish(isAnimated: Bool = true) {
guard isAnimated else { guard .animating == state || .notified == state || .starting == state else {
complete(isFinishing: true) return
return
}
var d: TimeInterval = 0
for a in animators {
d = max(d, a.resume(at: progress * totalDuration, isReversed: false))
}
complete(after: d, isFinishing: true)
} }
/** guard isAnimated else {
Cancel the interactive transition. complete(isFinishing: true)
Will stop the interactive transition and animate from the return
current state to the **begining** state
- Parameter isAnimated: A boolean indicating if the completion is animated.
*/
func cancel(isAnimated: Bool = true) {
guard .animating == state || .notified == state || .starting == state else {
return
}
guard isAnimated else {
complete(isFinishing: false)
return
}
var d: TimeInterval = 0
for a in animators {
var t = progress
if t < 0 {
t = -t
}
d = max(d, a.resume(at: t * totalDuration, isReversed: true))
}
complete(after: d, isFinishing: false)
} }
/** var d: TimeInterval = 0
Override transition animations during an interactive animation.
for a in animators {
For example: d = max(d, a.resume(at: progress * totalDuration, isReversed: false))
}
Motion.shared.apply([.position(x:50, y:50)], to: view)
complete(after: d, isFinishing: true)
will set the view's position to 50, 50 }
- Parameter modifiers: An Array of MotionModifier.
- Parameter to view: A UIView. /**
*/ Cancel the interactive transition.
func apply(modifiers: [MotionModifier], to view: UIView) { Will stop the interactive transition and animate from the
guard .animating == state else { current state to the **begining** state
return - Parameter isAnimated: A boolean indicating if the completion is animated.
} */
func cancel(isAnimated: Bool = true) {
let targetState = MotionTargetState(modifiers: modifiers) guard .animating == state || .notified == state || .starting == state else {
if let otherView = context.pairedView(for: view) { return
for animator in animators { }
animator.apply(state: targetState, to: otherView)
} guard isAnimated else {
} complete(isFinishing: false)
return
for animator in self.animators { }
animator.apply(state: targetState, to: view)
} var d: TimeInterval = 0
for a in animators {
var t = progress
if t < 0 {
t = -t
}
d = max(d, a.resume(at: t * totalDuration, isReversed: true))
}
complete(after: d, isFinishing: false)
}
/**
Override transition animations during an interactive animation.
For example:
Motion.shared.apply([.position(x:50, y:50)], to: view)
will set the view's position to 50, 50
- Parameter modifiers: An Array of MotionModifier.
- Parameter to view: A UIView.
*/
func apply(modifiers: [MotionModifier], to view: UIView) {
guard .animating == state else {
return
}
let targetState = MotionTargetState(modifiers: modifiers)
if let otherView = context.pairedView(for: view) {
for animator in animators {
animator.apply(state: targetState, to: otherView)
}
}
for animator in self.animators {
animator.apply(state: targetState, to: view)
} }
}
} }
...@@ -29,21 +29,21 @@ ...@@ -29,21 +29,21 @@
import UIKit import UIKit
extension MotionTransition: UINavigationControllerDelegate { extension MotionTransition: UINavigationControllerDelegate {
public func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { public func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
guard !isTransitioning else { guard !isTransitioning else {
return nil return nil
}
state = .notified
isPresenting = .push == operation
fromViewController = fromViewController ?? fromVC
toViewController = toViewController ?? toVC
isNavigationController = true
return self
} }
public func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { state = .notified
return interactiveTransitioning isPresenting = .push == operation
} fromViewController = fromViewController ?? fromVC
toViewController = toViewController ?? toVC
isNavigationController = true
return self
}
public func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return interactiveTransitioning
}
} }
...@@ -29,34 +29,34 @@ ...@@ -29,34 +29,34 @@
import UIKit import UIKit
extension MotionTransition: UITabBarControllerDelegate { extension MotionTransition: UITabBarControllerDelegate {
public func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool { public func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if isTransitioning { if isTransitioning {
cancel(isAnimated: false) cancel(isAnimated: false)
}
return true
} }
public func tabBarController(_ tabBarController: UITabBarController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { return true
return interactiveTransitioning }
public func tabBarController(_ tabBarController: UITabBarController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return interactiveTransitioning
}
public func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
guard !isTransitioning else {
return nil
} }
public func tabBarController(_ tabBarController: UITabBarController, animationControllerForTransitionFrom fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { state = .notified
guard !isTransitioning else {
return nil let fromVCIndex = tabBarController.childViewControllers.index(of: fromVC)!
} let toVCIndex = tabBarController.childViewControllers.index(of: toVC)!
state = .notified isPresenting = toVCIndex > fromVCIndex
fromViewController = fromViewController ?? fromVC
let fromVCIndex = tabBarController.childViewControllers.index(of: fromVC)! toViewController = toViewController ?? toVC
let toVCIndex = tabBarController.childViewControllers.index(of: toVC)! isTabBarController = true
isPresenting = toVCIndex > fromVCIndex return self
fromViewController = fromViewController ?? fromVC }
toViewController = toViewController ?? toVC
isTabBarController = true
return self
}
} }
...@@ -29,78 +29,78 @@ ...@@ -29,78 +29,78 @@
import UIKit import UIKit
extension MotionTransition: UIViewControllerTransitioningDelegate { extension MotionTransition: UIViewControllerTransitioningDelegate {
/// A reference to the interactive transitioning instance. /// A reference to the interactive transitioning instance.
var interactiveTransitioning: UIViewControllerInteractiveTransitioning? { var interactiveTransitioning: UIViewControllerInteractiveTransitioning? {
return forceNonInteractive ? nil : self return forceNonInteractive ? nil : self
}
public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
guard !isTransitioning else {
return nil
} }
public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { state = .notified
guard !isTransitioning else { isPresenting = true
return nil fromViewController = fromViewController ?? presenting
} toViewController = toViewController ?? presented
state = .notified
isPresenting = true
fromViewController = fromViewController ?? presenting
toViewController = toViewController ?? presented
return self
}
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
guard !isTransitioning else {
return nil
}
state = .notified
isPresenting = false
fromViewController = fromViewController ?? dismissed
return self
}
public func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { return self
return interactiveTransitioning }
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
guard !isTransitioning else {
return nil
} }
public func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { state = .notified
return interactiveTransitioning isPresenting = false
} fromViewController = fromViewController ?? dismissed
return self
}
public func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return interactiveTransitioning
}
public func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
return interactiveTransitioning
}
} }
extension MotionTransition: UIViewControllerAnimatedTransitioning { extension MotionTransition: UIViewControllerAnimatedTransitioning {
/** /**
The animation method that is used to coordinate the transition. The animation method that is used to coordinate the transition.
- Parameter using transitionContext: A UIViewControllerContextTransitioning. - Parameter using transitionContext: A UIViewControllerContextTransitioning.
*/ */
public func animateTransition(using context: UIViewControllerContextTransitioning) { public func animateTransition(using context: UIViewControllerContextTransitioning) {
transitionContext = context transitionContext = context
fromViewController = fromViewController ?? context.viewController(forKey: .from) fromViewController = fromViewController ?? context.viewController(forKey: .from)
toViewController = toViewController ?? context.viewController(forKey: .to) toViewController = toViewController ?? context.viewController(forKey: .to)
transitionContainer = context.containerView transitionContainer = context.containerView
start()
}
/** start()
Returns the transition duration time interval. }
- Parameter using transitionContext: An optional UIViewControllerContextTransitioning.
- Returns: A TimeInterval that is the total animation time including delays. /**
*/ Returns the transition duration time interval.
public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { - Parameter using transitionContext: An optional UIViewControllerContextTransitioning.
return 0 // Time will be updated dynamically. - Returns: A TimeInterval that is the total animation time including delays.
} */
public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
public func animationEnded(_ transitionCompleted: Bool) { return 0 // Time will be updated dynamically.
state = .possible }
}
public func animationEnded(_ transitionCompleted: Bool) {
state = .possible
}
} }
extension MotionTransition: UIViewControllerInteractiveTransitioning { extension MotionTransition: UIViewControllerInteractiveTransitioning {
public var wantsInteractiveStart: Bool { public var wantsInteractiveStart: Bool {
return true return true
} }
public func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) { public func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning) {
animateTransition(using: transitionContext) animateTransition(using: transitionContext)
} }
} }
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