CAAnimationUtils: added mediaTimingFunction to all methods

ListView: don't snap to overscroll bounds while tracking
This commit is contained in:
Peter 2019-04-23 18:04:15 +04:00
parent 6ae1baad94
commit 2bf1de59b6
4 changed files with 138 additions and 152 deletions

View File

@ -186,7 +186,7 @@ public extension CALayer {
self.add(animation, forKey: keyPath) self.add(animation, forKey: keyPath)
} }
public func animateAdditive(from: NSValue, to: NSValue, keyPath: String, key: String, timingFunction: String, duration: Double, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { public func animateAdditive(from: NSValue, to: NSValue, keyPath: String, key: String, timingFunction: String, mediaTimingFunction: CAMediaTimingFunction? = nil, duration: Double, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
let k = Float(UIView.animationDurationFactor()) let k = Float(UIView.animationDurationFactor())
var speed: Float = 1.0 var speed: Float = 1.0
if k != 0 && k != 1 { if k != 0 && k != 1 {
@ -197,7 +197,11 @@ public extension CALayer {
animation.fromValue = from animation.fromValue = from
animation.toValue = to animation.toValue = to
animation.duration = duration animation.duration = duration
animation.timingFunction = CAMediaTimingFunction(name: timingFunction) if let mediaTimingFunction = mediaTimingFunction {
animation.timingFunction = mediaTimingFunction
} else {
animation.timingFunction = CAMediaTimingFunction(name: timingFunction)
}
animation.isRemovedOnCompletion = removeOnCompletion animation.isRemovedOnCompletion = removeOnCompletion
animation.fillMode = kCAFillModeForwards animation.fillMode = kCAFillModeForwards
animation.speed = speed animation.speed = speed
@ -209,44 +213,44 @@ public extension CALayer {
self.add(animation, forKey: key) self.add(animation, forKey: key)
} }
public func animateAlpha(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) { public func animateAlpha(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) {
self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "opacity", timingFunction: timingFunction, duration: duration, delay: delay, removeOnCompletion: removeOnCompletion, completion: completion) self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "opacity", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, completion: completion)
} }
public func animateScale(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { public func animateScale(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale", timingFunction: timingFunction, duration: duration, delay: delay, removeOnCompletion: removeOnCompletion, completion: completion) self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, completion: completion)
} }
public func animateRotation(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { public func animateRotation(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.rotation.z", timingFunction: timingFunction, duration: duration, delay: delay, removeOnCompletion: removeOnCompletion, completion: completion) self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.rotation.z", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, completion: completion)
} }
func animatePosition(from: CGPoint, to: CGPoint, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { func animatePosition(from: CGPoint, to: CGPoint, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
if from == to && !force { if from == to && !force {
if let completion = completion { if let completion = completion {
completion(true) completion(true)
} }
return return
} }
self.animate(from: NSValue(cgPoint: from), to: NSValue(cgPoint: to), keyPath: "position", timingFunction: timingFunction, duration: duration, delay: delay, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) self.animate(from: NSValue(cgPoint: from), to: NSValue(cgPoint: to), keyPath: "position", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion)
} }
func animateBounds(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { func animateBounds(from: CGRect, to: CGRect, duration: Double, timingFunction: String, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
if from == to && !force { if from == to && !force {
if let completion = completion { if let completion = completion {
completion(true) completion(true)
} }
return return
} }
self.animate(from: NSValue(cgRect: from), to: NSValue(cgRect: to), keyPath: "bounds", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) self.animate(from: NSValue(cgRect: from), to: NSValue(cgRect: to), keyPath: "bounds", timingFunction: timingFunction, duration: duration, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion)
} }
public func animateBoundsOriginXAdditive(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { public func animateBoundsOriginXAdditive(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.x", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, additive: true, completion: completion) self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.x", timingFunction: timingFunction, duration: duration, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion)
} }
public func animateBoundsOriginYAdditive(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { public func animateBoundsOriginYAdditive(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, additive: true, completion: completion) self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", timingFunction: timingFunction, duration: duration, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion)
} }
public func animateBoundsOriginXAdditive(from: CGFloat, to: CGFloat, duration: Double, mediaTimingFunction: CAMediaTimingFunction) { public func animateBoundsOriginXAdditive(from: CGFloat, to: CGFloat, duration: Double, mediaTimingFunction: CAMediaTimingFunction) {
@ -261,7 +265,7 @@ public extension CALayer {
self.animateKeyframes(values: values.map { NSValue(cgPoint: $0) }, duration: duration, keyPath: "position") self.animateKeyframes(values: values.map { NSValue(cgPoint: $0) }, duration: duration, keyPath: "position")
} }
public func animateFrame(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { public func animateFrame(from: CGRect, to: CGRect, duration: Double, timingFunction: String, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
if from == to && !force { if from == to && !force {
if let completion = completion { if let completion = completion {
completion(true) completion(true)
@ -295,14 +299,14 @@ public extension CALayer {
toBounds = CGRect() toBounds = CGRect()
} }
self.animatePosition(from: fromPosition, to: toPosition, duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, force: force, completion: { value in self.animatePosition(from: fromPosition, to: toPosition, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, force: force, completion: { value in
if !value { if !value {
interrupted = true interrupted = true
} }
completedPosition = true completedPosition = true
partialCompletion() partialCompletion()
}) })
self.animateBounds(from: fromBounds, to: toBounds, duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, force: force, completion: { value in self.animateBounds(from: fromBounds, to: toBounds, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, force: force, completion: { value in
if !value { if !value {
interrupted = true interrupted = true
} }

View File

@ -9,6 +9,7 @@ import Foundation
public enum ContainedViewLayoutTransitionCurve { public enum ContainedViewLayoutTransitionCurve {
case easeInOut case easeInOut
case spring case spring
case custom(Float, Float, Float, Float)
} }
public extension ContainedViewLayoutTransitionCurve { public extension ContainedViewLayoutTransitionCurve {
@ -18,6 +19,19 @@ public extension ContainedViewLayoutTransitionCurve {
return kCAMediaTimingFunctionEaseInEaseOut return kCAMediaTimingFunctionEaseInEaseOut
case .spring: case .spring:
return kCAMediaTimingFunctionSpring return kCAMediaTimingFunctionSpring
case .custom:
return kCAMediaTimingFunctionEaseInEaseOut
}
}
var mediaTimingFunction: CAMediaTimingFunction? {
switch self {
case .easeInOut:
return nil
case .spring:
return nil
case let .custom(p1, p2, p3, p4):
return CAMediaTimingFunction(controlPoints: p1, p2, p3, p4)
} }
} }
@ -28,6 +42,8 @@ public extension ContainedViewLayoutTransitionCurve {
return [.curveEaseInOut] return [.curveEaseInOut]
case .spring: case .spring:
return UIViewAnimationOptions(rawValue: 7 << 16) return UIViewAnimationOptions(rawValue: 7 << 16)
case .custom:
return []
} }
} }
#endif #endif
@ -52,19 +68,19 @@ public extension ContainedViewLayoutTransition {
completion?(true) completion?(true)
} else { } else {
switch self { switch self {
case .immediate: case .immediate:
node.frame = frame node.frame = frame
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousFrame = node.frame
node.frame = frame
node.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in
if let completion = completion { if let completion = completion {
completion(result) completion(true)
} }
}) case let .animated(duration, curve):
let previousFrame = node.frame
node.frame = frame
node.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, force: force, completion: { result in
if let completion = completion {
completion(result)
}
})
} }
} }
} }
@ -82,7 +98,7 @@ public extension ContainedViewLayoutTransition {
case let .animated(duration, curve): case let .animated(duration, curve):
let previousBounds = node.bounds let previousBounds = node.bounds
node.bounds = bounds node.bounds = bounds
node.layer.animateBounds(from: previousBounds, to: bounds, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in node.layer.animateBounds(from: previousBounds, to: bounds, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, force: force, completion: { result in
if let completion = completion { if let completion = completion {
completion(result) completion(result)
} }
@ -104,7 +120,7 @@ public extension ContainedViewLayoutTransition {
case let .animated(duration, curve): case let .animated(duration, curve):
let previousBounds = layer.bounds let previousBounds = layer.bounds
layer.bounds = bounds layer.bounds = bounds
layer.animateBounds(from: previousBounds, to: bounds, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in layer.animateBounds(from: previousBounds, to: bounds, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, force: force, completion: { result in
if let completion = completion { if let completion = completion {
completion(result) completion(result)
} }
@ -126,7 +142,7 @@ public extension ContainedViewLayoutTransition {
case let .animated(duration, curve): case let .animated(duration, curve):
let previousPosition = node.position let previousPosition = node.position
node.position = position node.position = position
node.layer.animatePosition(from: previousPosition, to: position, duration: duration, timingFunction: curve.timingFunction, completion: { result in node.layer.animatePosition(from: previousPosition, to: position, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion { if let completion = completion {
completion(result) completion(result)
} }
@ -148,7 +164,7 @@ public extension ContainedViewLayoutTransition {
case let .animated(duration, curve): case let .animated(duration, curve):
let previousPosition = layer.position let previousPosition = layer.position
layer.position = position layer.position = position
layer.animatePosition(from: previousPosition, to: position, duration: duration, timingFunction: curve.timingFunction, completion: { result in layer.animatePosition(from: previousPosition, to: position, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion { if let completion = completion {
completion(result) completion(result)
} }
@ -164,7 +180,7 @@ public extension ContainedViewLayoutTransition {
completion(true) completion(true)
} }
case let .animated(duration, curve): case let .animated(duration, curve):
node.layer.animatePosition(from: position, to: node.position, duration: duration, timingFunction: curve.timingFunction, completion: { result in node.layer.animatePosition(from: position, to: node.position, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion { if let completion = completion {
completion(result) completion(result)
} }
@ -182,7 +198,7 @@ public extension ContainedViewLayoutTransition {
completion(true) completion(true)
} }
case let .animated(duration, curve): case let .animated(duration, curve):
node.layer.animatePosition(from: node.position, to: position, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, completion: { result in node.layer.animatePosition(from: node.position, to: position, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, completion: { result in
if let completion = completion { if let completion = completion {
completion(result) completion(result)
} }
@ -198,7 +214,7 @@ public extension ContainedViewLayoutTransition {
completion(true) completion(true)
} }
case let .animated(duration, curve): case let .animated(duration, curve):
node.layer.animateFrame(from: frame, to: toFrame ?? node.layer.frame, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: { result in node.layer.animateFrame(from: frame, to: toFrame ?? node.layer.frame, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: { result in
if let completion = completion { if let completion = completion {
completion(result) completion(result)
} }
@ -208,32 +224,25 @@ public extension ContainedViewLayoutTransition {
func animateBounds(layer: CALayer, from bounds: CGRect, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { func animateBounds(layer: CALayer, from bounds: CGRect, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
switch self { switch self {
case .immediate: case .immediate:
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
layer.animateBounds(from: bounds, to: layer.bounds, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, completion: { result in
if let completion = completion { if let completion = completion {
completion(result) completion(true)
} }
}) case let .animated(duration, curve):
layer.animateBounds(from: bounds, to: layer.bounds, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, completion: { result in
if let completion = completion {
completion(result)
}
})
} }
} }
func animateOffsetAdditive(node: ASDisplayNode, offset: CGFloat) { func animateOffsetAdditive(node: ASDisplayNode, offset: CGFloat) {
switch self { switch self {
case .immediate: case .immediate:
break break
case let .animated(duration, curve): case let .animated(duration, curve):
let timingFunction: String node.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction)
switch curve {
case .easeInOut:
timingFunction = kCAMediaTimingFunctionEaseInEaseOut
case .spring:
timingFunction = kCAMediaTimingFunctionSpring
}
node.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: timingFunction)
} }
} }
@ -242,32 +251,18 @@ public extension ContainedViewLayoutTransition {
case .immediate: case .immediate:
break break
case let .animated(duration, curve): case let .animated(duration, curve):
let timingFunction: String node.layer.animateBoundsOriginXAdditive(from: offset, to: 0.0, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction)
switch curve {
case .easeInOut:
timingFunction = kCAMediaTimingFunctionEaseInEaseOut
case .spring:
timingFunction = kCAMediaTimingFunctionSpring
}
node.layer.animateBoundsOriginXAdditive(from: offset, to: 0.0, duration: duration, timingFunction: timingFunction)
} }
} }
func animateOffsetAdditive(layer: CALayer, offset: CGFloat, completion: (() -> Void)? = nil) { func animateOffsetAdditive(layer: CALayer, offset: CGFloat, completion: (() -> Void)? = nil) {
switch self { switch self {
case .immediate: case .immediate:
completion?()
case let .animated(duration, curve):
let timingFunction: String
switch curve {
case .easeInOut:
timingFunction = kCAMediaTimingFunctionEaseInEaseOut
case .spring:
timingFunction = kCAMediaTimingFunctionSpring
}
layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: timingFunction, completion: { _ in
completion?() completion?()
}) case let .animated(duration, curve):
layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { _ in
completion?()
})
} }
} }
@ -276,14 +271,7 @@ public extension ContainedViewLayoutTransition {
case .immediate: case .immediate:
break break
case let .animated(duration, curve): case let .animated(duration, curve):
let timingFunction: String node.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion)
switch curve {
case .easeInOut:
timingFunction = kCAMediaTimingFunctionEaseInEaseOut
case .spring:
timingFunction = kCAMediaTimingFunctionSpring
}
node.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion)
} }
} }
@ -292,32 +280,18 @@ public extension ContainedViewLayoutTransition {
case .immediate: case .immediate:
break break
case let .animated(duration, curve): case let .animated(duration, curve):
let timingFunction: String layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion)
switch curve {
case .easeInOut:
timingFunction = kCAMediaTimingFunctionEaseInEaseOut
case .spring:
timingFunction = kCAMediaTimingFunctionSpring
}
layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion)
} }
} }
func animatePositionAdditive(node: ASDisplayNode, offset: CGPoint, removeOnCompletion: Bool = true, completion: (() -> Void)? = nil) { func animatePositionAdditive(node: ASDisplayNode, offset: CGPoint, removeOnCompletion: Bool = true, completion: (() -> Void)? = nil) {
switch self { switch self {
case .immediate: case .immediate:
break break
case let .animated(duration, curve): case let .animated(duration, curve):
let timingFunction: String node.layer.animatePosition(from: offset, to: CGPoint(), duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: { _ in
switch curve { completion?()
case .easeInOut: })
timingFunction = kCAMediaTimingFunctionEaseInEaseOut
case .spring:
timingFunction = kCAMediaTimingFunctionSpring
}
node.layer.animatePosition(from: offset, to: CGPoint(), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: { _ in
completion?()
})
} }
} }
@ -326,14 +300,7 @@ public extension ContainedViewLayoutTransition {
case .immediate: case .immediate:
break break
case let .animated(duration, curve): case let .animated(duration, curve):
let timingFunction: String layer.animatePosition(from: offset, to: toOffset, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: { _ in
switch curve {
case .easeInOut:
timingFunction = kCAMediaTimingFunctionEaseInEaseOut
case .spring:
timingFunction = kCAMediaTimingFunctionSpring
}
layer.animatePosition(from: offset, to: toOffset, duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: { _ in
completion?() completion?()
}) })
} }
@ -352,7 +319,7 @@ public extension ContainedViewLayoutTransition {
case let .animated(duration, curve): case let .animated(duration, curve):
let previousFrame = view.frame let previousFrame = view.frame
view.frame = frame view.frame = frame
view.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in view.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, force: force, completion: { result in
if let completion = completion { if let completion = completion {
completion(result) completion(result)
} }
@ -374,7 +341,7 @@ public extension ContainedViewLayoutTransition {
case let .animated(duration, curve): case let .animated(duration, curve):
let previousFrame = layer.frame let previousFrame = layer.frame
layer.frame = frame layer.frame = frame
layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, completion: { result in layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion { if let completion = completion {
completion(result) completion(result)
} }
@ -400,7 +367,7 @@ public extension ContainedViewLayoutTransition {
case let .animated(duration, curve): case let .animated(duration, curve):
let previousAlpha = node.alpha let previousAlpha = node.alpha
node.alpha = alpha node.alpha = alpha
node.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration, timingFunction: curve.timingFunction, completion: { result in node.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion { if let completion = completion {
completion(result) completion(result)
} }
@ -425,7 +392,7 @@ public extension ContainedViewLayoutTransition {
case let .animated(duration, curve): case let .animated(duration, curve):
let previousAlpha = layer.opacity let previousAlpha = layer.opacity
layer.opacity = Float(alpha) layer.opacity = Float(alpha)
layer.animateAlpha(from: CGFloat(previousAlpha), to: alpha, duration: duration, timingFunction: curve.timingFunction, completion: { result in layer.animateAlpha(from: CGFloat(previousAlpha), to: alpha, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion { if let completion = completion {
completion(result) completion(result)
} }
@ -450,7 +417,7 @@ public extension ContainedViewLayoutTransition {
case let .animated(duration, curve): case let .animated(duration, curve):
if let nodeColor = node.backgroundColor { if let nodeColor = node.backgroundColor {
node.backgroundColor = color node.backgroundColor = color
node.layer.animate(from: nodeColor.cgColor, to: color.cgColor, keyPath: "backgroundColor", timingFunction: curve.timingFunction, duration: duration, completion: { result in node.layer.animate(from: nodeColor.cgColor, to: color.cgColor, keyPath: "backgroundColor", timingFunction: curve.timingFunction, duration: duration, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion { if let completion = completion {
completion(result) completion(result)
} }
@ -481,7 +448,7 @@ public extension ContainedViewLayoutTransition {
case let .animated(duration, curve): case let .animated(duration, curve):
let previousCornerRadius = node.cornerRadius let previousCornerRadius = node.cornerRadius
node.cornerRadius = cornerRadius node.cornerRadius = cornerRadius
node.layer.animate(from: NSNumber(value: Float(previousCornerRadius)), to: NSNumber(value: Float(cornerRadius)), keyPath: "cornerRadius", timingFunction: curve.timingFunction, duration: duration, completion: { result in node.layer.animate(from: NSNumber(value: Float(previousCornerRadius)), to: NSNumber(value: Float(cornerRadius)), keyPath: "cornerRadius", timingFunction: curve.timingFunction, duration: duration, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion { if let completion = completion {
completion(result) completion(result)
} }
@ -505,7 +472,7 @@ public extension ContainedViewLayoutTransition {
completion(true) completion(true)
} }
case let .animated(duration, curve): case let .animated(duration, curve):
node.layer.animateScale(from: fromScale, to: currentScale, duration: duration, timingFunction: curve.timingFunction, completion: { result in node.layer.animateScale(from: fromScale, to: currentScale, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion { if let completion = completion {
completion(result) completion(result)
} }
@ -531,7 +498,7 @@ public extension ContainedViewLayoutTransition {
} }
case let .animated(duration, curve): case let .animated(duration, curve):
node.layer.transform = CATransform3DMakeScale(scale, scale, 1.0) node.layer.transform = CATransform3DMakeScale(scale, scale, 1.0)
node.layer.animateScale(from: currentScale, to: scale, duration: duration, timingFunction: curve.timingFunction, completion: { result in node.layer.animateScale(from: currentScale, to: scale, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion { if let completion = completion {
completion(result) completion(result)
} }
@ -557,7 +524,7 @@ public extension ContainedViewLayoutTransition {
} }
case let .animated(duration, curve): case let .animated(duration, curve):
layer.transform = CATransform3DMakeScale(scale, scale, 1.0) layer.transform = CATransform3DMakeScale(scale, scale, 1.0)
layer.animateScale(from: currentScale, to: scale, duration: duration, timingFunction: curve.timingFunction, completion: { result in layer.animateScale(from: currentScale, to: scale, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion { if let completion = completion {
completion(result) completion(result)
} }
@ -587,7 +554,7 @@ public extension ContainedViewLayoutTransition {
} }
case let .animated(duration, curve): case let .animated(duration, curve):
node.layer.sublayerTransform = CATransform3DMakeScale(scale, scale, 1.0) node.layer.sublayerTransform = CATransform3DMakeScale(scale, scale, 1.0)
node.layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: node.layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: false, completion: { node.layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: node.layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: false, completion: {
result in result in
if let completion = completion { if let completion = completion {
completion(result) completion(result)
@ -615,19 +582,19 @@ public extension ContainedViewLayoutTransition {
} }
switch self { switch self {
case .immediate: case .immediate:
node.layer.sublayerTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0) node.layer.sublayerTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0)
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
node.layer.sublayerTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0)
node.layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: node.layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: false, completion: {
result in
if let completion = completion { if let completion = completion {
completion(result) completion(true)
} }
}) case let .animated(duration, curve):
node.layer.sublayerTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0)
node.layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: node.layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: false, completion: {
result in
if let completion = completion {
completion(result)
}
})
} }
} }
@ -637,7 +604,7 @@ public extension ContainedViewLayoutTransition {
return return
} }
let t = node.layer.transform let t = node.layer.transform
var currentScaleX = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13)) let currentScaleX = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
var currentScaleY = sqrt((t.m21 * t.m21) + (t.m22 * t.m22) + (t.m23 * t.m23)) var currentScaleY = sqrt((t.m21 * t.m21) + (t.m22 * t.m22) + (t.m23 * t.m23))
if t.m22 < 0.0 { if t.m22 < 0.0 {
currentScaleY = -currentScaleY currentScaleY = -currentScaleY
@ -650,19 +617,19 @@ public extension ContainedViewLayoutTransition {
} }
switch self { switch self {
case .immediate: case .immediate:
node.layer.transform = CATransform3DMakeScale(scale.x, scale.y, 1.0) node.layer.transform = CATransform3DMakeScale(scale.x, scale.y, 1.0)
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
node.layer.transform = CATransform3DMakeScale(scale.x, scale.y, 1.0)
node.layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: node.layer.transform), keyPath: "transform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: false, completion: {
result in
if let completion = completion { if let completion = completion {
completion(result) completion(true)
} }
}) case let .animated(duration, curve):
node.layer.transform = CATransform3DMakeScale(scale.x, scale.y, 1.0)
node.layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: node.layer.transform), keyPath: "transform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: false, completion: {
result in
if let completion = completion {
completion(result)
}
})
} }
} }
@ -684,7 +651,7 @@ public extension ContainedViewLayoutTransition {
} }
case let .animated(duration, curve): case let .animated(duration, curve):
layer.sublayerTransform = CATransform3DMakeTranslation(offset.x, offset.y, 0.0) layer.sublayerTransform = CATransform3DMakeTranslation(offset.x, offset.y, 0.0)
layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: false, completion: { layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: false, completion: {
result in result in
if let completion = completion { if let completion = completion {
completion(result) completion(result)

View File

@ -884,6 +884,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
} }
} }
if self.isTracking {
offset = 0.0
}
if abs(offset) > CGFloat.ulpOfOne { if abs(offset) > CGFloat.ulpOfOne {
for itemNode in self.itemNodes { for itemNode in self.itemNodes {
var frame = itemNode.frame var frame = itemNode.frame
@ -2581,7 +2585,12 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
} }
} }
self.updateAccessoryNodes(animated: animated, currentTimestamp: timestamp, leftInset: listInsets.left, rightInset: listInsets.right) var accessoryNodesTransition: ContainedViewLayoutTransition = .immediate
if let scrollToItem = scrollToItem, scrollToItem.animated {
accessoryNodesTransition = .animated(duration: 0.3, curve: .easeInOut)
}
self.updateAccessoryNodes(transition: accessoryNodesTransition, currentTimestamp: timestamp, leftInset: listInsets.left, rightInset: listInsets.right)
if let highlightedItemNode = highlightedItemNode { if let highlightedItemNode = highlightedItemNode {
if highlightedItemNode.index != self.highlightedItemIndex { if highlightedItemNode.index != self.highlightedItemIndex {
@ -2918,6 +2927,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
switch curve { switch curve {
case .spring: case .spring:
transition.0.animateOffsetAdditive(node: headerNode, offset: offset) transition.0.animateOffsetAdditive(node: headerNode, offset: offset)
case let .custom(p1, p2, p3, p4):
headerNode.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, mediaTimingFunction: CAMediaTimingFunction(controlPoints: p1, p2, p3, p4))
case .easeInOut: case .easeInOut:
if transition.1 { if transition.1 {
headerNode.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, mediaTimingFunction: CAMediaTimingFunction(controlPoints: 0.33, 0.52, 0.25, 0.99)) headerNode.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, mediaTimingFunction: CAMediaTimingFunction(controlPoints: 0.33, 0.52, 0.25, 0.99))
@ -3023,7 +3034,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
} }
} }
private func updateAccessoryNodes(animated: Bool, currentTimestamp: Double, leftInset: CGFloat, rightInset: CGFloat) { private func updateAccessoryNodes(transition: ContainedViewLayoutTransition, currentTimestamp: Double, leftInset: CGFloat, rightInset: CGFloat) {
var totalVisibleHeight: CGFloat = 0.0 var totalVisibleHeight: CGFloat = 0.0
var index = -1 var index = -1
let count = self.itemNodes.count let count = self.itemNodes.count
@ -3235,13 +3246,13 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
if indicatorHeight >= visibleHeightWithoutIndicatorInsets { if indicatorHeight >= visibleHeightWithoutIndicatorInsets {
verticalScrollIndicator.isHidden = true verticalScrollIndicator.isHidden = true
verticalScrollIndicator.frame = indicatorFrame transition.updateFrame(node: verticalScrollIndicator, frame: indicatorFrame)
} else { } else {
if verticalScrollIndicator.isHidden { if verticalScrollIndicator.isHidden {
verticalScrollIndicator.isHidden = false verticalScrollIndicator.isHidden = false
verticalScrollIndicator.frame = indicatorFrame transition.updateFrame(node: verticalScrollIndicator, frame: indicatorFrame)
} else { } else {
verticalScrollIndicator.frame = indicatorFrame transition.updateFrame(node: verticalScrollIndicator, frame: indicatorFrame)
} }
} }
} else { } else {

View File

@ -13,6 +13,10 @@ open class NavigationBarContentNode: ASDisplayNode {
return self.nominalHeight return self.nominalHeight
} }
open var clippedHeight: CGFloat {
return self.nominalHeight
}
open var nominalHeight: CGFloat { open var nominalHeight: CGFloat {
return 44.0 return 44.0
} }