Commit 2caa4eb1 by Daniel Dahan

initial clean up for MotionCoreAnimationViewContext completed

parent 97383774
...@@ -43,13 +43,11 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext { ...@@ -43,13 +43,11 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext {
/// Layer which holds the overlay. /// Layer which holds the overlay.
fileprivate var overlayLayer: CALayer? fileprivate var overlayLayer: CALayer?
/** override func clean() {
Determines whether a view can be animated. super.clean()
- Parameter view: A UIView. overlayLayer = nil
- Parameter state: A MotionTargetState. }
- Parameter isAppearing: A boolean indicating whether or not the
view is going to appear.
*/
override class func canAnimate(view: UIView, state: MotionTargetState, isAppearing: Bool) -> Bool { override class func canAnimate(view: UIView, state: MotionTargetState, isAppearing: Bool) -> Bool {
return nil != state.position || return nil != state.position ||
nil != state.size || nil != state.size ||
...@@ -69,11 +67,73 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext { ...@@ -69,11 +67,73 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext {
state.forceAnimate state.forceAnimate
} }
override func apply(state: MotionTargetState) {
let ts = viewState(targetState: state)
for (key, targetValue) in ts {
if nil == transitionStates[key] {
let current = currentValue(for: key)
transitionStates[key] = (current, current)
}
animate(key: key, beginTime: 0, fromValue: targetValue, toValue: targetValue)
}
}
override func resume(elapsedTime: TimeInterval, isReversed: Bool) {
for (key, (fromValue, toValue)) in transitionStates {
transitionStates[key] = (currentValue(for: key), !isReversed ? toValue : fromValue)
}
targetState.duration = isReversed ? elapsedTime - targetState.delay : duration - elapsedTime
animate(delay: max(0, targetState.delay - elapsedTime))
}
override func seek(to elapsedTime: TimeInterval) {
seek(layer: snapshot.layer, elapsedTime: elapsedTime)
if let v = contentLayer {
seek(layer: v, elapsedTime: elapsedTime)
}
if let v = overlayLayer {
seek(layer: v, elapsedTime: elapsedTime)
}
}
override func startAnimations(isAppearing: Bool) {
if let beginState = targetState.beginState?.state {
let appeared = viewState(targetState: beginState)
for (k, v) in appeared {
snapshot.layer.setValue(v, forKeyPath: k)
}
if let (k, v) = beginState.overlay {
let overlay = getOverlayLayer()
overlay.backgroundColor = k
overlay.opacity = Float(v)
}
}
let disappeared = viewState(targetState: targetState)
for (k, v) in disappeared {
let isAppearingState = currentValue(for: k)
let toValue = isAppearing ? isAppearingState : v
let fromValue = !isAppearing ? isAppearingState : v
transitionStates[k] = (fromValue, toValue)
}
animate(delay: targetState.delay)
}
}
extension MotionCoreAnimationViewContext {
/** /**
Lazy loads the overlay layer. Lazy loads the overlay layer.
- Returns: A CALayer. - Returns: A CALayer.
*/ */
func getOverlayLayer() -> CALayer { fileprivate func getOverlayLayer() -> CALayer {
if nil == overlayLayer { if nil == overlayLayer {
overlayLayer = CALayer() overlayLayer = CALayer()
overlayLayer!.frame = snapshot.bounds overlayLayer!.frame = snapshot.bounds
...@@ -89,7 +149,7 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext { ...@@ -89,7 +149,7 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext {
- Parameter for key: A String. - Parameter for key: A String.
- Returns: An optional String. - Returns: An optional String.
*/ */
func overlayKey(for key: String) -> String? { fileprivate func overlayKey(for key: String) -> String? {
guard key.hasPrefix("overlay.") else { guard key.hasPrefix("overlay.") else {
return nil return nil
} }
...@@ -104,7 +164,7 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext { ...@@ -104,7 +164,7 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext {
- Parameter for key: A String. - Parameter for key: A String.
- Returns: An optional Any value. - Returns: An optional Any value.
*/ */
func currentValue(for key: String) -> Any? { fileprivate 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?.value(forKeyPath: key)
} }
...@@ -124,7 +184,7 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext { ...@@ -124,7 +184,7 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext {
- 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.
*/ */
func getAnimation(key: String, beginTime: TimeInterval, fromValue: Any?, toValue: Any?, ignoreArc: Bool = false) -> CAPropertyAnimation { fileprivate func getAnimation(key: String, beginTime: 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
...@@ -182,39 +242,49 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext { ...@@ -182,39 +242,49 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext {
- Parameter toValue: A optional Any value. - Parameter toValue: A optional Any value.
- Returns: A TimeInterval. - Returns: A TimeInterval.
*/ */
func animate(key: String, beginTime: TimeInterval, fromValue: Any?, toValue: Any?) -> TimeInterval { @discardableResult
fileprivate func animate(key: String, beginTime: TimeInterval, fromValue: Any?, toValue: Any?) -> TimeInterval {
let anim = getAnimation(key: key, beginTime:beginTime, fromValue: fromValue, toValue: toValue) let anim = getAnimation(key: key, beginTime:beginTime, fromValue: fromValue, toValue: toValue)
if let overlayKey = overlayKey(for: key) { if let overlayKey = overlayKey(for: key) {
getOverlayLayer().add(anim, forKey: overlayKey) getOverlayLayer().add(anim, forKey: overlayKey)
} 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) contentLayer?.add(anim, forKey: key)
overlayLayer?.add(anim, forKey: key) overlayLayer?.add(anim, forKey: key)
case "bounds.size": case "bounds.size":
let fromSize = (fromValue as? NSValue)!.cgSizeValue guard let fs = (fromValue as? NSValue)?.cgSizeValue else {
let toSize = (toValue as? NSValue)!.cgSizeValue return 0
}
guard let ts = (toValue as? NSValue)?.cgSizeValue else {
return 0
}
// for the snapshotView(UIReplicantView): there is a // for the snapshotView(UIReplicantView): there is a
// subview(UIReplicantContentView) that is hosting the real snapshot image. // subview(UIReplicantContentView) that is hosting the real snapshot image.
// because we are using CAAnimations and not UIView animations, // because we are using CAAnimations and not UIView animations,
// The snapshotView will not layout during animations. // The snapshotView will not layout during animations.
// we have to add two more animations to manually layout the content view. // we have to add two more animations to manually layout the content view.
let fromPosn = NSValue(cgPoint:fromSize.center) let fpn = NSValue(cgPoint: fs.center)
let toPosn = NSValue(cgPoint:toSize.center) let tpn = NSValue(cgPoint: ts.center)
let positionAnim = getAnimation(key: "position", beginTime:0, fromValue: fromPosn, toValue: toPosn, ignoreArc: true) let a = getAnimation(key: "position", beginTime: 0, fromValue: fpn, toValue: tpn, ignoreArc: true)
positionAnim.beginTime = anim.beginTime a.beginTime = anim.beginTime
positionAnim.timingFunction = anim.timingFunction a.timingFunction = anim.timingFunction
positionAnim.duration = anim.duration a.duration = anim.duration
contentLayer?.add(positionAnim, forKey: "position") contentLayer?.add(a, forKey: "position")
contentLayer?.add(anim, forKey: key) contentLayer?.add(anim, forKey: key)
overlayLayer?.add(positionAnim, forKey: "position") overlayLayer?.add(a, forKey: "position")
overlayLayer?.add(anim, forKey: key) overlayLayer?.add(anim, forKey: key)
default: break default: break
} }
} }
...@@ -222,15 +292,23 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext { ...@@ -222,15 +292,23 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext {
return anim.duration + anim.beginTime - beginTime return anim.duration + anim.beginTime - beginTime
} }
func animate(delay: TimeInterval) { /**
if let tf = targetState.timingFunction { Animates the contentLayer and overlayLayer with a given delay.
timingFunction = tf - Parameter delay: A TimeInterval.
*/
fileprivate func animate(delay: TimeInterval) {
if let v = targetState.timingFunction {
timingFunction = v
} }
duration = targetState.duration! if let v = targetState.duration {
duration = v
}
let beginTime = currentTime + delay let beginTime = currentTime + delay
var finalDuration: TimeInterval = duration var finalDuration: TimeInterval = duration
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: beginTime, fromValue: fromValue, toValue: toValue)
finalDuration = max(finalDuration, neededTime + delay) finalDuration = max(finalDuration, neededTime + delay)
...@@ -240,110 +318,99 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext { ...@@ -240,110 +318,99 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext {
} }
/** /**
- Returns: a CALayer [keyPath:value] map for animation Constructs a map of key paths to animation values.
- Parameter targetState state: A MotionTargetState.
- Returns: A map of key paths to animation values.
*/ */
func viewState(targetState: MotionTargetState) -> [String: Any?] { fileprivate func viewState(targetState ts: MotionTargetState) -> [String: Any?] {
var targetState = targetState var ts = ts
var rtn = [String: Any?]() var values = [String: Any?]()
if let size = targetState.size { if let size = ts.size {
if targetState.useScaleBasedSizeChange ?? self.targetState.useScaleBasedSizeChange ?? false { if ts.useScaleBasedSizeChange ?? targetState.useScaleBasedSizeChange ?? false {
let currentSize = snapshot.bounds.size let currentSize = snapshot.bounds.size
targetState.append(.scale(x:size.width / currentSize.width, ts.append(.scale(x: size.width / currentSize.width, y: size.height / currentSize.height))
y:size.height / currentSize.height))
} else { } else {
rtn["bounds.size"] = NSValue(cgSize:size) values["bounds.size"] = NSValue(cgSize:size)
} }
} }
if let position = targetState.position {
rtn["position"] = NSValue(cgPoint:position)
}
if let opacity = targetState.opacity, !(snapshot is UIVisualEffectView) {
rtn["opacity"] = NSNumber(value: opacity)
}
if let cornerRadius = targetState.cornerRadius {
rtn["cornerRadius"] = NSNumber(value: cornerRadius.native)
}
if let backgroundColor = targetState.backgroundColor {
rtn["backgroundColor"] = backgroundColor
}
if let zPosition = targetState.zPosition {
rtn["zPosition"] = NSNumber(value: zPosition.native)
}
if let borderWidth = targetState.borderWidth { if let position = ts.position {
rtn["borderWidth"] = NSNumber(value: borderWidth.native) values["position"] = NSValue(cgPoint:position)
} }
if let borderColor = targetState.borderColor {
rtn["borderColor"] = borderColor if let opacity = ts.opacity, !(snapshot is UIVisualEffectView) {
values["opacity"] = NSNumber(value: opacity)
} }
if let masksToBounds = targetState.masksToBounds {
rtn["masksToBounds"] = masksToBounds if let cornerRadius = ts.cornerRadius {
values["cornerRadius"] = NSNumber(value: cornerRadius.native)
} }
if targetState.displayShadow { if let backgroundColor = ts.backgroundColor {
if let shadowColor = targetState.shadowColor { values["backgroundColor"] = backgroundColor
rtn["shadowColor"] = shadowColor
} }
if let shadowRadius = targetState.shadowRadius {
rtn["shadowRadius"] = NSNumber(value: shadowRadius.native) if let zPosition = ts.zPosition {
values["zPosition"] = NSNumber(value: zPosition.native)
} }
if let shadowOpacity = targetState.shadowOpacity {
rtn["shadowOpacity"] = NSNumber(value: shadowOpacity) if let borderWidth = ts.borderWidth {
values["borderWidth"] = NSNumber(value: borderWidth.native)
} }
if let shadowPath = targetState.shadowPath {
rtn["shadowPath"] = shadowPath if let borderColor = ts.borderColor {
values["borderColor"] = borderColor
} }
if let shadowOffset = targetState.shadowOffset {
rtn["shadowOffset"] = NSValue(cgSize: shadowOffset) if let masksToBounds = ts.masksToBounds {
values["masksToBounds"] = masksToBounds
} }
if ts.displayShadow {
if let shadowColor = ts.shadowColor {
values["shadowColor"] = shadowColor
} }
if let contentsRect = targetState.contentsRect { if let shadowRadius = ts.shadowRadius {
rtn["contentsRect"] = NSValue(cgRect: contentsRect) values["shadowRadius"] = NSNumber(value: shadowRadius.native)
} }
if let contentsScale = targetState.contentsScale { if let shadowOpacity = ts.shadowOpacity {
rtn["contentsScale"] = NSNumber(value: contentsScale.native) values["shadowOpacity"] = NSNumber(value: shadowOpacity)
} }
if let transform = targetState.transform { if let shadowPath = ts.shadowPath {
rtn["transform"] = NSValue(caTransform3D: transform) values["shadowPath"] = shadowPath
} }
if let (color, opacity) = targetState.overlay { if let shadowOffset = ts.shadowOffset {
rtn["overlay.backgroundColor"] = color values["shadowOffset"] = NSValue(cgSize: shadowOffset)
rtn["overlay.opacity"] = NSNumber(value: opacity.native)
} }
return rtn
} }
override func apply(state: MotionTargetState) { if let contentsRect = ts.contentsRect {
let targetState = viewState(targetState: state) values["contentsRect"] = NSValue(cgRect: contentsRect)
for (key, targetValue) in targetState {
if self.transitionStates[key] == nil {
let current = currentValue(for: key)
self.transitionStates[key] = (current, current)
}
_ = animate(key: key, beginTime: 0, fromValue: targetValue, toValue: targetValue)
} }
if let contentsScale = ts.contentsScale {
values["contentsScale"] = NSNumber(value: contentsScale.native)
} }
override func resume(elapsedTime: TimeInterval, isReversed: Bool) { if let transform = ts.transform {
for (key, (fromValue, toValue)) in transitionStates { values["transform"] = NSValue(caTransform3D: transform)
let realToValue = !isReversed ? toValue : fromValue
let realFromValue = currentValue(for: key)
transitionStates[key] = (realFromValue, realToValue)
} }
// we need to update the duration to reflect current state if let (color, opacity) = ts.overlay {
targetState.duration = isReversed ? elapsedTime - targetState.delay : duration - elapsedTime values["overlay.backgroundColor"] = color
values["overlay.opacity"] = NSNumber(value: opacity.native)
}
let realDelay = max(0, targetState.delay - elapsedTime) return values
animate(delay: realDelay)
} }
func seek(layer: CALayer, elapsedTime: TimeInterval) { fileprivate func seek(layer: CALayer, elapsedTime: TimeInterval) {
let timeOffset = elapsedTime - targetState.delay let timeOffset = elapsedTime - targetState.delay
for (key, anim) in layer.animations { for (key, anim) in layer.animations {
anim.speed = 0 anim.speed = 0
...@@ -352,44 +419,4 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext { ...@@ -352,44 +419,4 @@ internal class MotionCoreAnimationViewContext: MotionAnimatorViewContext {
layer.add(anim, forKey: key) layer.add(anim, forKey: key)
} }
} }
override func seek(to elapsedTime: TimeInterval) {
seek(layer:snapshot.layer, elapsedTime:elapsedTime)
if let contentLayer = contentLayer {
seek(layer:contentLayer, elapsedTime:elapsedTime)
}
if let overlayLayer = overlayLayer {
seek(layer: overlayLayer, elapsedTime: elapsedTime)
}
}
override func clean() {
super.clean()
overlayLayer = nil
}
override func startAnimations(isAppearing: Bool) {
if let beginState = targetState.beginState?.state {
let appeared = viewState(targetState: beginState)
for (key, value) in appeared {
snapshot.layer.setValue(value, forKeyPath: key)
}
if let (color, opacity) = beginState.overlay {
let overlay = getOverlayLayer()
overlay.backgroundColor = color
overlay.opacity = Float(opacity)
}
}
let disappeared = viewState(targetState: targetState)
for (key, disappearedState) in disappeared {
let isAppearingState = currentValue(for: key)
let toValue = isAppearing ? isAppearingState : disappearedState
let fromValue = !isAppearing ? isAppearingState : disappearedState
transitionStates[key] = (fromValue, toValue)
}
animate(delay: targetState.delay)
}
} }
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