Swiftgram/submodules/Display/Source/ContainedViewLayoutTransition.swift
2023-12-06 00:50:36 +04:00

2326 lines
96 KiB
Swift

import Foundation
import UIKit
import AsyncDisplayKit
import ObjCRuntimeUtils
extension CGRect {
var center: CGPoint {
return CGPoint(x: self.midX, y: self.midY)
}
}
public enum ContainedViewLayoutTransitionCurve: Equatable, Hashable {
case linear
case easeInOut
case spring
case customSpring(damping: CGFloat, initialVelocity: CGFloat)
case custom(Float, Float, Float, Float)
public static var slide: ContainedViewLayoutTransitionCurve {
return .custom(0.33, 0.52, 0.25, 0.99)
}
}
public extension ContainedViewLayoutTransitionCurve {
func solve(at offset: CGFloat) -> CGFloat {
switch self {
case .linear:
return offset
case .easeInOut:
return listViewAnimationCurveEaseInOut(offset)
case .spring:
return listViewAnimationCurveSystem(offset)
case .customSpring:
return listViewAnimationCurveSystem(offset)
case let .custom(c1x, c1y, c2x, c2y):
return bezierPoint(CGFloat(c1x), CGFloat(c1y), CGFloat(c2x), CGFloat(c2y), offset)
}
}
}
public extension ContainedViewLayoutTransitionCurve {
var timingFunction: String {
switch self {
case .linear:
return CAMediaTimingFunctionName.linear.rawValue
case .easeInOut:
return CAMediaTimingFunctionName.easeInEaseOut.rawValue
case .spring:
return kCAMediaTimingFunctionSpring
case let .customSpring(damping, initialVelocity):
return "\(kCAMediaTimingFunctionCustomSpringPrefix)_\(damping)_\(initialVelocity)"
case .custom:
return CAMediaTimingFunctionName.easeInEaseOut.rawValue
}
}
var mediaTimingFunction: CAMediaTimingFunction? {
switch self {
case .linear:
return nil
case .easeInOut:
return nil
case .spring:
return nil
case .customSpring:
return nil
case let .custom(p1, p2, p3, p4):
return CAMediaTimingFunction(controlPoints: p1, p2, p3, p4)
}
}
var viewAnimationOptions: UIView.AnimationOptions {
switch self {
case .linear:
return [.curveLinear]
case .easeInOut:
return [.curveEaseInOut]
case .spring:
return UIView.AnimationOptions(rawValue: 7 << 16)
case .customSpring:
return UIView.AnimationOptions(rawValue: 7 << 16)
case .custom:
return []
}
}
}
public enum ContainedViewLayoutTransition {
case immediate
case animated(duration: Double, curve: ContainedViewLayoutTransitionCurve)
public var isAnimated: Bool {
if case .immediate = self {
return false
} else {
return true
}
}
}
public extension CGRect {
var ensuredValid: CGRect {
if !ASIsCGRectValidForLayout(CGRect(origin: CGPoint(), size: self.size)) {
return CGRect()
}
if !ASIsCGPositionValidForLayout(self.origin) {
return CGRect()
}
return self
}
}
private extension CALayer {
func animate(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, delay: Double, curve: ContainedViewLayoutTransitionCurve, removeOnCompletion: Bool, additive: Bool, completion: ((Bool) -> Void)? = nil) {
let timingFunction: String
let mediaTimingFunction: CAMediaTimingFunction?
switch curve {
case .spring:
timingFunction = kCAMediaTimingFunctionSpring
mediaTimingFunction = nil
default:
timingFunction = CAMediaTimingFunctionName.easeInEaseOut.rawValue
mediaTimingFunction = curve.mediaTimingFunction
}
self.animate(
from: from,
to: to,
keyPath: keyPath,
timingFunction: timingFunction,
duration: duration,
delay: delay,
mediaTimingFunction: mediaTimingFunction,
removeOnCompletion: removeOnCompletion,
additive: additive,
completion: completion
)
}
}
public extension ContainedViewLayoutTransition {
func animation() -> CABasicAnimation? {
switch self {
case .immediate:
return nil
case let .animated(duration, curve):
let animation = CALayer().makeAnimation(from: 0.0 as NSNumber, to: 1.0 as NSNumber, keyPath: "position", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: false, additive: false, completion: { _ in })
return animation as? CABasicAnimation
}
}
func updateFrame(node: ASDisplayNode, frame: CGRect, force: Bool = false, beginWithCurrentState: Bool = false, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) {
if frame.origin.x.isNaN {
return
}
if frame.origin.y.isNaN {
return
}
if frame.size.width.isNaN {
return
}
if frame.size.width < 0.0 {
return
}
if frame.size.height.isNaN {
return
}
if frame.size.height < 0.0 {
return
}
if !ASIsCGRectValidForLayout(CGRect(origin: CGPoint(), size: frame.size)) {
return
}
if !ASIsCGPositionValidForLayout(frame.origin) {
return
}
if node.frame.equalTo(frame) && !force {
completion?(true)
} else {
switch self {
case .immediate:
node.layer.removeAnimation(forKey: "position")
node.layer.removeAnimation(forKey: "bounds")
node.frame = frame
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousFrame: CGRect
if beginWithCurrentState, (node.layer.animation(forKey: "position") != nil || node.layer.animation(forKey: "bounds") != nil), let presentation = node.layer.presentation() {
previousFrame = presentation.frame
} else {
previousFrame = node.frame
}
node.frame = frame
node.layer.animateFrame(from: previousFrame, to: frame, duration: duration, delay: delay, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, force: force, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
}
func updateFrameAsPositionAndBounds(node: ASDisplayNode, frame: CGRect, force: Bool = false, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
if node.frame.equalTo(frame) && !force {
completion?(true)
} else {
switch self {
case .immediate:
node.layer.removeAnimation(forKey: "position")
node.layer.removeAnimation(forKey: "bounds")
node.position = frame.center
node.bounds = CGRect(origin: CGPoint(), size: frame.size)
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousPosition: CGPoint
let previousBounds: CGRect
if beginWithCurrentState, let presentation = node.layer.presentation() {
previousPosition = presentation.position
previousBounds = presentation.bounds
} else {
previousPosition = node.position
previousBounds = node.bounds
}
node.position = frame.center
node.bounds = CGRect(origin: CGPoint(), size: frame.size)
node.layer.animateFrame(from:
CGRect(origin: CGPoint(x: previousPosition.x - previousBounds.width / 2.0, y: previousPosition.y - previousBounds.height / 2.0), size: previousBounds.size), to: frame, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, force: force, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
}
func updateFrameAsPositionAndBounds(layer: CALayer, frame: CGRect, force: Bool = false, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
if layer.frame.equalTo(frame) && !force {
completion?(true)
} else {
switch self {
case .immediate:
layer.removeAnimation(forKey: "position")
layer.removeAnimation(forKey: "bounds")
layer.position = frame.center
layer.bounds = CGRect(origin: CGPoint(), size: frame.size)
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousPosition: CGPoint
let previousBounds: CGRect
if beginWithCurrentState, let presentation = layer.presentation() {
previousPosition = presentation.position
previousBounds = presentation.bounds
} else {
previousPosition = layer.position
previousBounds = layer.bounds
}
layer.position = frame.center
layer.bounds = CGRect(origin: CGPoint(), size: frame.size)
layer.animateFrame(from:
CGRect(origin: CGPoint(x: previousPosition.x - previousBounds.width / 2.0, y: previousPosition.y - previousBounds.height / 2.0), size: previousBounds.size), to: frame, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, force: force, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
}
func updateFrameAdditive(node: ASDisplayNode, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
if node.frame.equalTo(frame) && !force {
completion?(true)
} else {
switch self {
case .immediate:
node.frame = frame
if let completion = completion {
completion(true)
}
case .animated:
let previousFrame = node.frame
node.frame = frame
self.animatePositionAdditive(node: node, offset: CGPoint(x: previousFrame.minX - frame.minX, y: previousFrame.minY - frame.minY))
}
}
}
func updateFrameAdditive(view: UIView, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
if view.frame.equalTo(frame) && !force {
completion?(true)
} else {
switch self {
case .immediate:
view.frame = frame
if let completion = completion {
completion(true)
}
case .animated:
let previousFrame = view.frame
view.frame = frame
self.animatePositionAdditive(layer: view.layer, offset: CGPoint(x: previousFrame.minX - frame.minX, y: previousFrame.minY - frame.minY))
}
}
}
func updateFrameAdditiveToCenter(node: ASDisplayNode, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
if node.frame.equalTo(frame) && !force {
completion?(true)
} else {
switch self {
case .immediate:
node.position = frame.center
node.bounds = CGRect(origin: node.bounds.origin, size: frame.size)
if let completion = completion {
completion(true)
}
case .animated:
let previousCenter = node.frame.center
node.position = frame.center
node.bounds = CGRect(origin: node.bounds.origin, size: frame.size)
self.animatePositionAdditive(node: node, offset: CGPoint(x: previousCenter.x - frame.midX, y: previousCenter.y - frame.midY))
}
}
}
func updateFrameAdditiveToCenter(view: UIView, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
if view.frame.equalTo(frame) && !force {
completion?(true)
} else {
switch self {
case .immediate:
view.center = frame.center
view.bounds = CGRect(origin: view.bounds.origin, size: frame.size)
if let completion = completion {
completion(true)
}
case .animated:
let previousCenter = view.frame.center
view.center = frame.center
view.bounds = CGRect(origin: view.bounds.origin, size: frame.size)
self.animatePositionAdditive(layer: view.layer, offset: CGPoint(x: previousCenter.x - frame.midX, y: previousCenter.y - frame.midY))
}
}
}
func updateBounds(node: ASDisplayNode, bounds: CGRect, force: Bool = false, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
if node.bounds.equalTo(bounds) && !force {
completion?(true)
} else {
switch self {
case .immediate:
node.layer.removeAnimation(forKey: "bounds")
node.bounds = bounds
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousBounds: CGRect
if beginWithCurrentState, node.layer.animation(forKey: "bounds") != nil, let presentation = node.layer.presentation() {
previousBounds = presentation.bounds
} else {
previousBounds = node.bounds
}
node.bounds = bounds
node.layer.animateBounds(from: previousBounds, to: bounds, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, force: force, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
}
func updateBounds(layer: CALayer, bounds: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
if layer.bounds.equalTo(bounds) && !force {
completion?(true)
} else {
switch self {
case .immediate:
layer.removeAnimation(forKey: "bounds")
layer.bounds = bounds
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousBounds = layer.bounds
layer.bounds = bounds
layer.animateBounds(from: previousBounds, to: bounds, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, force: force, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
}
func updatePosition(node: ASDisplayNode, position: CGPoint, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
if node.position.equalTo(position) {
completion?(true)
} else {
switch self {
case .immediate:
node.layer.removeAnimation(forKey: "position")
node.position = position
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousPosition: CGPoint
if beginWithCurrentState, node.layer.animation(forKey: "position") != nil, let presentation = node.layer.presentation() {
previousPosition = presentation.position
} else {
previousPosition = node.position
}
node.position = position
node.layer.animatePosition(from: previousPosition, to: position, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
}
func updatePosition(layer: CALayer, position: CGPoint, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
if layer.position.equalTo(position) && !force {
completion?(true)
} else {
switch self {
case .immediate:
layer.removeAnimation(forKey: "position")
layer.position = position
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousPosition = layer.position
layer.position = position
layer.animatePosition(from: previousPosition, to: position, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
}
func updateAnchorPoint(layer: CALayer, anchorPoint: CGPoint, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
if layer.anchorPoint.equalTo(anchorPoint) && !force {
completion?(true)
} else {
switch self {
case .immediate:
layer.removeAnimation(forKey: "anchorPoint")
layer.anchorPoint = anchorPoint
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousAnchorPoint = layer.anchorPoint
layer.anchorPoint = anchorPoint
layer.animateAnchorPoint(from: previousAnchorPoint, to: anchorPoint, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
}
func animatePosition(layer: CALayer, from fromValue: CGPoint, to toValue: CGPoint, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
switch self {
case .immediate:
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
layer.animatePosition(from: fromValue, to: toValue, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
func animatePosition(node: ASDisplayNode, from position: CGPoint, completion: ((Bool) -> Void)? = nil) {
switch self {
case .immediate:
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
node.layer.animatePosition(from: position, to: node.position, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
func animatePosition(node: ASDisplayNode, to position: CGPoint, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
if !additive && node.position.equalTo(position) {
completion?(true)
} else {
switch self {
case .immediate:
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
node.layer.animatePosition(from: additive ? CGPoint() : node.position, to: position, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
}
func animatePositionWithKeyframes(node: ASDisplayNode, keyframes: [CGPoint], removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
self.animatePositionWithKeyframes(layer: node.layer, keyframes: keyframes, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion)
}
func animatePositionWithKeyframes(layer: CALayer, keyframes: [CGPoint], removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
switch self {
case .immediate:
completion?(true)
case let .animated(duration, curve):
layer.animateKeyframes(values: keyframes.map(NSValue.init(cgPoint:)), duration: duration, keyPath: "position", timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, completion: { value in
completion?(value)
})
}
}
func animateFrame(node: ASDisplayNode, from frame: CGRect, to toFrame: CGRect? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
switch self {
case .immediate:
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
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 {
completion(result)
}
})
}
}
func animateFrame(layer: CALayer, from frame: CGRect, to toFrame: CGRect? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
switch self {
case .immediate:
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
layer.animateFrame(from: frame, to: toFrame ?? layer.frame, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
func animateBounds(layer: CALayer, from bounds: CGRect, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
switch self {
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, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
func animateWidthAdditive(layer: CALayer, value: CGFloat, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
switch self {
case .immediate:
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
layer.animateWidth(from: value, to: 0.0, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
func animateHeightAdditive(layer: CALayer, value: CGFloat, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
switch self {
case .immediate:
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
layer.animateHeight(from: value, to: 0.0, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
func animateOffsetAdditive(node: ASDisplayNode, offset: CGFloat) {
switch self {
case .immediate:
break
case let .animated(duration, curve):
node.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction)
}
}
func animateHorizontalOffsetAdditive(node: ASDisplayNode, offset: CGFloat, completion: (() -> Void)? = nil) {
switch self {
case .immediate:
break
case let .animated(duration, curve):
node.layer.animateBoundsOriginXAdditive(from: offset, to: 0.0, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { _ in
completion?()
})
}
}
func animateHorizontalOffsetAdditive(layer: CALayer, offset: CGFloat, completion: (() -> Void)? = nil) {
switch self {
case .immediate:
break
case let .animated(duration, curve):
layer.animateBoundsOriginXAdditive(from: offset, to: 0.0, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { _ in
completion?()
})
}
}
func animateOffsetAdditive(layer: CALayer, offset: CGFloat, completion: (() -> Void)? = nil) {
switch self {
case .immediate:
completion?()
case let .animated(duration, curve):
layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { _ in
completion?()
})
}
}
func animatePositionAdditive(node: ASDisplayNode, offset: CGFloat, removeOnCompletion: Bool = true, completion: @escaping (Bool) -> Void) {
switch self {
case .immediate:
completion(true)
case let .animated(duration, curve):
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)
}
}
func animatePositionAdditive(layer: CALayer, offset: CGFloat, delay: Double = 0.0, removeOnCompletion: Bool = true, completion: @escaping (Bool) -> Void) {
switch self {
case .immediate:
completion(true)
case let .animated(duration, curve):
layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, delay: delay, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion)
}
}
func animatePositionAdditive(node: ASDisplayNode, offset: CGPoint, delay: Double = 0.0, removeOnCompletion: Bool = true, completion: (() -> Void)? = nil) {
switch self {
case .immediate:
completion?()
case let .animated(duration, curve):
node.layer.animatePosition(from: offset, to: CGPoint(), duration: duration, delay: delay, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: { _ in
completion?()
})
}
}
func animatePositionAdditive(layer: CALayer, offset: CGPoint, to toOffset: CGPoint = CGPoint(), removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
switch self {
case .immediate:
completion?(true)
case let .animated(duration, curve):
layer.animatePosition(from: offset, to: toOffset, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: { result in
completion?(result)
})
}
}
func animateContentsRectPositionAdditive(layer: CALayer, offset: CGPoint, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
switch self {
case .immediate:
completion?(true)
case let .animated(duration, curve):
layer.animate(from: NSValue(cgPoint: offset), to: NSValue(cgPoint: CGPoint()), keyPath: "contentsRect.origin", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion)
}
}
func updateFrame(view: UIView, frame: CGRect, force: Bool = false, beginWithCurrentState: Bool = false, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) {
if frame.origin.x.isNaN {
return
}
if frame.origin.y.isNaN {
return
}
if frame.size.width.isNaN {
return
}
if frame.size.width < 0.0 {
return
}
if frame.size.height.isNaN {
return
}
if frame.size.height < 0.0 {
return
}
if !ASIsCGRectValidForLayout(CGRect(origin: CGPoint(), size: frame.size)) {
return
}
if !ASIsCGPositionValidForLayout(frame.origin) {
return
}
if view.frame.equalTo(frame) && !force {
completion?(true)
} else {
switch self {
case .immediate:
//view.layer.removeAnimation(forKey: "position")
//view.layer.removeAnimation(forKey: "bounds")
view.frame = frame
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousFrame: CGRect
if beginWithCurrentState, (view.layer.animation(forKey: "position") != nil || view.layer.animation(forKey: "bounds") != nil), let presentation = view.layer.presentation() {
previousFrame = presentation.frame
} else {
previousFrame = view.frame
}
view.frame = frame
view.layer.animateFrame(from: previousFrame, to: frame, duration: duration, delay: delay, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, force: force, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
}
func updateFrame(layer: CALayer, frame: CGRect, beginWithCurrentState: Bool = false, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) {
if layer.frame.equalTo(frame) {
completion?(true)
} else {
switch self {
case .immediate:
layer.removeAnimation(forKey: "position")
layer.removeAnimation(forKey: "bounds")
if let view = layer.delegate as? UIView {
view.frame = frame
} else {
layer.frame = frame
}
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousFrame: CGRect
if beginWithCurrentState, (layer.animation(forKey: "position") != nil || layer.animation(forKey: "bounds") != nil), let presentation = layer.presentation() {
previousFrame = presentation.frame
} else {
previousFrame = layer.frame
}
layer.frame = frame
layer.animateFrame(from: previousFrame, to: frame, duration: duration, delay: delay, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
}
func updateAlpha(node: ASDisplayNode, alpha: CGFloat, beginWithCurrentState: Bool = false, force: Bool = false, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) {
if node.alpha.isEqual(to: alpha) && !force {
if let completion = completion {
completion(true)
}
return
}
switch self {
case .immediate:
node.alpha = alpha
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousAlpha: CGFloat
if beginWithCurrentState, let presentation = node.layer.presentation() {
previousAlpha = CGFloat(presentation.opacity)
} else {
previousAlpha = node.alpha
}
node.alpha = alpha
node.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration, delay: delay, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
func updateAlpha(layer: CALayer, alpha: CGFloat, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
if layer.opacity.isEqual(to: Float(alpha)) {
if let completion = completion {
completion(true)
}
return
}
switch self {
case .immediate:
layer.opacity = Float(alpha)
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousAlpha: Float
if beginWithCurrentState, let presentation = layer.presentation() {
previousAlpha = presentation.opacity
} else {
previousAlpha = layer.opacity
}
layer.opacity = Float(alpha)
layer.animateAlpha(from: CGFloat(previousAlpha), to: alpha, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
func updateBackgroundColor(node: ASDisplayNode, color: UIColor, completion: ((Bool) -> Void)? = nil) {
if let nodeColor = node.backgroundColor, nodeColor.isEqual(color) {
if let completion = completion {
completion(true)
}
return
}
switch self {
case .immediate:
node.backgroundColor = color
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
if let nodeColor = node.backgroundColor {
node.backgroundColor = color
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 {
completion(result)
}
})
} else {
node.backgroundColor = color
if let completion = completion {
completion(true)
}
}
}
}
func updateBackgroundColor(layer: CALayer, color: UIColor, completion: ((Bool) -> Void)? = nil) {
if let nodeColor = layer.backgroundColor, nodeColor == color.cgColor {
if let completion = completion {
completion(true)
}
return
}
switch self {
case .immediate:
layer.backgroundColor = color.cgColor
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
if let nodeColor = layer.backgroundColor {
layer.backgroundColor = color.cgColor
layer.animate(from: nodeColor, to: color.cgColor, keyPath: "backgroundColor", timingFunction: curve.timingFunction, duration: duration, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion {
completion(result)
}
})
} else {
layer.backgroundColor = color.cgColor
if let completion = completion {
completion(true)
}
}
}
}
func updateCornerRadius(node: ASDisplayNode, cornerRadius: CGFloat, completion: ((Bool) -> Void)? = nil) {
if node.cornerRadius.isEqual(to: cornerRadius) {
if let completion = completion {
completion(true)
}
return
}
switch self {
case .immediate:
node.layer.removeAnimation(forKey: "cornerRadius")
node.cornerRadius = cornerRadius
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousCornerRadius = node.cornerRadius
node.cornerRadius = cornerRadius
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 {
completion(result)
}
})
}
}
func updateCornerRadius(layer: CALayer, cornerRadius: CGFloat, completion: ((Bool) -> Void)? = nil) {
if layer.cornerRadius.isEqual(to: cornerRadius) {
if let completion = completion {
completion(true)
}
return
}
switch self {
case .immediate:
layer.removeAnimation(forKey: "cornerRadius")
layer.cornerRadius = cornerRadius
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousCornerRadius = layer.cornerRadius
layer.cornerRadius = cornerRadius
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 {
completion(result)
}
})
}
}
func updateTintColor(layer: CALayer, color: UIColor, completion: ((Bool) -> Void)? = nil) {
if let current = layer.layerTintColor, current == color.cgColor {
completion?(true)
return
}
switch self {
case .immediate:
layer.layerTintColor = color.cgColor
completion?(true)
case let .animated(duration, curve):
let previousColor: CGColor = layer.layerTintColor ?? UIColor.clear.cgColor
layer.layerTintColor = color.cgColor
layer.animate(
from: previousColor,
to: color.cgColor,
keyPath: "contentsMultiplyColor",
timingFunction: curve.timingFunction,
duration: duration,
delay: 0.0,
mediaTimingFunction: curve.mediaTimingFunction,
removeOnCompletion: true,
additive: false,
completion: completion
)
}
}
func updateContentsRect(layer: CALayer, contentsRect: CGRect, completion: ((Bool) -> Void)? = nil) {
if layer.contentsRect == contentsRect {
if let completion = completion {
completion(true)
}
return
}
switch self {
case .immediate:
layer.contentsRect = contentsRect
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousContentsRect = layer.contentsRect
layer.contentsRect = contentsRect
layer.animate(from: NSValue(cgRect: previousContentsRect), to: NSValue(cgRect: contentsRect), keyPath: "contentsRect", timingFunction: curve.timingFunction, duration: duration, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
func animateTransformScale(node: ASDisplayNode, from fromScale: CGFloat, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
let t = node.layer.transform
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
if currentScale.isEqual(to: fromScale) {
if let completion = completion {
completion(true)
}
return
}
switch self {
case .immediate:
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let calculatedFrom: CGFloat
let calculatedTo: CGFloat
if additive {
calculatedFrom = fromScale - currentScale
calculatedTo = 0.0
} else {
calculatedFrom = fromScale
calculatedTo = currentScale
}
node.layer.animateScale(from: calculatedFrom, to: calculatedTo, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, additive: additive, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
func animateTransformScale(node: ASDisplayNode, from fromScale: CGPoint, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) {
switch self {
case .immediate:
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let calculatedFrom: CGPoint
let calculatedTo: CGPoint
calculatedFrom = fromScale
calculatedTo = CGPoint(x: 1.0, y: 1.0)
node.layer.animateScaleX(from: calculatedFrom.x, to: calculatedTo.x, duration: duration, delay: delay, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion {
completion(result)
}
})
node.layer.animateScaleY(from: calculatedFrom.y, to: calculatedTo.y, duration: duration, delay: delay, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction)
}
}
func animateTransformScale(layer: CALayer, from fromScale: CGPoint, completion: ((Bool) -> Void)? = nil) {
switch self {
case .immediate:
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let calculatedFrom: CGPoint
let calculatedTo: CGPoint
calculatedFrom = fromScale
calculatedTo = CGPoint(x: 1.0, y: 1.0)
layer.animateScaleX(from: calculatedFrom.x, to: calculatedTo.x, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion {
completion(result)
}
})
layer.animateScaleY(from: calculatedFrom.y, to: calculatedTo.y, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction)
}
}
func animateTransformScale(layer: CALayer, from fromScale: CGPoint, to toScale: CGPoint, completion: ((Bool) -> Void)? = nil) {
switch self {
case .immediate:
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let calculatedFrom: CGPoint
let calculatedTo: CGPoint
calculatedFrom = fromScale
calculatedTo = toScale
layer.animateScaleX(from: calculatedFrom.x, to: calculatedTo.x, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion {
completion(result)
}
})
layer.animateScaleY(from: calculatedFrom.y, to: calculatedTo.y, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction)
}
}
func animateTransformScale(view: UIView, from fromScale: CGFloat, completion: ((Bool) -> Void)? = nil) {
let t = view.layer.transform
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
if currentScale.isEqual(to: fromScale) {
if let completion = completion {
completion(true)
}
return
}
switch self {
case .immediate:
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
view.layer.animateScale(from: fromScale, to: currentScale, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
func updateTransform(node: ASDisplayNode, transform: CGAffineTransform, beginWithCurrentState: Bool = false, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) {
self.updateTransform(layer: node.layer, transform: transform, beginWithCurrentState: beginWithCurrentState, delay: delay, completion: completion)
}
func updateTransform(layer: CALayer, transform: CGAffineTransform, beginWithCurrentState: Bool = false, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) {
let transform = CATransform3DMakeAffineTransform(transform)
if CATransform3DEqualToTransform(layer.transform, transform) {
if let completion = completion {
completion(true)
}
return
}
switch self {
case .immediate:
layer.transform = transform
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousTransform: CATransform3D
if beginWithCurrentState, let presentation = layer.presentation() {
previousTransform = presentation.transform
} else {
previousTransform = layer.transform
}
layer.transform = transform
layer.animate(from: NSValue(caTransform3D: previousTransform), to: NSValue(caTransform3D: transform), keyPath: "transform", timingFunction: curve.timingFunction, duration: duration, delay: delay, mediaTimingFunction: curve.mediaTimingFunction, completion: { value in
completion?(value)
})
}
}
func updateTransformScale(node: ASDisplayNode, scale: CGFloat, beginWithCurrentState: Bool = false, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) {
let t = node.layer.transform
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
if currentScale.isEqual(to: scale) {
if let completion = completion {
completion(true)
}
return
}
switch self {
case .immediate:
node.layer.transform = CATransform3DMakeScale(scale, scale, 1.0)
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousScale: CGFloat
if beginWithCurrentState, let presentation = node.layer.presentation() {
let t = presentation.transform
previousScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
} else {
previousScale = currentScale
}
node.layer.transform = CATransform3DMakeScale(scale, scale, 1.0)
node.layer.animateScale(from: previousScale, to: scale, duration: duration, delay: delay, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
func updateTransformScale(layer: CALayer, scale: CGFloat, completion: ((Bool) -> Void)? = nil) {
let t = layer.transform
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
if currentScale.isEqual(to: scale) {
if let completion = completion {
completion(true)
}
return
}
switch self {
case .immediate:
layer.transform = CATransform3DMakeScale(scale, scale, 1.0)
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
layer.transform = CATransform3DMakeScale(scale, scale, 1.0)
layer.animateScale(from: currentScale, to: scale, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
func updateSublayerTransformScale(node: ASDisplayNode, scale: CGFloat, delay: Double = 0.0, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
if !node.isNodeLoaded {
node.subnodeTransform = CATransform3DMakeScale(scale, scale, 1.0)
completion?(true)
return
}
self.updateSublayerTransformScale(layer: node.layer, scale: CGPoint(x: scale, y: scale), beginWithCurrentState: beginWithCurrentState, completion: completion)
}
func updateSublayerTransformScaleAdditive(node: ASDisplayNode, scale: CGFloat, completion: ((Bool) -> Void)? = nil) {
if !node.isNodeLoaded {
node.subnodeTransform = CATransform3DMakeScale(scale, scale, 1.0)
completion?(true)
return
}
self.updateSublayerTransformScaleAdditive(layer: node.layer, scale: scale, completion: completion)
}
func updateSublayerTransformScaleAdditive(layer: CALayer, scale: CGFloat, completion: ((Bool) -> Void)? = nil) {
let t = layer.sublayerTransform
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
if currentScale.isEqual(to: scale) {
if let completion = completion {
completion(true)
}
return
}
switch self {
case .immediate:
layer.removeAnimation(forKey: "sublayerTransform")
layer.sublayerTransform = CATransform3DMakeScale(scale, scale, 1.0)
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let t = layer.sublayerTransform
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
layer.sublayerTransform = CATransform3DMakeScale(scale, scale, 1.0)
layer.animate(from: -(scale - currentScale) as NSNumber, to: 0.0 as NSNumber, keyPath: "sublayerTransform.scale", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: true, completion: {
result in
if let completion = completion {
completion(result)
}
})
}
}
func updateSublayerTransformScaleAndOffset(layer: CALayer, scale: CGFloat, offset: CGPoint, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
let t = layer.sublayerTransform
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
let currentOffset = CGPoint(x: t.m41 / currentScale, y: t.m42 / currentScale)
if abs(currentScale - scale) <= CGFloat.ulpOfOne && abs(currentOffset.x - offset.x) <= CGFloat.ulpOfOne && abs(currentOffset.y - offset.y) <= CGFloat.ulpOfOne {
if let completion = completion {
completion(true)
}
return
}
let transform = CATransform3DTranslate(CATransform3DMakeScale(scale, scale, 1.0), offset.x, offset.y, 0.0)
switch self {
case .immediate:
layer.removeAnimation(forKey: "sublayerTransform")
layer.sublayerTransform = transform
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let initialTransform: CATransform3D
if beginWithCurrentState {
initialTransform = layer.presentation()?.sublayerTransform ?? t
} else {
initialTransform = t
}
layer.sublayerTransform = transform
layer.animate(from: NSValue(caTransform3D: initialTransform), 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
if let completion = completion {
completion(result)
}
})
}
}
func updateSublayerTransformScaleAndOffset(node: ASDisplayNode, scale: CGFloat, offset: CGPoint, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
if !node.isNodeLoaded {
node.subnodeTransform = CATransform3DMakeScale(scale, scale, 1.0)
completion?(true)
return
}
return updateSublayerTransformScaleAndOffset(layer: node.layer, scale: scale, offset: offset, beginWithCurrentState: beginWithCurrentState, completion: completion)
}
func updateSublayerTransformScale(node: ASDisplayNode, scale: CGPoint, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
if !node.isNodeLoaded {
node.subnodeTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0)
completion?(true)
return
}
self.updateSublayerTransformScale(layer: node.layer, scale: scale, beginWithCurrentState: beginWithCurrentState, completion: completion)
}
func updateSublayerTransformScale(layer: CALayer, scale: CGPoint, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
let t = layer.sublayerTransform
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))
if t.m22 < 0.0 {
currentScaleY = -currentScaleY
}
if CGPoint(x: currentScaleX, y: currentScaleY) == scale {
if let completion = completion {
completion(true)
}
return
}
switch self {
case .immediate:
layer.removeAnimation(forKey: "sublayerTransform")
layer.sublayerTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0)
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let initialTransform: CATransform3D
if beginWithCurrentState {
initialTransform = layer.presentation()?.sublayerTransform ?? t
} else {
initialTransform = t
}
layer.sublayerTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0)
layer.animate(from: NSValue(caTransform3D: initialTransform), 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
if let completion = completion {
completion(result)
}
})
}
}
func updateTransformScale(node: ASDisplayNode, scale: CGPoint, completion: ((Bool) -> Void)? = nil) {
if !node.isNodeLoaded {
node.subnodeTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0)
completion?(true)
return
}
self.updateTransformScale(layer: node.layer, scale: scale, completion: completion)
}
func updateTransformScale(layer: CALayer, scale: CGPoint, completion: ((Bool) -> Void)? = nil) {
let t = layer.transform
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))
if t.m22 < 0.0 {
currentScaleY = -currentScaleY
}
if CGPoint(x: currentScaleX, y: currentScaleY) == scale {
if let completion = completion {
completion(true)
}
return
}
switch self {
case .immediate:
layer.removeAnimation(forKey: "transform")
layer.transform = CATransform3DMakeScale(scale.x, scale.y, 1.0)
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
layer.transform = CATransform3DMakeScale(scale.x, scale.y, 1.0)
layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: 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)
}
})
}
}
func updateSublayerTransformOffset(layer: CALayer, offset: CGPoint, completion: ((Bool) -> Void)? = nil) {
let t = layer.sublayerTransform
let currentOffset = CGPoint(x: t.m41, y: t.m42)
if currentOffset == offset {
if let completion = completion {
completion(true)
}
return
}
switch self {
case .immediate:
layer.removeAnimation(forKey: "sublayerTransform")
layer.sublayerTransform = CATransform3DMakeTranslation(offset.x, offset.y, 0.0)
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
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: curve.mediaTimingFunction, removeOnCompletion: true, additive: false, completion: {
result in
if let completion = completion {
completion(result)
}
})
}
}
func updateTransformRotation(node: ASDisplayNode, angle: CGFloat, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
let t = node.layer.transform
let currentAngle = atan2(t.m12, t.m11)
if currentAngle.isEqual(to: angle) {
if let completion = completion {
completion(true)
}
return
}
switch self {
case .immediate:
node.layer.transform = CATransform3DMakeRotation(angle, 0.0, 0.0, 1.0)
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousAngle: CGFloat
if beginWithCurrentState, let presentation = node.layer.presentation() {
let t = presentation.transform
previousAngle = atan2(t.m12, t.m11)
} else {
previousAngle = currentAngle
}
node.layer.transform = CATransform3DMakeRotation(angle, 0.0, 0.0, 1.0)
node.layer.animateRotation(from: previousAngle, to: angle, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
func updateTransformRotation(view: UIView, angle: CGFloat, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
let t = view.layer.transform
let currentAngle = atan2(t.m12, t.m11)
if currentAngle.isEqual(to: angle) {
if let completion = completion {
completion(true)
}
return
}
switch self {
case .immediate:
view.layer.transform = CATransform3DMakeRotation(angle, 0.0, 0.0, 1.0)
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousAngle: CGFloat
if beginWithCurrentState, let presentation = view.layer.presentation() {
let t = presentation.transform
previousAngle = atan2(t.m12, t.m11)
} else {
previousAngle = currentAngle
}
view.layer.transform = CATransform3DMakeRotation(angle, 0.0, 0.0, 1.0)
view.layer.animateRotation(from: previousAngle, to: angle, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
func updateTransformRotationAndScale(view: UIView, angle: CGFloat, scale: CGPoint, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) {
let t = view.layer.transform
let currentAngle = atan2(t.m12, t.m11)
let currentScale = CGPoint(x: t.m11, y: t.m12)
if currentAngle.isEqual(to: angle) && currentScale == scale {
if let completion = completion {
completion(true)
}
return
}
switch self {
case .immediate:
view.layer.transform = CATransform3DRotate(CATransform3DMakeScale(scale.x, scale.y, 1.0), angle, 0.0, 0.0, 1.0)
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let previousAngle: CGFloat
if beginWithCurrentState, let presentation = view.layer.presentation() {
let t = presentation.transform
previousAngle = atan2(t.m12, t.m11)
} else {
previousAngle = currentAngle
}
view.layer.transform = CATransform3DRotate(CATransform3DMakeScale(scale.x, scale.y, 1.0), angle, 0.0, 0.0, 1.0)
view.layer.animateRotation(from: previousAngle, to: angle, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in
if let completion = completion {
completion(result)
}
})
}
}
func updatePath(layer: CAShapeLayer, path: CGPath, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) {
if layer.path == path {
completion?(true)
return
}
switch self {
case .immediate:
layer.removeAnimation(forKey: "path")
layer.path = path
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let fromPath = layer.path
layer.path = path
layer.animate(from: fromPath, to: path, keyPath: "path", timingFunction: curve.timingFunction, duration: duration, delay: delay, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: false, completion: {
result in
if let completion = completion {
completion(result)
}
})
}
}
func updateLineWidth(layer: CAShapeLayer, lineWidth: CGFloat, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) {
if layer.lineWidth == lineWidth {
completion?(true)
return
}
switch self {
case .immediate:
layer.removeAnimation(forKey: "lineWidth")
layer.lineWidth = lineWidth
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let fromLineWidth = layer.lineWidth
layer.lineWidth = lineWidth
layer.animate(from: fromLineWidth as NSNumber, to: lineWidth as NSNumber, keyPath: "lineWidth", timingFunction: curve.timingFunction, duration: duration, delay: delay, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: false, completion: {
result in
if let completion = completion {
completion(result)
}
})
}
}
func updateStrokeColor(layer: CAShapeLayer, strokeColor: UIColor, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) {
if layer.strokeColor.flatMap(UIColor.init(cgColor:)) == strokeColor {
completion?(true)
return
}
switch self {
case .immediate:
layer.removeAnimation(forKey: "strokeColor")
layer.strokeColor = strokeColor.cgColor
if let completion = completion {
completion(true)
}
case let .animated(duration, curve):
let fromStrokeColor = layer.strokeColor ?? UIColor.clear.cgColor
layer.strokeColor = strokeColor.cgColor
layer.animate(from: fromStrokeColor, to: strokeColor.cgColor, keyPath: "strokeColor", timingFunction: curve.timingFunction, duration: duration, delay: delay, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: false, completion: {
result in
if let completion = completion {
completion(result)
}
})
}
}
func attachAnimation(view: UIView, id: String, completion: @escaping (Bool) -> Void) {
switch self {
case .immediate:
completion(true)
case let .animated(duration, curve):
view.layer.animate(
from: 0.0 as NSNumber,
to: 1.0 as NSNumber,
keyPath: id,
duration: duration,
delay: 0.0,
curve: curve,
removeOnCompletion: true,
additive: false,
completion: completion
)
}
}
}
public struct CombinedTransition {
public var horizontal: ContainedViewLayoutTransition
public var vertical: ContainedViewLayoutTransition
public var isAnimated: Bool {
return self.horizontal.isAnimated || self.vertical.isAnimated
}
public init(horizontal: ContainedViewLayoutTransition, vertical: ContainedViewLayoutTransition) {
self.horizontal = horizontal
self.vertical = vertical
}
public func animateFrame(layer: CALayer, from fromFrame: CGRect, completion: ((Bool) -> Void)? = nil) {
//self.horizontal.animateFrame(layer: layer, from: fromFrame, completion: completion)
//return;
let toFrame = layer.frame
enum Keys: CaseIterable {
case positionX, positionY
case sizeWidth, sizeHeight
}
var remainingKeys = Keys.allCases
var completedValue = true
let completeKey: (Keys, Bool) -> Void = { key, completed in
remainingKeys.removeAll(where: { $0 == key })
if !completed {
completedValue = false
}
if remainingKeys.isEmpty {
completion?(completedValue)
}
}
self.horizontal.animatePositionAdditive(layer: layer, offset: CGPoint(x: fromFrame.midX - toFrame.midX, y: 0.0), completion: { result in
completeKey(.positionX, result)
})
self.vertical.animatePositionAdditive(layer: layer, offset: CGPoint(x: 0.0, y: fromFrame.midY - toFrame.midY), completion: { result in
completeKey(.positionY, result)
})
self.horizontal.animateWidthAdditive(layer: layer, value: fromFrame.width - toFrame.width, completion: { result in
completeKey(.sizeWidth, result)
})
self.vertical.animateHeightAdditive(layer: layer, value: fromFrame.height - toFrame.height, completion: { result in
completeKey(.sizeHeight, result)
})
}
public func updateFrame(layer: CALayer, frame: CGRect, completion: ((Bool) -> Void)? = nil) {
let fromFrame = layer.frame
layer.frame = frame
self.animateFrame(layer: layer, from: fromFrame, completion: completion)
}
public func updateFrame(node: ASDisplayNode, frame: CGRect, completion: ((Bool) -> Void)? = nil) {
let fromFrame = node.frame
node.frame = frame
self.animateFrame(layer: node.layer, from: fromFrame, completion: completion)
}
public func updatePosition(layer: CALayer, position: CGPoint, completion: ((Bool) -> Void)? = nil) {
let fromPosition = layer.position
layer.position = position
enum Keys: CaseIterable {
case positionX, positionY
}
var remainingKeys = Keys.allCases
var completedValue = true
let completeKey: (Keys, Bool) -> Void = { key, completed in
remainingKeys.removeAll(where: { $0 == key })
if !completed {
completedValue = false
}
if remainingKeys.isEmpty {
completion?(completedValue)
}
}
self.horizontal.animatePositionAdditive(layer: layer, offset: CGPoint(x: fromPosition.x - position.x, y: 0.0), completion: { result in
completeKey(.positionX, result)
})
self.vertical.animatePositionAdditive(layer: layer, offset: CGPoint(x: 0.0, y: fromPosition.y - position.y), completion: { result in
completeKey(.positionY, result)
})
}
public func animatePositionAdditive(layer: CALayer, offset: CGPoint, to toOffset: CGPoint = CGPoint(), removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
enum Keys: CaseIterable {
case positionX, positionY
}
var remainingKeys = Keys.allCases
var completedValue = true
let completeKey: (Keys, Bool) -> Void = { key, completed in
remainingKeys.removeAll(where: { $0 == key })
if !completed {
completedValue = false
}
if remainingKeys.isEmpty {
completion?(completedValue)
}
}
self.horizontal.animatePositionAdditive(layer: layer, offset: CGPoint(x: offset.x, y: 0.0), to: CGPoint(x: toOffset.x, y: 0.0), completion: { result in
completeKey(.positionX, result)
})
self.vertical.animatePositionAdditive(layer: layer, offset: CGPoint(x: 0.0, y: offset.y), to: CGPoint(x: 0.0, y: toOffset.y), completion: { result in
completeKey(.positionY, result)
})
}
}
public extension ContainedViewLayoutTransition {
func animateView(allowUserInteraction: Bool = false, delay: Double = 0.0, _ f: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) {
switch self {
case .immediate:
f()
completion?(true)
case let .animated(duration, curve):
var options = curve.viewAnimationOptions
if allowUserInteraction {
options.insert(.allowUserInteraction)
}
UIView.animate(withDuration: duration, delay: delay, options: options, animations: {
f()
}, completion: completion)
}
}
}
public protocol ControlledTransitionAnimator: AnyObject {
var duration: Double { get }
func startAnimation()
func setAnimationProgress(_ progress: CGFloat)
func finishAnimation()
func updateAlpha(layer: CALayer, alpha: CGFloat, completion: ((Bool) -> Void)?)
func updateScale(layer: CALayer, scale: CGFloat, completion: ((Bool) -> Void)?)
func animateScale(layer: CALayer, from fromValue: CGFloat, to toValue: CGFloat, completion: ((Bool) -> Void)?)
func updatePosition(layer: CALayer, position: CGPoint, completion: ((Bool) -> Void)?)
func animatePosition(layer: CALayer, from fromValue: CGPoint, to toValue: CGPoint, completion: ((Bool) -> Void)?)
func updateBounds(layer: CALayer, bounds: CGRect, completion: ((Bool) -> Void)?)
func updateFrame(layer: CALayer, frame: CGRect, completion: ((Bool) -> Void)?)
func updateCornerRadius(layer: CALayer, cornerRadius: CGFloat, completion: ((Bool) -> Void)?)
func updateContentsRect(layer: CALayer, contentsRect: CGRect, completion: ((Bool) -> Void)?)
}
protocol AnyValueProviding {
var anyValue: ControlledTransitionProperty.AnyValue { get }
}
extension CGFloat: AnyValueProviding {
func interpolate(with other: CGFloat, fraction: CGFloat) -> CGFloat {
let invT = 1.0 - fraction
let result = other * fraction + self * invT
return result
}
var anyValue: ControlledTransitionProperty.AnyValue {
return ControlledTransitionProperty.AnyValue(
value: self,
nsValue: self as NSNumber,
stringValue: { "\(self)" },
isEqual: { other in
if let otherValue = other.value as? CGFloat {
return self == otherValue
} else {
return false
}
},
interpolate: { other, fraction in
guard let otherValue = other.value as? CGFloat else {
preconditionFailure()
}
return self.interpolate(with: otherValue, fraction: fraction).anyValue
}
)
}
}
extension Float: AnyValueProviding {
func interpolate(with other: Float, fraction: CGFloat) -> Float {
let invT = 1.0 - Float(fraction)
let result = other * Float(fraction) + self * invT
return result
}
var anyValue: ControlledTransitionProperty.AnyValue {
return ControlledTransitionProperty.AnyValue(
value: self,
nsValue: self as NSNumber,
stringValue: { "\(self)" },
isEqual: { other in
if let otherValue = other.value as? Float {
return self == otherValue
} else {
return false
}
},
interpolate: { other, fraction in
guard let otherValue = other.value as? Float else {
preconditionFailure()
}
return self.interpolate(with: otherValue, fraction: fraction).anyValue
}
)
}
}
extension CGPoint: AnyValueProviding {
func interpolate(with other: CGPoint, fraction: CGFloat) -> CGPoint {
return CGPoint(x: self.x.interpolate(with: other.x, fraction: fraction), y: self.y.interpolate(with: other.y, fraction: fraction))
}
var anyValue: ControlledTransitionProperty.AnyValue {
return ControlledTransitionProperty.AnyValue(
value: self,
nsValue: NSValue(cgPoint: self),
stringValue: { "\(self)" },
isEqual: { other in
if let otherValue = other.value as? CGPoint {
return self == otherValue
} else {
return false
}
},
interpolate: { other, fraction in
guard let otherValue = other.value as? CGPoint else {
preconditionFailure()
}
return self.interpolate(with: otherValue, fraction: fraction).anyValue
}
)
}
}
extension CGSize: AnyValueProviding {
func interpolate(with other: CGSize, fraction: CGFloat) -> CGSize {
return CGSize(width: self.width.interpolate(with: other.width, fraction: fraction), height: self.height.interpolate(with: other.height, fraction: fraction))
}
var anyValue: ControlledTransitionProperty.AnyValue {
return ControlledTransitionProperty.AnyValue(
value: self,
nsValue: NSValue(cgSize: self),
stringValue: { "\(self)" },
isEqual: { other in
if let otherValue = other.value as? CGSize {
return self == otherValue
} else {
return false
}
},
interpolate: { other, fraction in
guard let otherValue = other.value as? CGSize else {
preconditionFailure()
}
return self.interpolate(with: otherValue, fraction: fraction).anyValue
}
)
}
}
extension CGRect: AnyValueProviding {
func interpolate(with other: CGRect, fraction: CGFloat) -> CGRect {
return CGRect(origin: self.origin.interpolate(with: other.origin, fraction: fraction), size: self.size.interpolate(with: other.size, fraction: fraction))
}
var anyValue: ControlledTransitionProperty.AnyValue {
return ControlledTransitionProperty.AnyValue(
value: self,
nsValue: NSValue(cgRect: self),
stringValue: { "\(self)" },
isEqual: { other in
if let otherValue = other.value as? CGRect {
return self == otherValue
} else {
return false
}
},
interpolate: { other, fraction in
guard let otherValue = other.value as? CGRect else {
preconditionFailure()
}
return self.interpolate(with: otherValue, fraction: fraction).anyValue
}
)
}
}
final class ControlledTransitionProperty {
final class AnyValue: Equatable, CustomStringConvertible {
let value: Any
let nsValue: Any
let stringValue: () -> String
let isEqual: (AnyValue) -> Bool
let interpolate: (AnyValue, CGFloat) -> AnyValue
init(
value: Any,
nsValue: Any,
stringValue: @escaping () -> String,
isEqual: @escaping (AnyValue) -> Bool,
interpolate: @escaping (AnyValue, CGFloat) -> AnyValue
) {
self.value = value
self.nsValue = nsValue
self.stringValue = stringValue
self.isEqual = isEqual
self.interpolate = interpolate
}
var description: String {
return self.stringValue()
}
static func ==(lhs: AnyValue, rhs: AnyValue) -> Bool {
if lhs.isEqual(rhs) {
return true
} else {
return false
}
}
}
let layer: CALayer
let path: String
var fromValue: AnyValue
let toValue: AnyValue
private let completion: ((Bool) -> Void)?
private lazy var animationKey: String = {
return "MyCustomAnimation_\(Unmanaged.passUnretained(self).toOpaque())"
}()
init<T: Equatable>(layer: CALayer, path: String, fromValue: T, toValue: T, completion: ((Bool) -> Void)?) where T: AnyValueProviding {
self.layer = layer
self.path = path
self.fromValue = fromValue.anyValue
self.toValue = toValue.anyValue
self.completion = completion
self.update(at: 0.0)
}
deinit {
self.layer.removeAnimation(forKey: self.animationKey)
}
func update(at fraction: CGFloat) {
let value = self.fromValue.interpolate(toValue, fraction)
let animation = CABasicAnimation(keyPath: self.path)
animation.speed = 0.0
animation.beginTime = CACurrentMediaTime() + 1000.0
animation.timeOffset = 0.01
animation.duration = 1.0
animation.fillMode = .both
animation.fromValue = value.nsValue
animation.toValue = value.nsValue
animation.timingFunction = CAMediaTimingFunction(name: .linear)
animation.isRemovedOnCompletion = false
self.layer.add(animation, forKey: self.animationKey)
}
func complete(atEnd: Bool) {
self.completion?(atEnd)
}
}
public final class ControlledTransition {
public final class NativeAnimator: ControlledTransitionAnimator {
public let duration: Double
private let curve: ContainedViewLayoutTransitionCurve
private var animations: [ControlledTransitionProperty] = []
init(
duration: Double,
curve: ContainedViewLayoutTransitionCurve
) {
self.duration = duration
self.curve = curve
}
func merge(with other: NativeAnimator, forceRestart: Bool) {
var removeAnimationIndices: [Int] = []
for i in 0 ..< self.animations.count {
let animation = self.animations[i]
var removeOtherAnimationIndices: [Int] = []
for j in 0 ..< other.animations.count {
let otherAnimation = other.animations[j]
if animation.layer === otherAnimation.layer && animation.path == otherAnimation.path {
if animation.toValue == otherAnimation.toValue && !forceRestart {
removeAnimationIndices.append(i)
} else {
removeOtherAnimationIndices.append(j)
}
}
}
for j in removeOtherAnimationIndices.reversed() {
let otherAnimation = other.animations.remove(at: j)
otherAnimation.complete(atEnd: false)
}
}
for i in Set(removeAnimationIndices).sorted().reversed() {
self.animations.remove(at: i).complete(atEnd: false)
}
}
public func startAnimation() {
}
public func setAnimationProgress(_ progress: CGFloat) {
let mappedFraction: CGFloat
switch self.curve {
case .spring:
mappedFraction = springAnimationSolver(progress)
case let .custom(c1x, c1y, c2x, c2y):
mappedFraction = bezierPoint(CGFloat(c1x), CGFloat(c1y), CGFloat(c2x), CGFloat(c2y), progress)
default:
mappedFraction = progress
}
for animation in self.animations {
animation.update(at: mappedFraction)
}
}
public func finishAnimation() {
for animation in self.animations {
animation.update(at: 1.0)
animation.complete(atEnd: true)
}
self.animations.removeAll()
}
private func add(animation: ControlledTransitionProperty) {
for i in 0 ..< self.animations.count {
let otherAnimation = self.animations[i]
if otherAnimation.layer === animation.layer && otherAnimation.path == animation.path {
let currentAnimation = self.animations[i]
currentAnimation.complete(atEnd: false)
self.animations.remove(at: i)
break
}
}
self.animations.append(animation)
}
public func updateAlpha(layer: CALayer, alpha: CGFloat, completion: ((Bool) -> Void)?) {
if layer.opacity == Float(alpha) {
return
}
let fromValue = layer.presentation()?.opacity ?? layer.opacity
layer.opacity = Float(alpha)
self.add(animation: ControlledTransitionProperty(
layer: layer,
path: "opacity",
fromValue: fromValue,
toValue: Float(alpha),
completion: completion
))
}
public func updateScale(layer: CALayer, scale: CGFloat, completion: ((Bool) -> Void)?) {
let t = layer.presentation()?.transform ?? layer.transform
let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13))
if currentScale == scale {
return
}
layer.transform = CATransform3DMakeScale(scale, scale, 1.0)
self.add(animation: ControlledTransitionProperty(
layer: layer,
path: "transform.scale",
fromValue: currentScale,
toValue: scale,
completion: completion
))
}
public func animateScale(layer: CALayer, from fromValue: CGFloat, to toValue: CGFloat, completion: ((Bool) -> Void)?) {
self.add(animation: ControlledTransitionProperty(
layer: layer,
path: "transform.scale",
fromValue: fromValue,
toValue: toValue,
completion: completion
))
}
public func animatePosition(layer: CALayer, from fromValue: CGPoint, to toValue: CGPoint, completion: ((Bool) -> Void)?) {
self.add(animation: ControlledTransitionProperty(
layer: layer,
path: "position",
fromValue: fromValue,
toValue: toValue,
completion: completion
))
}
public func updatePosition(layer: CALayer, position: CGPoint, completion: ((Bool) -> Void)?) {
if layer.position == position {
return
}
let fromValue = layer.presentation()?.position ?? layer.position
layer.position = position
self.add(animation: ControlledTransitionProperty(
layer: layer,
path: "position",
fromValue: fromValue,
toValue: position,
completion: completion
))
}
public func updateBounds(layer: CALayer, bounds: CGRect, completion: ((Bool) -> Void)?) {
if layer.bounds == bounds {
return
}
let fromValue: CGRect
if let animationKeys = layer.animationKeys(), animationKeys.contains(where: { key in
guard let animation = layer.animation(forKey: key) as? CAPropertyAnimation else {
return false
}
if animation.keyPath == "bounds" {
return true
} else {
return false
}
}) {
fromValue = layer.presentation()?.bounds ?? layer.bounds
} else {
fromValue = layer.bounds
}
layer.bounds = bounds
self.add(animation: ControlledTransitionProperty(
layer: layer,
path: "bounds",
fromValue: fromValue,
toValue: bounds,
completion: completion
))
}
public func updateFrame(layer: CALayer, frame: CGRect, completion: ((Bool) -> Void)?) {
self.updatePosition(layer: layer, position: frame.center, completion: completion)
self.updateBounds(layer: layer, bounds: CGRect(origin: CGPoint(), size: frame.size), completion: nil)
}
public func updateCornerRadius(layer: CALayer, cornerRadius: CGFloat, completion: ((Bool) -> Void)?) {
if layer.cornerRadius == cornerRadius {
return
}
let fromValue = layer.presentation()?.cornerRadius ?? layer.cornerRadius
layer.cornerRadius = cornerRadius
self.add(animation: ControlledTransitionProperty(
layer: layer,
path: "cornerRadius",
fromValue: fromValue,
toValue: cornerRadius,
completion: completion
))
}
public func updateContentsRect(layer: CALayer, contentsRect: CGRect, completion: ((Bool) -> Void)?) {
if layer.contentsRect == contentsRect {
return
}
let fromValue = layer.presentation()?.contentsRect ?? layer.contentsRect
layer.contentsRect = contentsRect
self.add(animation: ControlledTransitionProperty(
layer: layer,
path: "contentsRect",
fromValue: fromValue,
toValue: contentsRect,
completion: completion
))
}
}
public final class LegacyAnimator: ControlledTransitionAnimator {
public let duration: Double
public let transition: ContainedViewLayoutTransition
init(
duration: Double,
curve: ContainedViewLayoutTransitionCurve
) {
self.duration = duration
if duration.isZero {
self.transition = .immediate
} else {
self.transition = .animated(duration: duration, curve: curve)
}
}
public func startAnimation() {
}
public func setAnimationProgress(_ progress: CGFloat) {
}
public func finishAnimation() {
}
public func updateAlpha(layer: CALayer, alpha: CGFloat, completion: ((Bool) -> Void)?) {
self.transition.updateAlpha(layer: layer, alpha: alpha, completion: completion)
}
public func updateScale(layer: CALayer, scale: CGFloat, completion: ((Bool) -> Void)?) {
self.transition.updateTransformScale(layer: layer, scale: scale, completion: completion)
}
public func animateScale(layer: CALayer, from fromValue: CGFloat, to toValue: CGFloat, completion: ((Bool) -> Void)?) {
self.transition.animateTransformScale(layer: layer, from: CGPoint(x: fromValue, y: fromValue), to: CGPoint(x: toValue, y: toValue), completion: completion)
}
public func updatePosition(layer: CALayer, position: CGPoint, completion: ((Bool) -> Void)?) {
self.transition.updatePosition(layer: layer, position: position, completion: completion)
}
public func animatePosition(layer: CALayer, from fromValue: CGPoint, to toValue: CGPoint, completion: ((Bool) -> Void)?) {
self.transition.animatePosition(layer: layer, from: fromValue, to: toValue, completion: completion)
}
public func updateBounds(layer: CALayer, bounds: CGRect, completion: ((Bool) -> Void)?) {
self.transition.updateBounds(layer: layer, bounds: bounds, completion: completion)
}
public func updateFrame(layer: CALayer, frame: CGRect, completion: ((Bool) -> Void)?) {
self.transition.updateFrame(layer: layer, frame: frame, completion: completion)
}
public func updateCornerRadius(layer: CALayer, cornerRadius: CGFloat, completion: ((Bool) -> Void)?) {
self.transition.updateCornerRadius(layer: layer, cornerRadius: cornerRadius, completion: completion)
}
public func updateContentsRect(layer: CALayer, contentsRect: CGRect, completion: ((Bool) -> Void)?) {
self.transition.updateContentsRect(layer: layer, contentsRect: contentsRect, completion: completion)
}
}
public let animator: ControlledTransitionAnimator
public let legacyAnimator: LegacyAnimator
public init(
duration: Double,
curve: ContainedViewLayoutTransitionCurve,
interactive: Bool
) {
self.legacyAnimator = LegacyAnimator(
duration: duration,
curve: curve
)
if interactive {
self.animator = NativeAnimator(
duration: duration,
curve: curve
)
} else {
self.animator = self.legacyAnimator
}
}
public func merge(with other: ControlledTransition, forceRestart: Bool) {
if let animator = self.animator as? NativeAnimator, let otherAnimator = other.animator as? NativeAnimator {
animator.merge(with: otherAnimator, forceRestart: forceRestart)
}
}
}