no message

This commit is contained in:
Peter 2016-06-14 18:12:05 +03:00
parent c3d1e3cfd3
commit 572a74e278
6 changed files with 136 additions and 49 deletions

View File

@ -17,6 +17,7 @@ import UIKit
}
private let completionKey = "CAAnimationUtils_completion"
private let springKey = "CAAnimationUtilsSpringCurve"
public extension CAAnimation {
public var completion: (Bool -> Void)? {
@ -38,27 +39,50 @@ public extension CAAnimation {
public extension CALayer {
public func animate(from from: NSValue, to: NSValue, keyPath: String, timingFunction: String, duration: NSTimeInterval, removeOnCompletion: Bool = true, completion: (Bool -> Void)? = nil) {
let k = Float(UIView.animationDurationFactor())
var speed: Float = 1.0
if k != 0 && k != 1 {
speed = Float(1.0) / k
if timingFunction == springKey {
let animation = CASpringAnimation(keyPath: keyPath)
animation.fromValue = from
animation.toValue = to
animation.damping = 500.0
animation.stiffness = 1000.0
animation.mass = 3.0
animation.duration = animation.settlingDuration
animation.removedOnCompletion = removeOnCompletion
animation.fillMode = kCAFillModeForwards
if let completion = completion {
animation.delegate = CALayerAnimationDelegate(completion: completion)
}
let k = Float(UIView.animationDurationFactor())
var speed: Float = 1.0
if k != 0 && k != 1 {
speed = Float(1.0) / k
}
animation.speed = speed * Float(animation.duration / duration)
self.addAnimation(animation, forKey: keyPath)
} else {
let k = Float(UIView.animationDurationFactor())
var speed: Float = 1.0
if k != 0 && k != 1 {
speed = Float(1.0) / k
}
let animation = CABasicAnimation(keyPath: keyPath)
animation.fromValue = from
animation.toValue = to
animation.duration = duration
animation.timingFunction = CAMediaTimingFunction(name: timingFunction)
animation.removedOnCompletion = removeOnCompletion
animation.fillMode = kCAFillModeForwards
animation.speed = speed
if let completion = completion {
animation.delegate = CALayerAnimationDelegate(completion: completion)
}
self.addAnimation(animation, forKey: keyPath)
}
let animation = CABasicAnimation(keyPath: keyPath)
animation.fromValue = from
animation.toValue = to
animation.duration = duration
animation.timingFunction = CAMediaTimingFunction(name: timingFunction)
animation.removedOnCompletion = removeOnCompletion
animation.fillMode = kCAFillModeForwards
animation.speed = speed
if let completion = completion {
animation.delegate = CALayerAnimationDelegate(completion: completion)
}
self.addAnimation(animation, forKey: keyPath)
//self.setValue(to, forKey: keyPath)
}
public func animateAdditive(from from: NSValue, to: NSValue, keyPath: String, key: String, timingFunction: String, duration: NSTimeInterval, removeOnCompletion: Bool = true, completion: (Bool -> Void)? = nil) {
@ -93,10 +117,28 @@ public extension CALayer {
}
internal func animatePosition(from from: CGPoint, to: CGPoint, duration: NSTimeInterval, completion: (Bool -> Void)? = nil) {
self.animate(from: NSValue(CGPoint: from), to: NSValue(CGPoint: to), keyPath: "position", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true, completion: completion)
if from == to {
return
}
self.animate(from: NSValue(CGPoint: from), to: NSValue(CGPoint: to), keyPath: "position", timingFunction: springKey, duration: duration, removeOnCompletion: true, completion: completion)
}
internal func animateBounds(from from: CGRect, to: CGRect, duration: NSTimeInterval, completion: (Bool -> Void)? = nil) {
if from == to {
return
}
self.animate(from: NSValue(CGRect: from), to: NSValue(CGRect: to), keyPath: "bounds", timingFunction: springKey, duration: duration, removeOnCompletion: true, completion: completion)
}
public func animateBoundsOriginYAdditive(from from: CGFloat, to: CGFloat, duration: NSTimeInterval) {
self.animateAdditive(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", key: "boundsOriginYAdditive", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true)
}
public func animateFrame(from from: CGRect, to: CGRect, duration: NSTimeInterval, spring: Bool = false, completion: (Bool -> Void)? = nil) {
if from == to {
return
}
self.animatePosition(from: CGPoint(x: from.midX, y: from.midY), to: CGPoint(x: to.midX, y: to.midY), duration: duration, completion: nil)
self.animateBounds(from: CGRect(origin: self.bounds.origin, size: from.size), to: CGRect(origin: self.bounds.origin, size: to.size), duration: duration, completion: completion)
}
}

View File

@ -1269,7 +1269,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate {
})
}
private func nodeForItem(synchronous: Bool, item: ListViewItem, previousNode: ListViewItemNode?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, width: CGFloat, completion: (ListViewItemNode, ListViewItemNodeLayout, () -> Void) -> Void) {
private func nodeForItem(synchronous: Bool, item: ListViewItem, previousNode: ListViewItemNode?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, width: CGFloat, updateAnimation: ListViewItemUpdateAnimation, completion: (ListViewItemNode, ListViewItemNodeLayout, () -> Void) -> Void) {
if let previousNode = previousNode {
item.updateNode({ f in
if synchronous {
@ -1277,7 +1277,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate {
} else {
self.async(f)
}
}, node: previousNode, width: width, previousItem: previousItem, nextItem: nextItem, completion: { (layout, apply) in
}, node: previousNode, width: width, previousItem: previousItem, nextItem: nextItem, animation: updateAnimation, completion: { (layout, apply) in
if NSThread.isMainThread() {
if synchronous {
completion(previousNode, layout, {
@ -1637,7 +1637,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate {
} else {
self.async(f)
}
}, node: referenceNode, width: state.visibleSize.width, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: index == self.items.count - 1 ? nil : self.items[index + 1], completion: { layout, apply in
}, node: referenceNode, width: state.visibleSize.width, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: index == self.items.count - 1 ? nil : self.items[index + 1], animation: .None, completion: { layout, apply in
var updatedState = state
var updatedOperations = operations
@ -1670,6 +1670,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate {
var previousNodes = inputPreviousNodes
var operations = inputOperations
let completion = inputCompletion
let updateAnimation: ListViewItemUpdateAnimation = animated ? .System(duration: insertionAnimationDuration) : .None
while true {
if self.items.count == 0 {
@ -1694,7 +1695,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate {
let index = insertionItemIndexAndDirection.0
let threadId = pthread_self()
var tailRecurse = false
self.nodeForItem(synchronous, item: self.items[index], previousNode: previousNodes[index], index: index, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: self.items.count == index + 1 ? nil : self.items[index + 1], width: state.visibleSize.width, completion: { (node, layout, apply) in
self.nodeForItem(synchronous, item: self.items[index], previousNode: previousNodes[index], index: index, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: self.items.count == index + 1 ? nil : self.items[index + 1], width: state.visibleSize.width, updateAnimation: updateAnimation, completion: { (node, layout, apply) in
if pthread_equal(pthread_self(), threadId) != 0 && !tailRecurse {
tailRecurse = true
@ -1749,6 +1750,10 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate {
let previousApparentHeight = node.apparentHeight
let previousInsets = node.insets
if node.wantsScrollDynamics && previousFrame != nil {
assert(true)
}
node.contentSize = layout.contentSize
node.insets = layout.insets
node.apparentHeight = animated ? 0.0 : layout.size.height
@ -1787,6 +1792,16 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate {
nextNode.removeApparentHeightAnimation()
takenAnimation = true
if abs(layout.size.height - previousApparentHeight) > CGFloat(FLT_EPSILON) {
node.addApparentHeightAnimation(layout.size.height, duration: insertionAnimationDuration, beginAt: timestamp, update: { [weak node] progress in
if let node = node {
node.animateFrameTransition(progress)
}
})
node.transitionOffset = previousApparentHeight - layout.size.height
node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration, beginAt: timestamp)
}
}
}
}
@ -2285,7 +2300,9 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate {
updatedAccessoryItemNodeOrigin.y += updatedParentOrigin.y
updatedAccessoryItemNodeOrigin.y -= itemNode.bounds.origin.y
nextAccessoryItemNode.animateTransitionOffset(CGPoint(x: 0.0, y: updatedAccessoryItemNodeOrigin.y - previousAccessoryItemNodeOrigin.y), beginAt: currentTimestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor(), curve: listViewAnimationCurveSystem)
let deltaHeight = itemNode.frame.size.height - nextItemNode.frame.size.height
nextAccessoryItemNode.animateTransitionOffset(CGPoint(x: 0.0, y: updatedAccessoryItemNodeOrigin.y - previousAccessoryItemNodeOrigin.y - deltaHeight), beginAt: currentTimestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor(), curve: listViewAnimationCurveSystem)
}
} else {
break

View File

@ -12,7 +12,7 @@ public class ListViewAccessoryItemNode: ASDisplayNode {
final func animateTransitionOffset(from: CGPoint, beginAt: Double, duration: Double, curve: CGFloat -> CGFloat) {
self.transitionOffset = from
self.transitionOffsetAnimation = ListViewAnimation(from: from, to: CGPoint(), duration: duration, curve: curve, beginAt: beginAt, update: { [weak self] currentValue in
self.transitionOffsetAnimation = ListViewAnimation(from: from, to: CGPoint(), duration: duration, curve: curve, beginAt: beginAt, update: { [weak self] _, currentValue in
if let strongSelf = self {
strongSelf.transitionOffset = currentValue
}

View File

@ -43,6 +43,16 @@ extension UIEdgeInsets: Interpolatable {
}
}
extension CGRect: Interpolatable {
public static func interpolator() -> (Interpolatable, Interpolatable, CGFloat) -> Interpolatable {
return { from, to, t -> Interpolatable in
let fromValue = from as! CGRect
let toValue = to as! CGRect
return floorToPixels(CGRect(x: toValue.origin.x * t + fromValue.origin.x * (1.0 - t), y: toValue.origin.y * t + fromValue.origin.y * (1.0 - t), width: toValue.size.width * t + fromValue.size.width * (1.0 - t), height: toValue.size.height * t + fromValue.size.height * (1.0 - t)))
}
}
}
extension CGPoint: Interpolatable {
public static func interpolator() -> (Interpolatable, Interpolatable, CGFloat) -> Interpolatable {
return { from, to, t -> Interpolatable in
@ -55,11 +65,10 @@ extension CGPoint: Interpolatable {
private let springAnimationIn: CASpringAnimation = {
let animation = CASpringAnimation()
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animation.duration = 0.6
animation.damping = 500.0
animation.stiffness = 1000.0
animation.mass = 3.0
animation.duration = animation.settlingDuration
return animation
}()
@ -87,18 +96,18 @@ public final class ListViewAnimation {
let startTime: Double
private let curve: CGFloat -> CGFloat
private let interpolator: (Interpolatable, Interpolatable, CGFloat) -> Interpolatable
private let update: Interpolatable -> Void
private let update: (CGFloat, Interpolatable) -> Void
private let completed: Bool -> Void
public init<T: Interpolatable>(from: T, to: T, duration: Double, curve: CGFloat -> CGFloat, beginAt: Double, update: T -> Void, completed: Bool -> Void = { _ in }) {
public init<T: Interpolatable>(from: T, to: T, duration: Double, curve: CGFloat -> CGFloat, beginAt: Double, update: (CGFloat, T) -> Void, completed: Bool -> Void = { _ in }) {
self.from = from
self.to = to
self.duration = duration
self.curve = curve
self.startTime = beginAt
self.interpolator = T.interpolator()
self.update = { value in
update(value as! T)
self.update = { progress, value in
update(progress, value as! T)
}
self.completed = completed
}
@ -116,21 +125,28 @@ public final class ListViewAnimation {
self.completed(false)
}
private func valueAt(timestamp: Double) -> Interpolatable {
if timestamp < self.startTime {
private func valueAt(t: CGFloat) -> Interpolatable {
if t <= 0.0 {
return self.from
}
let t = CGFloat((timestamp - self.startTime) / self.duration)
if t >= 1.0 {
} else if t >= 1.0 {
return self.to
} else {
return self.interpolator(self.from, self.to, self.curve(t))
return self.interpolator(self.from, self.to, t)
}
}
public func applyAt(timestamp: Double) {
self.update(self.valueAt(timestamp))
var t = CGFloat((timestamp - self.startTime) / self.duration)
let ct: CGFloat
if t <= 0.0 + CGFloat(FLT_EPSILON) {
t = 0.0
ct = 0.0
} else if t >= 1.0 - CGFloat(FLT_EPSILON) {
t = 1.0
ct = 1.0
} else {
ct = self.curve(t)
}
self.update(ct, self.valueAt(ct))
}
}

View File

@ -1,9 +1,14 @@
import Foundation
import SwiftSignalKit
public enum ListViewItemUpdateAnimation {
case None
case System(duration: Double)
}
public protocol ListViewItem {
func nodeConfiguredForWidth(async: (() -> Void) -> Void, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: (ListViewItemNode, () -> Void) -> Void)
func updateNode(async: (() -> Void) -> Void, node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: (ListViewItemNodeLayout, () -> Void) -> Void)
func updateNode(async: (() -> Void) -> Void, node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: (ListViewItemNodeLayout, () -> Void) -> Void)
var accessoryItem: ListViewAccessoryItem? { get }
var headerAccessoryItem: ListViewAccessoryItem? { get }
@ -28,7 +33,7 @@ public extension ListViewItem {
func selected() {
}
func updateNode(async: (() -> Void) -> Void, node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: (ListViewItemNodeLayout, () -> Void) -> Void) {
func updateNode(async: (() -> Void) -> Void, node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: (ListViewItemNodeLayout, () -> Void) -> Void) {
completion(ListViewItemNodeLayout(contentSize: node.contentSize, insets: node.insets), {})
}
}

View File

@ -334,7 +334,7 @@ public class ListViewItemNode: ASDisplayNode {
}
public func addInsetsAnimationToValue(value: UIEdgeInsets, duration: Double, beginAt: Double) {
let animation = ListViewAnimation(from: self.insets, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] currentValue in
let animation = ListViewAnimation(from: self.insets, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] _, currentValue in
if let strongSelf = self {
strongSelf.insets = currentValue
}
@ -342,10 +342,13 @@ public class ListViewItemNode: ASDisplayNode {
self.setAnimationForKey("insets", animation: animation)
}
public func addApparentHeightAnimation(value: CGFloat, duration: Double, beginAt: Double) {
let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] currentValue in
public func addApparentHeightAnimation(value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat) -> Void)? = nil) {
let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] progress, currentValue in
if let strongSelf = self {
strongSelf.apparentHeight = currentValue
if let update = update {
update(progress)
}
}
})
self.setAnimationForKey("apparentHeight", animation: animation)
@ -358,7 +361,7 @@ public class ListViewItemNode: ASDisplayNode {
duration = 0.0
}
let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] currentValue in
let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] _, currentValue in
if let strongSelf = self {
strongSelf.apparentHeight = currentValue
}
@ -373,7 +376,7 @@ public class ListViewItemNode: ASDisplayNode {
}
public func addTransitionOffsetAnimation(value: CGFloat, duration: Double, beginAt: Double) {
let animation = ListViewAnimation(from: self.transitionOffset, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] currentValue in
let animation = ListViewAnimation(from: self.transitionOffset, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] _, currentValue in
if let strongSelf = self {
strongSelf.transitionOffset = currentValue
}
@ -392,4 +395,8 @@ public class ListViewItemNode: ASDisplayNode {
public func setupGestures() {
}
public func animateFrameTransition(progress: CGFloat) {
}
}