Commit 55e5e287 by Daniel Dahan

progress commit for MotionCoreAnimationViewContext

parent 7b809595
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
965FE9671FDD99800098BDD0 /* Motion+Start.swift in Sources */ = {isa = PBXBuildFile; fileRef = 965FE9661FDD99800098BDD0 /* Motion+Start.swift */; }; 965FE9671FDD99800098BDD0 /* Motion+Start.swift in Sources */ = {isa = PBXBuildFile; fileRef = 965FE9661FDD99800098BDD0 /* Motion+Start.swift */; };
965FE9691FDDA1F20098BDD0 /* MotionViewOrderStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 965FE9681FDDA1F20098BDD0 /* MotionViewOrderStrategy.swift */; }; 965FE9691FDDA1F20098BDD0 /* MotionViewOrderStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 965FE9681FDDA1F20098BDD0 /* MotionViewOrderStrategy.swift */; };
965FE96B1FDDA4EA0098BDD0 /* BaseMotionPreprocessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 965FE96A1FDDA4EA0098BDD0 /* BaseMotionPreprocessor.swift */; }; 965FE96B1FDDA4EA0098BDD0 /* BaseMotionPreprocessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 965FE96A1FDDA4EA0098BDD0 /* BaseMotionPreprocessor.swift */; };
965FE96D1FDDA6400098BDD0 /* BaseMotionAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 965FE96C1FDDA6400098BDD0 /* BaseMotionAnimator.swift */; }; 965FE96D1FDDA6400098BDD0 /* MotionCoreAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 965FE96C1FDDA6400098BDD0 /* MotionCoreAnimator.swift */; };
965FE96F1FDEFA8B0098BDD0 /* Motion+Animate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 965FE96E1FDEFA8B0098BDD0 /* Motion+Animate.swift */; }; 965FE96F1FDEFA8B0098BDD0 /* Motion+Animate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 965FE96E1FDEFA8B0098BDD0 /* Motion+Animate.swift */; };
96E409651F24F7370015A2B5 /* MotionAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96E4093D1F24F7370015A2B5 /* MotionAnimator.swift */; }; 96E409651F24F7370015A2B5 /* MotionAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96E4093D1F24F7370015A2B5 /* MotionAnimator.swift */; };
96E409661F24F7370015A2B5 /* MotionAnimatorViewContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96E4093E1F24F7370015A2B5 /* MotionAnimatorViewContext.swift */; }; 96E409661F24F7370015A2B5 /* MotionAnimatorViewContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96E4093E1F24F7370015A2B5 /* MotionAnimatorViewContext.swift */; };
...@@ -91,7 +91,7 @@ ...@@ -91,7 +91,7 @@
965FE9661FDD99800098BDD0 /* Motion+Start.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Motion+Start.swift"; sourceTree = "<group>"; }; 965FE9661FDD99800098BDD0 /* Motion+Start.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Motion+Start.swift"; sourceTree = "<group>"; };
965FE9681FDDA1F20098BDD0 /* MotionViewOrderStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MotionViewOrderStrategy.swift; sourceTree = "<group>"; }; 965FE9681FDDA1F20098BDD0 /* MotionViewOrderStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MotionViewOrderStrategy.swift; sourceTree = "<group>"; };
965FE96A1FDDA4EA0098BDD0 /* BaseMotionPreprocessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseMotionPreprocessor.swift; sourceTree = "<group>"; }; 965FE96A1FDDA4EA0098BDD0 /* BaseMotionPreprocessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseMotionPreprocessor.swift; sourceTree = "<group>"; };
965FE96C1FDDA6400098BDD0 /* BaseMotionAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseMotionAnimator.swift; sourceTree = "<group>"; }; 965FE96C1FDDA6400098BDD0 /* MotionCoreAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MotionCoreAnimator.swift; sourceTree = "<group>"; };
965FE96E1FDEFA8B0098BDD0 /* Motion+Animate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Motion+Animate.swift"; sourceTree = "<group>"; }; 965FE96E1FDEFA8B0098BDD0 /* Motion+Animate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Motion+Animate.swift"; sourceTree = "<group>"; };
96C98DD11E424AB000B22906 /* Motion.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Motion.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 96C98DD11E424AB000B22906 /* Motion.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Motion.framework; sourceTree = BUILT_PRODUCTS_DIR; };
96E4093D1F24F7370015A2B5 /* MotionAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionAnimator.swift; sourceTree = "<group>"; }; 96E4093D1F24F7370015A2B5 /* MotionAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MotionAnimator.swift; sourceTree = "<group>"; };
...@@ -183,7 +183,7 @@ ...@@ -183,7 +183,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
96E4093D1F24F7370015A2B5 /* MotionAnimator.swift */, 96E4093D1F24F7370015A2B5 /* MotionAnimator.swift */,
965FE96C1FDDA6400098BDD0 /* BaseMotionAnimator.swift */, 965FE96C1FDDA6400098BDD0 /* MotionCoreAnimator.swift */,
96E4093E1F24F7370015A2B5 /* MotionAnimatorViewContext.swift */, 96E4093E1F24F7370015A2B5 /* MotionAnimatorViewContext.swift */,
96E4093F1F24F7370015A2B5 /* MotionCoreAnimationViewContext.swift */, 96E4093F1F24F7370015A2B5 /* MotionCoreAnimationViewContext.swift */,
96E409401F24F7370015A2B5 /* MotionHasInsertOrder.swift */, 96E409401F24F7370015A2B5 /* MotionHasInsertOrder.swift */,
...@@ -332,7 +332,7 @@ ...@@ -332,7 +332,7 @@
965FE9631FDCCE030098BDD0 /* Motion+Complete.swift in Sources */, 965FE9631FDCCE030098BDD0 /* Motion+Complete.swift in Sources */,
96E4097D1F24F7370015A2B5 /* MotionPlugin.swift in Sources */, 96E4097D1F24F7370015A2B5 /* MotionPlugin.swift in Sources */,
96E409681F24F7370015A2B5 /* MotionHasInsertOrder.swift in Sources */, 96E409681F24F7370015A2B5 /* MotionHasInsertOrder.swift in Sources */,
965FE96D1FDDA6400098BDD0 /* BaseMotionAnimator.swift in Sources */, 965FE96D1FDDA6400098BDD0 /* MotionCoreAnimator.swift in Sources */,
96E4096E1F24F7370015A2B5 /* Motion+CG.swift in Sources */, 96E4096E1F24F7370015A2B5 /* Motion+CG.swift in Sources */,
96E409851F24F7370015A2B5 /* MatchPreprocessor.swift in Sources */, 96E409851F24F7370015A2B5 /* MatchPreprocessor.swift in Sources */,
96E409861F24F7370015A2B5 /* MotionPreprocessor.swift in Sources */, 96E409861F24F7370015A2B5 /* MotionPreprocessor.swift in Sources */,
......
...@@ -30,17 +30,20 @@ import UIKit ...@@ -30,17 +30,20 @@ import UIKit
internal class MotionAnimatorViewContext { internal class MotionAnimatorViewContext {
/// An optional reference to a MotionAnimator. /// An optional reference to a MotionAnimator.
var animator: BaseMotionAnimator? var animator: MotionCoreAnimator?
/// A reference to the snapshot UIView. /// A reference to the snapshot UIView.
var snapshot: UIView var snapshot: UIView
/// Animation duration time.
var duration: TimeInterval = 0
/// The animation target state. /// The animation target state.
var targetState: MotionTransitionState var targetState: MotionTransitionState
/// A boolean indicating if the view is appearing.
var isAppearing: Bool
/// Animation duration time.
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)
...@@ -56,11 +59,13 @@ internal class MotionAnimatorViewContext { ...@@ -56,11 +59,13 @@ internal class MotionAnimatorViewContext {
- Parameter animator: A MotionAnimator. - Parameter animator: A MotionAnimator.
- Parameter snapshot: A UIView. - Parameter snapshot: A UIView.
- Parameter targetState: A MotionTransitionState. - Parameter targetState: A MotionTransitionState.
- Parameter isAppearing: A Boolean.
*/ */
required init(animator: BaseMotionAnimator, snapshot: UIView, targetState: MotionTransitionState) { required init(animator: MotionCoreAnimator, snapshot: UIView, targetState: MotionTransitionState, isAppearing: Bool) {
self.animator = animator self.animator = animator
self.snapshot = snapshot self.snapshot = snapshot
self.targetState = targetState self.targetState = targetState
self.isAppearing = isAppearing
} }
/// Cleans the context. /// Cleans the context.
...@@ -86,8 +91,11 @@ internal class MotionAnimatorViewContext { ...@@ -86,8 +91,11 @@ internal class MotionAnimatorViewContext {
- Parameter at elapsedTime: A TimeInterval. - Parameter at elapsedTime: 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.
*/ */
func resume(at elapsedTime: TimeInterval, isReversed: Bool) {} func resume(at elapsedTime: TimeInterval, isReversed: Bool) -> TimeInterval {
return 0
}
/** /**
Moves the animation to the given elapsed time. Moves the animation to the given elapsed time.
...@@ -106,5 +114,8 @@ internal class MotionAnimatorViewContext { ...@@ -106,5 +114,8 @@ internal class MotionAnimatorViewContext {
- Parameter isAppearing: A boolean value whether the view - Parameter isAppearing: A boolean value whether the view
is appearing or not. is appearing or not.
*/ */
func startAnimations(isAppearing: Bool) {} @discardableResult
func startAnimations() -> TimeInterval {
return 0
}
} }
...@@ -35,6 +35,9 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext { ...@@ -35,6 +35,9 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext {
/// A reference to the animation timing function. /// A reference to the animation timing function.
fileprivate var timingFunction = CAMediaTimingFunction.standard fileprivate var timingFunction = CAMediaTimingFunction.standard
/// Current animations.
var animations = [(CALayer, String, CAAnimation)]()
/// Layer which holds the content. /// Layer which holds the content.
fileprivate var contentLayer: CALayer? { fileprivate var contentLayer: CALayer? {
return snapshot.layer.sublayers?.get(0) return snapshot.layer.sublayers?.get(0)
...@@ -68,40 +71,60 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext { ...@@ -68,40 +71,60 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext {
} }
override func apply(state: MotionTransitionState) { override func apply(state: MotionTransitionState) {
let ts = viewState(targetState: state) let targetState = viewState(targetState: state)
for (key, targetValue) in ts { for (key, targetValue) in targetState {
if nil == transitionStates[key] { if nil == transitionStates[key] {
let current = currentValue(for: key) let current = currentValue(for: key)
transitionStates[key] = (current, current) transitionStates[key] = (current, current)
} }
animate(key: key, beginTime: 0, fromValue: targetValue, toValue: targetValue) let oldAnimations = animations
animations = []
animate(key: key, beginTime: 0, duration: 100, fromValue: targetValue, toValue: targetValue)
animations = oldAnimations
} }
} }
override func resume(at elapsedTime: TimeInterval, isReversed: Bool) { override func resume(at elapsedTime: TimeInterval, isReversed: Bool) -> TimeInterval {
for (key, (fromValue, toValue)) in transitionStates { for (key, (fromValue, toValue)) in transitionStates {
transitionStates[key] = (currentValue(for: key), !isReversed ? toValue : fromValue) transitionStates[key] = (currentValue(for: key), isReversed ? fromValue : toValue)
} }
targetState.duration = isReversed ? elapsedTime - targetState.delay : duration - elapsedTime if isReversed {
animate(delay: max(0, targetState.delay - elapsedTime)) if elapsedTime > targetState.delay + duration {
let backDelay = elapsedTime - (targetState.delay + duration)
return animate(delay: backDelay, duration: duration)
} else if elapsedTime > targetState.delay {
return animate(delay: 0, duration: duration - elapsedTime - targetState.delay)
}
} else {
if elapsedTime <= targetState.delay {
return animate(delay: targetState.delay - elapsedTime, duration: duration)
} else if elapsedTime <= targetState.delay + duration {
let timePassedDelay = elapsedTime - targetState.delay
return animate(delay: 0, duration: duration - timePassedDelay)
}
}
return 0
} }
override func seek(to elapsedTime: TimeInterval) { override func seek(to elapsedTime: TimeInterval) {
seek(layer: snapshot.layer, elapsedTime: elapsedTime) let timeOffset = CGFloat(elapsedTime - targetState.delay)
if let v = contentLayer { for (layer, key, anim) in animations {
seek(layer: v, elapsedTime: elapsedTime) anim.speed = 0
} anim.timeOffset = CFTimeInterval(timeOffset.clamp(0, CGFloat(anim.duration) - 0.001))
layer.removeAnimation(forKey: key)
if let v = overlayLayer { layer.add(anim, forKey: key)
seek(layer: v, elapsedTime: elapsedTime)
} }
} }
override func startAnimations(isAppearing: Bool) { override func startAnimations() -> TimeInterval {
if let beginState = targetState.beginState?.state { if let beginState = targetState.beginState?.state {
let appeared = viewState(targetState: beginState) let appeared = viewState(targetState: beginState)
for (k, v) in appeared { for (k, v) in appeared {
...@@ -124,16 +147,16 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext { ...@@ -124,16 +147,16 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext {
transitionStates[k] = (fromValue, toValue) transitionStates[k] = (fromValue, toValue)
} }
animate(delay: targetState.delay) return animate(delay: targetState.delay, duration: duration)
} }
} }
extension MotionCoreAnimationViewContext { fileprivate extension MotionCoreAnimationViewContext {
/** /**
Lazy loads the overlay layer. Lazy loads the overlay layer.
- Returns: A CALayer. - Returns: A CALayer.
*/ */
fileprivate func getOverlayLayer() -> CALayer { func getOverlayLayer() -> CALayer {
if nil == overlayLayer { if nil == overlayLayer {
overlayLayer = CALayer() overlayLayer = CALayer()
overlayLayer!.frame = snapshot.bounds overlayLayer!.frame = snapshot.bounds
...@@ -149,7 +172,7 @@ extension MotionCoreAnimationViewContext { ...@@ -149,7 +172,7 @@ extension MotionCoreAnimationViewContext {
- Parameter for key: A String. - Parameter for key: A String.
- Returns: An optional String. - Returns: An optional String.
*/ */
fileprivate func overlayKey(for key: String) -> String? { func overlayKey(for key: String) -> String? {
guard key.hasPrefix("overlay.") else { guard key.hasPrefix("overlay.") else {
return nil return nil
} }
...@@ -164,9 +187,9 @@ extension MotionCoreAnimationViewContext { ...@@ -164,9 +187,9 @@ extension MotionCoreAnimationViewContext {
- Parameter for key: A String. - Parameter for key: A String.
- Returns: An optional Any value. - Returns: An optional Any value.
*/ */
fileprivate func currentValue(for key: String) -> Any? { func currentValue(for key: String) -> Any? {
if let key = overlayKey(for: key) { if let key = overlayKey(for: key) {
return overlayLayer?.value(forKeyPath: key) return (overlayLayer?.presentation() ?? overlayLayer)?.value(forKeyPath: key)
} }
if false != snapshot.layer.animationKeys()?.isEmpty { if false != snapshot.layer.animationKeys()?.isEmpty {
...@@ -180,11 +203,12 @@ extension MotionCoreAnimationViewContext { ...@@ -180,11 +203,12 @@ extension MotionCoreAnimationViewContext {
Retrieves the animation for a given key. Retrieves the animation for a given key.
- Parameter key: String. - Parameter key: String.
- Parameter beginTime: A TimeInterval. - Parameter beginTime: A TimeInterval.
- Parameter duration: A TimeInterval.
- Parameter fromValue: An optional Any value. - Parameter fromValue: An optional Any value.
- Parameter toValue: An optional Any value. - Parameter toValue: An optional Any value.
- Parameter ignoreArc: A Boolean value to ignore an arc position. - Parameter ignoreArc: A Boolean value to ignore an arc position.
*/ */
fileprivate func getAnimation(key: String, beginTime: TimeInterval, fromValue: Any?, toValue: Any?, ignoreArc: Bool = false) -> CAPropertyAnimation { func getAnimation(key: String, beginTime: TimeInterval, duration: TimeInterval, fromValue: Any?, toValue: Any?, ignoreArc: Bool = false) -> CAPropertyAnimation {
let key = overlayKey(for: key) ?? key let key = overlayKey(for: key) ?? key
let anim: CAPropertyAnimation let anim: CAPropertyAnimation
...@@ -211,7 +235,7 @@ extension MotionCoreAnimationViewContext { ...@@ -211,7 +235,7 @@ extension MotionCoreAnimationViewContext {
let a = CASpringAnimation(keyPath: key) let a = CASpringAnimation(keyPath: key)
a.stiffness = stiffness a.stiffness = stiffness
a.damping = damping a.damping = damping
a.duration = a.settlingDuration * 0.9 a.duration = a.settlingDuration
a.fromValue = fromValue a.fromValue = fromValue
a.toValue = toValue a.toValue = toValue
...@@ -234,28 +258,118 @@ extension MotionCoreAnimationViewContext { ...@@ -234,28 +258,118 @@ extension MotionCoreAnimationViewContext {
} }
/** /**
Sets a new size for the given view.
- Parameter view: A UIView.
- Parameter newSize: A CGSize.
*/
func setSize(view: UIView, newSize: CGSize) {
let oldSize = view.bounds.size
if .noSnapshot != targetState.snapshotType {
if 0 == oldSize.width || 0 == oldSize.height || 0 == newSize.width || 0 == newSize.height {
for v in view.subviews {
v.center = newSize.center
v.bounds.size = newSize
setSize(view: v, newSize: newSize)
}
} else {
let sizeRatio = oldSize / newSize
for v in view.subviews {
let center = v.center
let size = v.bounds.size
v.center = center / sizeRatio
v.bounds.size = size / sizeRatio
setSize(view: v, newSize: size / sizeRatio)
}
}
view.bounds.size = newSize
} else {
view.bounds.size = newSize
view.layoutSubviews()
}
}
/**
Executes a UIView based animation.
- Parameter duration: A TimeInterval.
- Parameter delay: A TimeInterval.
- Parameter _ animations: An animation block.
*/
func uiViewBasedAnimate(duration: TimeInterval, delay: TimeInterval, _ animations: @escaping () -> Void) {
CALayer.motionAddedAnimations = []
if let (stiffness, damping) = targetState.spring {
UIView.animate(withDuration: duration, delay: delay, usingSpringWithDamping: 1, initialSpringVelocity: 0, options: [], animations: animations, completion: nil)
let addedAnimations = CALayer.motionAddedAnimations!
CALayer.motionAddedAnimations = nil
for (layer, key, anim) in addedAnimations {
if #available(iOS 9.0, *), let anim = anim as? CASpringAnimation {
anim.stiffness = stiffness
anim.damping = damping
self.addAnimation(anim, for: key, to: layer)
} else {
self.animations.append((layer, key, anim))
}
}
} else {
CATransaction.begin()
CATransaction.setAnimationTimingFunction(timingFunction)
UIView.animate(withDuration: duration, delay: delay, options: [], animations: animations, completion: nil)
CATransaction.commit()
let addedAnimations = CALayer.motionAddedAnimations!
CALayer.motionAddedAnimations = nil
self.animations.append(contentsOf: addedAnimations)
}
}
/**
Adds an animation to a given layer.
- Parameter _ animation: A CAAnimation.
- Parameter for key: A String.
- Parameter to layer: A CALayer.
*/
func addAnimation(_ animation: CAAnimation, for key: String, to layer: CALayer) {
animations.append((layer, key, animation))
layer.add(animation, forKey: key)
}
/**
Retrieves the duration of an animation, including the Retrieves the duration of an animation, including the
duration of the animation and the initial delay. duration of the animation and the initial delay.
- Parameter key: A String. - Parameter key: A String.
- Parameter beginTime: A TimeInterval. - Parameter beginTime: A TimeInterval.
- Parameter duration: A TimeInterval.
- Parameter fromValue: A optional Any value. - Parameter fromValue: A optional Any value.
- Parameter toValue: A optional Any value. - Parameter toValue: A optional Any value.
- Returns: A TimeInterval. - Returns: A TimeInterval.
*/ */
@discardableResult @discardableResult
fileprivate func animate(key: String, beginTime: TimeInterval, fromValue: Any?, toValue: Any?) -> TimeInterval { func animate(key: String, beginTime: TimeInterval, duration: TimeInterval, fromValue: Any?, toValue: Any?) -> TimeInterval {
let anim = getAnimation(key: key, beginTime:beginTime, fromValue: fromValue, toValue: toValue) let anim = getAnimation(key: key, beginTime: beginTime, duration: duration, fromValue: fromValue, toValue: toValue)
if let overlayKey = overlayKey(for: key) { if let overlayKey = overlayKey(for: key) {
getOverlayLayer().add(anim, forKey: overlayKey) addAnimation(anim, for: overlayKey, to: getOverlayLayer())
} else { } else {
snapshot.layer.add(anim, forKey: key) snapshot.layer.add(anim, forKey: key)
switch key { switch key {
case "cornerRadius", "contentsRect", "contentsScale": case "cornerRadius", "contentsRect", "contentsScale":
contentLayer?.add(anim, forKey: key) addAnimation(anim, for: key, to: snapshot.layer)
overlayLayer?.add(anim, forKey: key)
if let v = contentLayer {
addAnimation(anim.copy() as! CAAnimation, for: key, to: v)
}
if let v = overlayLayer {
addAnimation(anim.copy() as! CAAnimation, for: key, to: v)
}
case "bounds.size": case "bounds.size":
guard let fs = (fromValue as? NSValue)?.cgSizeValue else { guard let fs = (fromValue as? NSValue)?.cgSizeValue else {
...@@ -266,26 +380,17 @@ extension MotionCoreAnimationViewContext { ...@@ -266,26 +380,17 @@ extension MotionCoreAnimationViewContext {
return 0 return 0
} }
// for the snapshotView(UIReplicantView): there is a setSize(view: snapshot, newSize: fs)
// subview(UIReplicantContentView) that is hosting the real snapshot image. uiViewBasedAnimate(duration: anim.duration, delay: beginTime - currentTime) { [weak self] in
// because we are using CAAnimations and not UIView animations, guard let `self` = self else {
// The snapshotView will not layout during animations. return
// we have to add two more animations to manually layout the content view. }
let fpn = NSValue(cgPoint: fs.center)
let tpn = NSValue(cgPoint: ts.center) self.setSize(view: self.snapshot, newSize: ts)
}
let a = getAnimation(key: "position", beginTime: 0, fromValue: fpn, toValue: tpn, ignoreArc: true)
a.beginTime = anim.beginTime
a.timingFunction = anim.timingFunction
a.duration = anim.duration
contentLayer?.add(a, forKey: "position")
contentLayer?.add(anim, forKey: key)
overlayLayer?.add(a, forKey: "position")
overlayLayer?.add(anim, forKey: key)
default: break default:
addAnimation(anim, for: key, to: snapshot.layer)
} }
} }
...@@ -295,26 +400,28 @@ extension MotionCoreAnimationViewContext { ...@@ -295,26 +400,28 @@ extension MotionCoreAnimationViewContext {
/** /**
Animates the contentLayer and overlayLayer with a given delay. Animates the contentLayer and overlayLayer with a given delay.
- Parameter delay: A TimeInterval. - Parameter delay: A TimeInterval.
- Parameter duration: A TimeInterval.
- Returns: A TimeInterval.
*/ */
fileprivate func animate(delay: TimeInterval) { func animate(delay: TimeInterval, duration: TimeInterval) -> TimeInterval {
if let v = targetState.timingFunction { for (layer, key, _) in animations {
timingFunction = v layer.removeAnimation(forKey: key)
} }
if let v = targetState.duration { if let tf = targetState.timingFunction {
duration = v timingFunction = tf
} }
let beginTime = currentTime + delay var timeUntilStop = duration
var finalDuration: TimeInterval = duration animations = []
for (key, (fromValue, toValue)) in transitionStates { for (key, (fromValue, toValue)) in transitionStates {
let neededTime = animate(key: key, beginTime: beginTime, fromValue: fromValue, toValue: toValue) let neededTime = animate(key: key, beginTime: currentTime + delay, duration: duration, fromValue: fromValue, toValue: toValue)
finalDuration = max(finalDuration, neededTime + delay) timeUntilStop = max(timeUntilStop, neededTime)
} }
duration = finalDuration return timeUntilStop + delay
} }
/** /**
...@@ -322,7 +429,7 @@ extension MotionCoreAnimationViewContext { ...@@ -322,7 +429,7 @@ extension MotionCoreAnimationViewContext {
- Parameter targetState state: A MotionTransitionState. - Parameter targetState state: A MotionTransitionState.
- Returns: A map of key paths to animation values. - Returns: A map of key paths to animation values.
*/ */
fileprivate func viewState(targetState ts: MotionTransitionState) -> [String: Any?] { func viewState(targetState ts: MotionTransitionState) -> [String: Any?] {
var ts = ts var ts = ts
var values = [String: Any?]() var values = [String: Any?]()
...@@ -409,19 +516,4 @@ extension MotionCoreAnimationViewContext { ...@@ -409,19 +516,4 @@ extension MotionCoreAnimationViewContext {
return values return values
} }
/**
Moves a layer's animation to a given elapsed time.
- Parameter layer: A CALayer.
- Parameter elapsedTime: A TimeInterval.
*/
fileprivate 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))
layer.removeAnimation(forKey: key)
layer.add(anim, forKey: key)
}
}
} }
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
import UIKit import UIKit
class BaseMotionAnimator: MotionAnimator { class MotionCoreAnimator: MotionAnimator {
weak public var motion: Motion! weak public var motion: Motion!
/// A reference to the MotionContext. /// A reference to the MotionContext.
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
import UIKit import UIKit
internal class MotionTransitionAnimator<T: MotionAnimatorViewContext>: BaseMotionAnimator, MotionHasInsertOrder { internal class MotionTransitionAnimator<T: MotionAnimatorViewContext>: MotionCoreAnimator, MotionHasInsertOrder {
/// An index of views to their corresponding animator context. /// An index of views to their corresponding animator context.
var viewToContexts = [UIView: T]() var viewToContexts = [UIView: T]()
...@@ -114,8 +114,10 @@ internal class MotionTransitionAnimator<T: MotionAnimatorViewContext>: BaseMotio ...@@ -114,8 +114,10 @@ internal class MotionTransitionAnimator<T: MotionAnimatorViewContext>: BaseMotio
var duration: TimeInterval = 0 var duration: TimeInterval = 0
for (_, v) in viewToContexts { for (_, v) in viewToContexts {
v.resume(at: elapsedTime, isReversed: isReversed) if v.targetState.duration == nil {
duration = max(duration, v.duration) v.duration = max(v.duration, v.snapshot.optimizedDuration(targetState: v.targetState) + elapsedTime)
}
duration = max(duration, v.resume(at: elapsedTime, isReversed: isReversed))
} }
return duration return duration
...@@ -144,11 +146,11 @@ extension MotionTransitionAnimator { ...@@ -144,11 +146,11 @@ extension MotionTransitionAnimator {
*/ */
fileprivate func animate(view: UIView, isAppearing: Bool) { fileprivate func animate(view: UIView, isAppearing: Bool) {
let s = context.snapshotView(for: view) let s = context.snapshotView(for: view)
let v = T(animator: self, snapshot: s, targetState: context[view]!) let v = T(animator: self, snapshot: s, targetState: context[view]!, isAppearing: isAppearing)
viewToContexts[view] = v viewToContexts[view] = v
v.startAnimations(isAppearing: isAppearing) v.startAnimations()
} }
} }
...@@ -29,6 +29,26 @@ import UIKit ...@@ -29,6 +29,26 @@ import UIKit
extension CALayer: CAAnimationDelegate {} extension CALayer: CAAnimationDelegate {}
internal extension CALayer { internal extension CALayer {
internal static var motionAddedAnimations: [(CALayer, String, CAAnimation)]? = {
let swizzling: (AnyClass, Selector, Selector) -> Void = { forClass, originalSelector, swizzledSelector in
if let originalMethod = class_getInstanceMethod(forClass, originalSelector), let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector) {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
swizzling(CALayer.self, #selector(add(_:forKey:)), #selector(motionAdd(anim:forKey:)))
return nil
}()
@objc
dynamic func motionAdd(anim: CAAnimation, forKey: String?) {
let copiedAnim = anim.copy() as! CAAnimation
copiedAnim.delegate = nil // having delegate resulted some weird animation behavior
CALayer.motionAddedAnimations?.append((self, forKey!, copiedAnim))
motionAdd(anim: anim, forKey: forKey)
}
/// Retrieves all currently running animations for the layer. /// Retrieves all currently running animations for the layer.
var animations: [(String, CAAnimation)] { var animations: [(String, CAAnimation)] {
guard let keys = animationKeys() else { guard let keys = animationKeys() else {
......
...@@ -187,6 +187,16 @@ public func /(left: CGPoint, right: CGPoint) -> CGPoint { ...@@ -187,6 +187,16 @@ public func /(left: CGPoint, right: CGPoint) -> CGPoint {
/** /**
A handler for the (/) operator. A handler for the (/) operator.
- Parameter left: A CGPoint.
- Parameter right: A CGSize.
- Returns: A CGPoint.
*/
public func /(left: CGPoint, right: CGSize) -> CGPoint {
return CGPoint(x: left.x / right.width, y: left.y / right.height)
}
/**
A handler for the (/) operator.
- Parameter left: A CGSize. - Parameter left: A CGSize.
- Parameter right: A CGSize. - Parameter right: A CGSize.
- Returns: A CGSize. - Returns: A CGSize.
......
...@@ -284,6 +284,17 @@ internal extension UIView { ...@@ -284,6 +284,17 @@ internal extension UIView {
} }
/** /**
Calculates the optimized duration for a view.
- Parameter targetState: A MotionTransitionState.
- Returns: A TimeInterval.
*/
func optimizedDuration(targetState: MotionTransitionState) -> TimeInterval {
return optimizedDuration(position: targetState.position,
size: targetState.size,
transform: targetState.transform)
}
/**
Takes a snapshot of a view usinag the UI graphics context. Takes a snapshot of a view usinag the UI graphics context.
- Returns: A UIView with an embedded UIImageView. - Returns: A UIView with an embedded UIImageView.
*/ */
......
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