Commit f1dcf964 by Daniel Dahan

updated Motion+CAMediaTimingFunction class extension

parent cab3d97c
......@@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */
961409B81E43D21300E7BA99 /* Motion.h in Headers */ = {isa = PBXBuildFile; fileRef = 96C98DED1E438A5700B22906 /* Motion.h */; settings = {ATTRIBUTES = (Public, ); }; };
963150D21EE50DA6002B0D42 /* Motion+Obj-C.swift in Sources */ = {isa = PBXBuildFile; fileRef = 963150D11EE50DA6002B0D42 /* Motion+Obj-C.swift */; };
96AEB68D1EE4610F009A3BE0 /* MotionAnimatorViewContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6651EE4610F009A3BE0 /* MotionAnimatorViewContext.swift */; };
96AEB68E1EE4610F009A3BE0 /* MotionCoreAnimationViewContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6661EE4610F009A3BE0 /* MotionCoreAnimationViewContext.swift */; };
96AEB68F1EE4610F009A3BE0 /* MotionDefaultAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6671EE4610F009A3BE0 /* MotionDefaultAnimator.swift */; };
......@@ -15,7 +16,7 @@
96AEB6911EE4610F009A3BE0 /* MotionDebugPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB66A1EE4610F009A3BE0 /* MotionDebugPlugin.swift */; };
96AEB6921EE4610F009A3BE0 /* MotionDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB66B1EE4610F009A3BE0 /* MotionDebugView.swift */; };
96AEB6931EE4610F009A3BE0 /* DefaultAnimationPreprocessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB66C1EE4610F009A3BE0 /* DefaultAnimationPreprocessor.swift */; };
96AEB6941EE4610F009A3BE0 /* MotionTransition+Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB66E1EE4610F009A3BE0 /* MotionTransition+Array.swift */; };
96AEB6941EE4610F009A3BE0 /* Motion+Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB66E1EE4610F009A3BE0 /* Motion+Array.swift */; };
96AEB6951EE4610F009A3BE0 /* Motion+CALayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB66F1EE4610F009A3BE0 /* Motion+CALayer.swift */; };
96AEB6961EE4610F009A3BE0 /* Motion+CAMediaTimingFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6701EE4610F009A3BE0 /* Motion+CAMediaTimingFunction.swift */; };
96AEB6971EE4610F009A3BE0 /* Motion+CG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96AEB6711EE4610F009A3BE0 /* Motion+CG.swift */; };
......@@ -46,6 +47,7 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
963150D11EE50DA6002B0D42 /* Motion+Obj-C.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Motion+Obj-C.swift"; sourceTree = "<group>"; };
96AEB6651EE4610F009A3BE0 /* MotionAnimatorViewContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionAnimatorViewContext.swift; sourceTree = "<group>"; };
96AEB6661EE4610F009A3BE0 /* MotionCoreAnimationViewContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionCoreAnimationViewContext.swift; sourceTree = "<group>"; };
96AEB6671EE4610F009A3BE0 /* MotionDefaultAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionDefaultAnimator.swift; sourceTree = "<group>"; };
......@@ -53,7 +55,7 @@
96AEB66A1EE4610F009A3BE0 /* MotionDebugPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionDebugPlugin.swift; sourceTree = "<group>"; };
96AEB66B1EE4610F009A3BE0 /* MotionDebugView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionDebugView.swift; sourceTree = "<group>"; };
96AEB66C1EE4610F009A3BE0 /* DefaultAnimationPreprocessor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultAnimationPreprocessor.swift; sourceTree = "<group>"; };
96AEB66E1EE4610F009A3BE0 /* MotionTransition+Array.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MotionTransition+Array.swift"; sourceTree = "<group>"; };
96AEB66E1EE4610F009A3BE0 /* Motion+Array.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Motion+Array.swift"; sourceTree = "<group>"; };
96AEB66F1EE4610F009A3BE0 /* Motion+CALayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Motion+CALayer.swift"; sourceTree = "<group>"; };
96AEB6701EE4610F009A3BE0 /* Motion+CAMediaTimingFunction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Motion+CAMediaTimingFunction.swift"; sourceTree = "<group>"; };
96AEB6711EE4610F009A3BE0 /* Motion+CG.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Motion+CG.swift"; sourceTree = "<group>"; };
......@@ -121,7 +123,7 @@
96AEB66D1EE4610F009A3BE0 /* Extensions */ = {
isa = PBXGroup;
children = (
96AEB66E1EE4610F009A3BE0 /* MotionTransition+Array.swift */,
96AEB66E1EE4610F009A3BE0 /* Motion+Array.swift */,
96AEB66F1EE4610F009A3BE0 /* Motion+CALayer.swift */,
96AEB6701EE4610F009A3BE0 /* Motion+CAMediaTimingFunction.swift */,
96AEB6711EE4610F009A3BE0 /* Motion+CG.swift */,
......@@ -129,6 +131,7 @@
96AEB6731EE4610F009A3BE0 /* Motion+UIKit.swift */,
96AEB6741EE4610F009A3BE0 /* Motion+UIView.swift */,
96AEB6751EE4610F009A3BE0 /* Motion+UIViewController.swift */,
963150D11EE50DA6002B0D42 /* Motion+Obj-C.swift */,
);
path = Extensions;
sourceTree = "<group>";
......@@ -288,7 +291,7 @@
96AEB6A21EE4610F009A3BE0 /* MotionTransition+MotionStringConvertible.swift in Sources */,
96AEB6A51EE4610F009A3BE0 /* MotionTargetState.swift in Sources */,
96AEB6961EE4610F009A3BE0 /* Motion+CAMediaTimingFunction.swift in Sources */,
96AEB6941EE4610F009A3BE0 /* MotionTransition+Array.swift in Sources */,
96AEB6941EE4610F009A3BE0 /* Motion+Array.swift in Sources */,
96AEB6951EE4610F009A3BE0 /* Motion+CALayer.swift in Sources */,
96AEB6981EE4610F009A3BE0 /* Motion+DispatchQueue.swift in Sources */,
96AEB6A61EE4610F009A3BE0 /* MotionTypes.swift in Sources */,
......@@ -308,6 +311,7 @@
96AEB68D1EE4610F009A3BE0 /* MotionAnimatorViewContext.swift in Sources */,
96AEB6B01EE4610F009A3BE0 /* SourcePreprocessor.swift in Sources */,
96AEB6911EE4610F009A3BE0 /* MotionDebugPlugin.swift in Sources */,
963150D21EE50DA6002B0D42 /* Motion+Obj-C.swift in Sources */,
96AEB6AF1EE4610F009A3BE0 /* MatchPreprocessor.swift in Sources */,
96AEB69E1EE4610F009A3BE0 /* MotionController.swift in Sources */,
96AEB6A11EE4610F009A3BE0 /* MotionTransition.swift in Sources */,
......
......@@ -29,43 +29,82 @@
import UIKit
internal class MotionAnimatorViewContext {
var animator: MotionAnimator?
var snapshot: UIView
var duration: TimeInterval = 0
/// An optional reference to a MotionAnimator.
var animator: MotionAnimator?
/// A reference to the snapshot UIView.
var snapshot: UIView
/// Animation duration time.
var duration: TimeInterval = 0
/// The animation target state.
var targetState: MotionTargetState
var targetState: MotionTargetState
/// The computed current time of the snapshot layer.
var currentTime: TimeInterval {
return snapshot.layer.convertTime(CACurrentMediaTime(), from: nil)
}
/// A container view for the transition.
var container: UIView? {
return animator?.context.container
}
// computed
var currentTime: TimeInterval {
return snapshot.layer.convertTime(CACurrentMediaTime(), from: nil)
}
var container: UIView? {
return animator?.context.container
}
/**
An initializer.
- Parameter animator: A MotionAnimator.
- Parameter snapshot: A UIView.
- Parameter targetState: A MotionTargetState.
*/
required init(animator: MotionAnimator, snapshot: UIView, targetState: MotionTargetState) {
self.animator = animator
self.snapshot = snapshot
self.targetState = targetState
}
class func canAnimate(view: UIView, state: MotionTargetState, appearing: Bool) -> Bool {
return false
}
/// Cleans the context.
func clean() {
animator = nil
}
/**
A class function that determines if a view can be animated
to a given state.
- Parameter view: A UIView.
- Parameter state: A MotionTargetState.
- Parameter isAppearing: A boolean that determines whether the
view is appearing.
*/
class func canAnimate(view: UIView, state: MotionTargetState, isAppearing: Bool) -> Bool {
return false
}
func apply(state: MotionTargetState) {
}
/**
Applies the given state to the target state.
- Parameter state: A MotionTargetState.
*/
func apply(state: MotionTargetState) {}
/**
Resumes the animation with a given elapsed time and
optional reversed boolean.
- Parameter elapsedTime: A TimeInterval.
- Parameter isReversed: A boolean to reverse the animation
or not.
*/
func resume(elapsedTime: TimeInterval, isReversed: Bool) {}
/**
Moves the animation to the given elapsed time.
- Parameter to elapsedTime: A TimeInterval.
*/
func seek(to elapsedTime: TimeInterval) {}
func resume(timePassed: TimeInterval, reverse: Bool) {
}
func seek(timePassed: TimeInterval) {
}
func clean() {
animator = nil
}
func startAnimations(appearing: Bool) {
}
required init(animator: MotionAnimator, snapshot: UIView, targetState: MotionTargetState) {
self.animator = animator
self.snapshot = snapshot
self.targetState = targetState
}
/**
Starts the animations with an appearing boolean flag.
- Parameter isAppearing: A boolean value whether the view
is appearing or not.
*/
func startAnimations(isAppearing: Bool) {}
}
......@@ -29,107 +29,116 @@
import UIKit
internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext {
var state = [String: (Any?, Any?)]()
var timingFunction: CAMediaTimingFunction = .standard
// computed
var contentLayer: CALayer? {
return snapshot.layer.sublayers?.get(0)
}
var overlayLayer: CALayer?
override class func canAnimate(view: UIView, state: MotionTargetState, appearing: Bool) -> Bool {
return state.position != nil ||
state.size != nil ||
state.transform != nil ||
state.cornerRadius != nil ||
state.opacity != nil ||
state.overlay != nil ||
state.backgroundColor != nil ||
state.borderColor != nil ||
state.borderWidth != nil ||
state.shadowOpacity != nil ||
state.shadowRadius != nil ||
state.shadowOffset != nil ||
state.shadowColor != nil ||
state.shadowPath != nil ||
state.contentsRect != nil ||
state.forceAnimate
}
func getOverlayLayer() -> CALayer {
if overlayLayer == nil {
overlayLayer = CALayer()
overlayLayer!.frame = snapshot.bounds
overlayLayer!.opacity = 0
snapshot.layer.addSublayer(overlayLayer!)
/// The transition states.
fileprivate var transitionStates = [String: (Any?, Any?)]()
/// A reference to the animation timing function.
fileprivate var timingFunction = CAMediaTimingFunction.from(mediaTimingFunctionType: .standard)
/// Layer which holds the content.
fileprivate var contentLayer: CALayer? {
return snapshot.layer.sublayers?.get(0)
}
return overlayLayer!
}
func overlayKeyFor(key: String) -> String? {
if key.hasPrefix("overlay.") {
var key = key
key.removeSubrange(key.startIndex..<key.index(key.startIndex, offsetBy: 8))
return key
/// Layer which holds the overlay.
fileprivate var overlayLayer: CALayer?
override class func canAnimate(view: UIView, state: MotionTargetState, isAppearing: Bool) -> Bool {
return nil != state.position ||
nil != state.size ||
nil != state.transform ||
nil != state.cornerRadius ||
nil != state.opacity ||
nil != state.overlay ||
nil != state.backgroundColor ||
nil != state.borderColor ||
nil != state.borderWidth ||
nil != state.shadowOpacity ||
nil != state.shadowRadius ||
nil != state.shadowOffset ||
nil != state.shadowColor ||
nil != state.shadowPath ||
nil != state.contentsRect ||
state.forceAnimate
}
return nil
}
func currentValue(key: String) -> Any? {
if let key = overlayKeyFor(key: key) {
return overlayLayer?.value(forKeyPath: key)
func getOverlayLayer() -> CALayer {
if nil == overlayLayer {
overlayLayer = CALayer()
overlayLayer!.frame = snapshot.bounds
overlayLayer!.opacity = 0
snapshot.layer.addSublayer(overlayLayer!)
}
return overlayLayer!
}
if snapshot.layer.animationKeys()?.isEmpty != false {
return snapshot.layer.value(forKeyPath:key)
func overlayKeyFor(key: String) -> String? {
guard key.hasPrefix("overlay.") else {
return nil
}
var k = key
k.removeSubrange(k.startIndex..<k.index(key.startIndex, offsetBy: 8))
return k
}
return (snapshot.layer.presentation() ?? snapshot.layer).value(forKeyPath: key)
}
func getAnimation(key: String, beginTime: TimeInterval, fromValue: Any?, toValue: Any?, ignoreArc: Bool = false) -> CAPropertyAnimation {
let key = overlayKeyFor(key: key) ?? key
let anim: CAPropertyAnimation
if !ignoreArc, key == "position", let arcIntensity = targetState.arc,
let fromPos = (fromValue as? NSValue)?.cgPointValue,
let toPos = (toValue as? NSValue)?.cgPointValue,
abs(fromPos.x - toPos.x) >= 1, abs(fromPos.y - toPos.y) >= 1 {
let kanim = CAKeyframeAnimation(keyPath: key)
let path = CGMutablePath()
let maxControl = fromPos.y > toPos.y ? CGPoint(x: toPos.x, y: fromPos.y) : CGPoint(x: fromPos.x, y: toPos.y)
let minControl = (toPos - fromPos) / 2 + fromPos
path.move(to: fromPos)
path.addQuadCurve(to: toPos, control: minControl + (maxControl - minControl) * arcIntensity)
kanim.values = [fromValue!, toValue!]
kanim.path = path
kanim.duration = duration
kanim.timingFunctions = [timingFunction]
anim = kanim
} else if #available(iOS 9.0, *), key != "cornerRadius", let (stiffness, damping) = targetState.spring {
let sanim = CASpringAnimation(keyPath: key)
sanim.stiffness = stiffness
sanim.damping = damping
sanim.duration = sanim.settlingDuration * 0.9
sanim.fromValue = fromValue
sanim.toValue = toValue
anim = sanim
} else {
let banim = CABasicAnimation(keyPath: key)
banim.duration = duration
banim.fromValue = fromValue
banim.toValue = toValue
banim.timingFunction = timingFunction
anim = banim
func currentValue(key: String) -> Any? {
if let key = overlayKeyFor(key: key) {
return overlayLayer?.value(forKeyPath: key)
}
if snapshot.layer.animationKeys()?.isEmpty != false {
return snapshot.layer.value(forKeyPath:key)
}
return (snapshot.layer.presentation() ?? snapshot.layer).value(forKeyPath: key)
}
anim.fillMode = kCAFillModeBoth
anim.isRemovedOnCompletion = false
anim.beginTime = beginTime
return anim
func getAnimation(key: String, beginTime: TimeInterval, fromValue: Any?, toValue: Any?, ignoreArc: Bool = false) -> CAPropertyAnimation {
let key = overlayKeyFor(key: key) ?? key
let anim: CAPropertyAnimation
if !ignoreArc, key == "position", let arcIntensity = targetState.arc,
let fromPos = (fromValue as? NSValue)?.cgPointValue,
let toPos = (toValue as? NSValue)?.cgPointValue,
abs(fromPos.x - toPos.x) >= 1, abs(fromPos.y - toPos.y) >= 1 {
let kanim = CAKeyframeAnimation(keyPath: key)
let path = CGMutablePath()
let maxControl = fromPos.y > toPos.y ? CGPoint(x: toPos.x, y: fromPos.y) : CGPoint(x: fromPos.x, y: toPos.y)
let minControl = (toPos - fromPos) / 2 + fromPos
path.move(to: fromPos)
path.addQuadCurve(to: toPos, control: minControl + (maxControl - minControl) * arcIntensity)
kanim.values = [fromValue!, toValue!]
kanim.path = path
kanim.duration = duration
kanim.timingFunctions = [timingFunction]
anim = kanim
} else if #available(iOS 9.0, *), key != "cornerRadius", let (stiffness, damping) = targetState.spring {
let sanim = CASpringAnimation(keyPath: key)
sanim.stiffness = stiffness
sanim.damping = damping
sanim.duration = sanim.settlingDuration * 0.9
sanim.fromValue = fromValue
sanim.toValue = toValue
anim = sanim
} else {
let banim = CABasicAnimation(keyPath: key)
banim.duration = duration
banim.fromValue = fromValue
banim.toValue = toValue
banim.timingFunction = timingFunction
anim = banim
}
anim.fillMode = kCAFillModeBoth
anim.isRemovedOnCompletion = false
anim.beginTime = beginTime
return anim
}
// return the completion duration of the animation (duration + initial delay, not counting the beginTime)
......@@ -182,7 +191,7 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext {
let beginTime = currentTime + delay
var finalDuration: TimeInterval = duration
for (key, (fromValue, toValue)) in state {
for (key, (fromValue, toValue)) in transitionStates {
let neededTime = animate(key: key, beginTime: beginTime, fromValue: fromValue, toValue: toValue)
finalDuration = max(finalDuration, neededTime + delay)
}
......@@ -272,30 +281,30 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext {
override func apply(state: MotionTargetState) {
let targetState = viewState(targetState: state)
for (key, targetValue) in targetState {
if self.state[key] == nil {
if self.transitionStates[key] == nil {
let current = currentValue(key: key)
self.state[key] = (current, current)
self.transitionStates[key] = (current, current)
}
_ = animate(key: key, beginTime: 0, fromValue: targetValue, toValue: targetValue)
}
}
override func resume(timePassed: TimeInterval, reverse: Bool) {
for (key, (fromValue, toValue)) in state {
let realToValue = !reverse ? toValue : fromValue
override func resume(elapsedTime: TimeInterval, isReversed: Bool) {
for (key, (fromValue, toValue)) in transitionStates {
let realToValue = !isReversed ? toValue : fromValue
let realFromValue = currentValue(key: key)
state[key] = (realFromValue, realToValue)
transitionStates[key] = (realFromValue, realToValue)
}
// we need to update the duration to reflect current state
targetState.duration = reverse ? timePassed - targetState.delay : duration - timePassed
targetState.duration = isReversed ? elapsedTime - targetState.delay : duration - elapsedTime
let realDelay = max(0, targetState.delay - timePassed)
let realDelay = max(0, targetState.delay - elapsedTime)
animate(delay: realDelay)
}
func seek(layer: CALayer, timePassed: TimeInterval) {
let timeOffset = timePassed - targetState.delay
func seek(layer: CALayer, elapsedTime: TimeInterval) {
let timeOffset = elapsedTime - targetState.delay
for (key, anim) in layer.animations {
anim.speed = 0
anim.timeOffset = max(0, min(anim.duration - 0.01, timeOffset))
......@@ -304,13 +313,13 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext {
}
}
override func seek(timePassed: TimeInterval) {
seek(layer:snapshot.layer, timePassed:timePassed)
override func seek(to elapsedTime: TimeInterval) {
seek(layer:snapshot.layer, elapsedTime:elapsedTime)
if let contentLayer = contentLayer {
seek(layer:contentLayer, timePassed:timePassed)
seek(layer:contentLayer, elapsedTime:elapsedTime)
}
if let overlayLayer = overlayLayer {
seek(layer: overlayLayer, timePassed: timePassed)
seek(layer: overlayLayer, elapsedTime: elapsedTime)
}
}
......@@ -319,7 +328,7 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext {
overlayLayer = nil
}
override func startAnimations(appearing: Bool) {
override func startAnimations(isAppearing: Bool) {
if let beginState = targetState.beginState?.state {
let appeared = viewState(targetState: beginState)
for (key, value) in appeared {
......@@ -335,10 +344,10 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext {
let disappeared = viewState(targetState: targetState)
for (key, disappearedState) in disappeared {
let appearingState = currentValue(key: key)
let toValue = appearing ? appearingState : disappearedState
let fromValue = !appearing ? appearingState : disappearedState
state[key] = (fromValue, toValue)
let isAppearingState = currentValue(key: key)
let toValue = isAppearing ? isAppearingState : disappearedState
let fromValue = !isAppearing ? isAppearingState : disappearedState
transitionStates[key] = (fromValue, toValue)
}
animate(delay: targetState.delay)
......
......@@ -59,16 +59,16 @@ internal class MotionDefaultAnimator<ViewContext: MotionAnimatorViewContext>: Mo
var viewContexts: [UIView: ViewContext] = [:]
internal var insertToViewFirst = false
public func seekTo(timePassed: TimeInterval) {
public func seekTo(elapsedTime: TimeInterval) {
for viewContext in viewContexts.values {
viewContext.seek(timePassed: timePassed)
viewContext.seek(to: elapsedTime)
}
}
public func resume(timePassed: TimeInterval, reverse: Bool) -> TimeInterval {
public func resume(elapsedTime: TimeInterval, isReversed: Bool) -> TimeInterval {
var duration: TimeInterval = 0
for (_, context) in viewContexts {
context.resume(timePassed: timePassed, reverse: reverse)
context.resume(elapsedTime: elapsedTime, isReversed: isReversed)
duration = max(duration, context.duration)
}
return duration
......@@ -80,20 +80,20 @@ internal class MotionDefaultAnimator<ViewContext: MotionAnimatorViewContext>: Mo
}
}
public func canAnimate(view: UIView, appearing: Bool) -> Bool {
public func canAnimate(view: UIView, isAppearing: Bool) -> Bool {
guard let state = context[view] else { return false }
return ViewContext.canAnimate(view: view, state: state, appearing: appearing)
return ViewContext.canAnimate(view: view, state: state, isAppearing: isAppearing)
}
public func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval {
var duration: TimeInterval = 0
if insertToViewFirst {
for v in toViews { animate(view: v, appearing: true) }
for v in fromViews { animate(view: v, appearing: false) }
for v in toViews { animate(view: v, isAppearing: true) }
for v in fromViews { animate(view: v, isAppearing: false) }
} else {
for v in fromViews { animate(view: v, appearing: false) }
for v in toViews { animate(view: v, appearing: true) }
for v in fromViews { animate(view: v, isAppearing: false) }
for v in toViews { animate(view: v, isAppearing: true) }
}
for viewContext in viewContexts.values {
......@@ -103,11 +103,11 @@ internal class MotionDefaultAnimator<ViewContext: MotionAnimatorViewContext>: Mo
return duration
}
func animate(view: UIView, appearing: Bool) {
func animate(view: UIView, isAppearing: Bool) {
let snapshot = context.snapshotView(for: view)
let viewContext = ViewContext(animator:self, snapshot: snapshot, targetState: context[view]!)
viewContexts[view] = viewContext
viewContext.startAnimations(appearing: appearing)
viewContext.startAnimations(isAppearing: isAppearing)
}
public func clean() {
......
......@@ -33,17 +33,17 @@ internal class MotionViewPropertyViewContext: MotionAnimatorViewContext {
var viewPropertyAnimator: UIViewPropertyAnimator?
override class func canAnimate(view: UIView, state: MotionTargetState, appearing: Bool) -> Bool {
override class func canAnimate(view: UIView, state: MotionTargetState, isAppearing: Bool) -> Bool {
return view is UIVisualEffectView && state.opacity != nil
}
override func resume(timePassed: TimeInterval, reverse: Bool) {
viewPropertyAnimator?.finishAnimation(at: reverse ? .start : .end)
override func resume(elapsedTime: TimeInterval, isReversed: Bool) {
viewPropertyAnimator?.finishAnimation(at: isReversed ? .start : .end)
}
override func seek(timePassed: TimeInterval) {
override func seek(to elapsedTime: TimeInterval) {
viewPropertyAnimator?.pauseAnimation()
viewPropertyAnimator?.fractionComplete = CGFloat(timePassed / duration)
viewPropertyAnimator?.fractionComplete = CGFloat(elapsedTime / duration)
}
override func clean() {
......@@ -52,15 +52,15 @@ internal class MotionViewPropertyViewContext: MotionAnimatorViewContext {
viewPropertyAnimator = nil
}
override func startAnimations(appearing: Bool) {
override func startAnimations(isAppearing: Bool) {
guard let visualEffectView = snapshot as? UIVisualEffectView else { return }
let appearedEffect = visualEffectView.effect
let disappearedEffect = targetState.opacity == 0 ? nil : visualEffectView.effect
visualEffectView.effect = appearing ? disappearedEffect : appearedEffect
visualEffectView.effect = isAppearing ? disappearedEffect : appearedEffect
duration = targetState.duration!
viewPropertyAnimator = UIViewPropertyAnimator(duration: duration, curve: .easeInOut) {
visualEffectView.effect = appearing ? appearedEffect : disappearedEffect
visualEffectView.effect = isAppearing ? appearedEffect : disappearedEffect
}
viewPropertyAnimator!.startAnimation()
}
......
......@@ -59,7 +59,7 @@ public class MotionDebugPlugin: MotionPlugin {
return .infinity
}
public override func resume(timePassed: TimeInterval, reverse: Bool) -> TimeInterval {
public override func resume(elapsedTime: TimeInterval, isReversed: Bool) -> TimeInterval {
guard let debugView = debugView else { return 0.4 }
debugView.delegate = nil
......
......@@ -217,14 +217,14 @@ class DefaultAnimationPreprocessor: BasePreprocessor {
self.motion = motion
}
func shift(direction: MotionDefaultAnimationType.Direction, appearing: Bool, size: CGSize? = nil, transpose: Bool = false) -> CGPoint {
func shift(direction: MotionDefaultAnimationType.Direction, isAppearing: Bool, size: CGSize? = nil, transpose: Bool = false) -> CGPoint {
let size = size ?? context.container.bounds.size
let rtn: CGPoint
switch direction {
case .left, .right:
rtn = CGPoint(x: (direction == .right) == appearing ? -size.width : size.width, y: 0)
rtn = CGPoint(x: (direction == .right) == isAppearing ? -size.width : size.width, y: 0)
case .up, .down:
rtn = CGPoint(x: 0, y: (direction == .down) == appearing ? -size.height : size.height)
rtn = CGPoint(x: 0, y: (direction == .down) == isAppearing ? -size.height : size.height)
}
if transpose {
return CGPoint(x: rtn.y, y: rtn.x)
......@@ -261,7 +261,7 @@ class DefaultAnimationPreprocessor: BasePreprocessor {
}
if case .auto = defaultAnimation {
if animators!.contains(where: { $0.canAnimate(view: toView, appearing: true) || $0.canAnimate(view: fromView, appearing: false) }) {
if animators!.contains(where: { $0.canAnimate(view: toView, isAppearing: true) || $0.canAnimate(view: fromView, isAppearing: false) }) {
defaultAnimation = .none
} else if inNavigationController {
defaultAnimation = presenting ? .push(direction:.left) : .pull(direction:.right)
......@@ -286,28 +286,28 @@ class DefaultAnimationPreprocessor: BasePreprocessor {
.masksToBounds(false)]
switch defaultAnimation {
case .push(let direction):
context[toView]!.append(contentsOf: [.translate(shift(direction: direction, appearing: true)),
context[toView]!.append(contentsOf: [.translate(shift(direction: direction, isAppearing: true)),
.shadowOpacity(0),
.beginWith(modifiers: shadowState),
.timingFunction(.deceleration)])
context[fromView]!.append(contentsOf: [.translate(shift(direction: direction, appearing: false) / 3),
context[fromView]!.append(contentsOf: [.translate(shift(direction: direction, isAppearing: false) / 3),
.overlay(color: .black, opacity: 0.1),
.timingFunction(.deceleration)])
case .pull(let direction):
motion.insertToViewFirst = true
context[fromView]!.append(contentsOf: [.translate(shift(direction: direction, appearing: false)),
context[fromView]!.append(contentsOf: [.translate(shift(direction: direction, isAppearing: false)),
.shadowOpacity(0),
.beginWith(modifiers: shadowState)])
context[toView]!.append(contentsOf: [.translate(shift(direction: direction, appearing: true) / 3),
context[toView]!.append(contentsOf: [.translate(shift(direction: direction, isAppearing: true) / 3),
.overlay(color: .black, opacity: 0.1)])
case .slide(let direction):
context[fromView]!.append(contentsOf: [.translate(shift(direction: direction, appearing: false))])
context[toView]!.append(contentsOf: [.translate(shift(direction: direction, appearing: true))])
context[fromView]!.append(contentsOf: [.translate(shift(direction: direction, isAppearing: false))])
context[toView]!.append(contentsOf: [.translate(shift(direction: direction, isAppearing: true))])
case .zoomSlide(let direction):
context[fromView]!.append(contentsOf: [.translate(shift(direction: direction, appearing: false)), .scale(0.8)])
context[toView]!.append(contentsOf: [.translate(shift(direction: direction, appearing: true)), .scale(0.8)])
context[fromView]!.append(contentsOf: [.translate(shift(direction: direction, isAppearing: false)), .scale(0.8)])
context[toView]!.append(contentsOf: [.translate(shift(direction: direction, isAppearing: true)), .scale(0.8)])
case .cover(let direction):
context[toView]!.append(contentsOf: [.translate(shift(direction: direction, appearing: true)),
context[toView]!.append(contentsOf: [.translate(shift(direction: direction, isAppearing: true)),
.shadowOpacity(0),
.beginWith(modifiers: shadowState),
.timingFunction(.deceleration)])
......@@ -315,12 +315,12 @@ class DefaultAnimationPreprocessor: BasePreprocessor {
.timingFunction(.deceleration)])
case .uncover(let direction):
motion.insertToViewFirst = true
context[fromView]!.append(contentsOf: [.translate(shift(direction: direction, appearing: false)),
context[fromView]!.append(contentsOf: [.translate(shift(direction: direction, isAppearing: false)),
.shadowOpacity(0),
.beginWith(modifiers: shadowState)])
context[toView]!.append(contentsOf: [.overlay(color: .black, opacity: 0.1)])
case .pageIn(let direction):
context[toView]!.append(contentsOf: [.translate(shift(direction: direction, appearing: true)),
context[toView]!.append(contentsOf: [.translate(shift(direction: direction, isAppearing: true)),
.shadowOpacity(0),
.beginWith(modifiers: shadowState),
.timingFunction(.deceleration)])
......@@ -329,7 +329,7 @@ class DefaultAnimationPreprocessor: BasePreprocessor {
.timingFunction(.deceleration)])
case .pageOut(let direction):
motion.insertToViewFirst = true
context[fromView]!.append(contentsOf: [.translate(shift(direction: direction, appearing: false)),
context[fromView]!.append(contentsOf: [.translate(shift(direction: direction, isAppearing: false)),
.shadowOpacity(0),
.beginWith(modifiers: shadowState)])
context[toView]!.append(contentsOf: [.scale(0.7),
......
......@@ -25,45 +25,91 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import UIKit
internal enum CAMediaTimingFunctionType {
case linear
case easeIn
case easeOut
case easeInOut
case standard
case deceleration
case acceleration
case sharp
case easeOutBack
}
internal extension CAMediaTimingFunction {
// default
static let linear = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
static let easeIn = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
static let easeOut = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
static let easeInOut = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
// default
static let linear = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
static let easeIn = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
static let easeOut = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
static let easeInOut = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
// material
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 acceleration = CAMediaTimingFunction(controlPoints: 0.4, 0.0, 1, 1)
static let sharp = CAMediaTimingFunction(controlPoints: 0.4, 0.0, 0.6, 1)
// easing.net
static let easeOutBack = CAMediaTimingFunction(controlPoints: 0.175, 0.885, 0.32, 1.275)
static func from(name: String) -> CAMediaTimingFunction? {
switch name {
case "linear":
return .linear
case "easeIn":
return .easeIn
case "easeOut":
return .easeOut
case "easeInOut":
return .easeInOut
case "standard":
return .standard
case "deceleration":
return .deceleration
case "acceleration":
return .acceleration
case "sharp":
return .sharp
default:
return nil
// material
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 acceleration = CAMediaTimingFunction(controlPoints: 0.4, 0.0, 1, 1)
static let sharp = CAMediaTimingFunction(controlPoints: 0.4, 0.0, 0.6, 1)
// easing.net
static let easeOutBack = CAMediaTimingFunction(controlPoints: 0.175, 0.885, 0.32, 1.75)
/**
Converts a string name matching a CAMediaTimingFunctionType to a
CAMediaTimingFunction value.
- Parameter mediaTimingFunctionType: A String.
*/
static func from(mediaTimingFunctionType: String) -> CAMediaTimingFunction? {
switch mediaTimingFunctionType {
case "linear":
return .linear
case "easeIn":
return .easeIn
case "easeOut":
return .easeOut
case "easeInOut":
return .easeInOut
case "standard":
return .standard
case "deceleration":
return .deceleration
case "acceleration":
return .acceleration
case "sharp":
return .sharp
case "easeOutBack":
return .easeOutBack
default:
return nil
}
}
/**
Converts a CAMediaTimingFunctionType to a CAMediaTimingFunction value.
- Parameter mediaTimingFunctionType: A CAMediaTimingFunctionType.
*/
static func from(mediaTimingFunctionType: CAMediaTimingFunctionType) -> CAMediaTimingFunction {
switch mediaTimingFunctionType {
case .linear:
return .linear
case .easeIn:
return .easeIn
case .easeOut:
return .easeOut
case .easeInOut:
return .easeInOut
case .standard:
return .standard
case .deceleration:
return .deceleration
case .acceleration:
return .acceleration
case .sharp:
return .sharp
case .easeOutBack:
return .easeOutBack
}
}
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
Gets the Obj-C reference for the instance object within the UIView extension.
- Parameter base: Base object.
- Parameter key: Memory key pointer.
- Parameter initializer: Object initializer.
- Returns: The associated reference for the initializer object.
*/
internal func AssociatedObject<T: Any>(base: Any, key: UnsafePointer<UInt8>, initializer: () -> T) -> T {
if let v = objc_getAssociatedObject(base, key) as? T {
return v
}
let v = initializer()
objc_setAssociatedObject(base, key, v, .OBJC_ASSOCIATION_RETAIN)
return v
}
/**
Sets the Obj-C reference for the instance object within the UIView extension.
- Parameter base: Base object.
- Parameter key: Memory key pointer.
- Parameter value: The object instance to set for the associated object.
- Returns: The associated reference for the initializer object.
*/
internal func AssociateObject<T: Any>(base: Any, key: UnsafePointer<UInt8>, value: T) {
objc_setAssociatedObject(base, key, value, .OBJC_ASSOCIATION_RETAIN)
}
......@@ -47,14 +47,14 @@ public class MotionController: NSObject {
}
}
let timePassed = progress * totalDuration
let elapsedTime = progress * totalDuration
if interactive {
for animator in animators {
animator.seekTo(timePassed: timePassed)
animator.seekTo(elapsedTime: elapsedTime)
}
} else {
for plugin in plugins where plugin.requirePerFrameCallback {
plugin.seekTo(timePassed: timePassed)
plugin.seekTo(elapsedTime: elapsedTime)
}
}
}
......@@ -99,14 +99,14 @@ public class MotionController: NSObject {
}
func displayUpdate(_ link: CADisplayLink) {
if transitioning, duration > 0, let beginTime = beginTime {
let timePassed = CACurrentMediaTime() - beginTime
let elapsedTime = CACurrentMediaTime() - beginTime
if timePassed > duration {
if elapsedTime > duration {
progress = finishing ? 1 : 0
self.beginTime = nil
complete(finished: finishing)
} else {
var completed = timePassed / totalDuration
var completed = elapsedTime / totalDuration
if !finishing {
completed = 1 - completed
}
......@@ -156,8 +156,8 @@ public extension MotionController {
}
var maxTime: TimeInterval = 0
for animator in self.animators {
maxTime = max(maxTime, animator.resume(timePassed:self.progress * self.totalDuration,
reverse: false))
maxTime = max(maxTime, animator.resume(elapsedTime:self.progress * self.totalDuration,
isReversed: false))
}
self.complete(after: maxTime, finishing: true)
}
......@@ -179,8 +179,8 @@ public extension MotionController {
if adjustedProgress < 0 {
adjustedProgress = -adjustedProgress
}
maxTime = max(maxTime, animator.resume(timePassed:adjustedProgress * self.totalDuration,
reverse: true))
maxTime = max(maxTime, animator.resume(elapsedTime:adjustedProgress * self.totalDuration,
isReversed: true))
}
self.complete(after: maxTime, finishing: false)
}
......@@ -286,10 +286,10 @@ internal extension MotionController {
animatingViews = [([UIView], [UIView])]()
for animator in animators {
let currentFromViews = context.fromViews.filter { (view: UIView) -> Bool in
return animator.canAnimate(view: view, appearing: false)
return animator.canAnimate(view: view, isAppearing: false)
}
let currentToViews = context.toViews.filter { (view: UIView) -> Bool in
return animator.canAnimate(view: view, appearing: true)
return animator.canAnimate(view: view, isAppearing: true)
}
animatingViews.append((currentFromViews, currentToViews))
}
......@@ -335,10 +335,10 @@ internal extension MotionController {
complete(finished: finishing)
return
}
let timePassed = (finishing ? progress : 1 - progress) * totalDuration
let elapsedTime = (finishing ? progress : 1 - progress) * totalDuration
self.finishing = finishing
self.duration = after + timePassed
self.beginTime = CACurrentMediaTime() - timePassed
self.duration = after + elapsedTime
self.beginTime = CACurrentMediaTime() - elapsedTime
}
func complete(finished: Bool) {
......
......@@ -71,11 +71,11 @@ open class MotionPlugin: NSObject, MotionPreprocessor, MotionAnimator {
- Parameters:
- context: object holding all parsed and changed modifiers,
- view: the view to check whether or not the plugin can handle the animation
- appearing: true if the view is appearing(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.
The view will also be hidden automatically during the animation.
*/
open func canAnimate(view: UIView, appearing: Bool) -> Bool { return false }
open func canAnimate(view: UIView, isAppearing: Bool) -> Bool { return false }
/**
Perform the animation.
......@@ -103,9 +103,9 @@ open class MotionPlugin: NSObject, MotionPreprocessor, MotionAnimator {
This method is called when an interactive animation is in place
The plugin should pause the animation, and seek to the given progress
- Parameters:
- timePassed: time of the animation to seek to.
- elapsedTime: time of the animation to seek to.
*/
open func seekTo(timePassed: TimeInterval) {}
open func seekTo(elapsedTime: TimeInterval) {}
/**
For supporting interactive animation only.
......@@ -113,10 +113,10 @@ open class MotionPlugin: NSObject, MotionPreprocessor, MotionAnimator {
This method is called when an interactive animation is ended
The plugin should resume the animation.
- Parameters:
- timePassed: will be the same value since last `seekTo`
- elapsedTime: will be the same value since last `seekTo`
- reverse: a boolean value indicating whether or not the animation should reverse
*/
open func resume(timePassed: TimeInterval, reverse: Bool) -> TimeInterval { return 0 }
open func resume(elapsedTime: TimeInterval, isReversed: Bool) -> TimeInterval { return 0 }
/**
For supporting interactive animation only.
......
......@@ -89,7 +89,7 @@ extension MotionTransition: MotionStringConvertible {
let c3 = parameters.getFloat(2),
let c4 = parameters.getFloat(3) {
return .timingFunction(CAMediaTimingFunction(controlPoints: c1, c2, c3, c4))
} else if let name = parameters.get(0)?.name, let timingFunction = CAMediaTimingFunction.from(name:name) {
} else if let name = parameters.get(0)?.name, let timingFunction = CAMediaTimingFunction.from(mediaTimingFunctionType: name) {
return .timingFunction(timingFunction)
}
case "arc":
......
......@@ -35,12 +35,12 @@ public protocol MotionPreprocessor: class {
public protocol MotionAnimator: class {
weak var context: MotionContext! { get set }
func canAnimate(view: UIView, appearing: Bool) -> Bool
func canAnimate(view: UIView, isAppearing: Bool) -> Bool
func animate(fromViews: [UIView], toViews: [UIView]) -> TimeInterval
func clean()
func seekTo(timePassed: TimeInterval)
func resume(timePassed: TimeInterval, reverse: Bool) -> TimeInterval
func seekTo(elapsedTime: TimeInterval)
func resume(elapsedTime: TimeInterval, isReversed: Bool) -> TimeInterval
func apply(state: MotionTargetState, to view: UIView)
}
......
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