Commit 2caa4eb1 by Daniel Dahan

initial clean up for MotionCoreAnimationViewContext completed

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