即使将 timingFunction 显式设置为线性,动画也不会线性执行。
我使用下面的代码来初始化动画。
再往下就是整个类的实现以及ViewController在InterfaceBuilder中是如何设置的
private func timeLayerAnimation() {
let animation = CABasicAnimation()
animation.keyPath = "strokeEnd"
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animation.duration = 240.0
animation.toValue = 0
animation.fillMode = kCAFillModeForwards
animation.isRemovedOnCompletion = false
timeLayer.add(animation, forKey: nil)
}
总动画时长为 240 秒。
但 30 秒后。已经只有 75% 的圆仍然可见。
停止次数如下:
75 % (1.5 π): 30 sec. (∆ 30 sec.)
50 % (1 π): 70 sec. (∆ 40 sec.)
25 % (0.5 π): 120 sec. (∆ 50 sec.)
// 13 % (0.25 π): 155 sec.
0 % (0 π): 240 sec. (∆ 120 sec.)
更新
我发现当负责动画的 ViewController 位于容器 View 中时会出现问题。
我的猜测是它可能与默认的 UIViewAnimationCurve 有关,但我不确定,也不知道从哪里开始测试它:(
容器 View 的所有边都固定到安全区域。 MainVC 的实现是空的,EmbeddedVC 如下所示:
import UIKit
class EmbeddedVC: UIViewController {
// MARK: - Properties
let timeLayer = CAShapeLayer()
// MARK: - View Lifecycle
override func viewDidLayoutSubviews() {
setupTimerLayout()
}
}
// MARK: - Timer Layout Setup
extension EmbeddedVC {
func setupTimerLayout() {
let circularPath = UIBezierPath.init(arcCenter: .zero,
radius: view.frame.width * 0.36,
startAngle: 0,
endAngle: 2 * CGFloat.pi,
clockwise: true)
// Configure time layer
timeLayer.path = circularPath.cgPath
timeLayer.fillColor = UIColor.clear.cgColor
timeLayer.lineCap = kCALineCapRound
timeLayer.strokeColor = UIColor.red.cgColor
timeLayer.strokeEnd = 1
timeLayer.lineWidth = 10
timeLayer.position = view.center
view.layer.addSublayer(timeLayer)
animateTimeLayer()
}
private func animateTimeLayer() {
let animation = CABasicAnimation()
animation.keyPath = "strokeEnd"
animation.duration = 240.0
animation.toValue = 0
animation.fillMode = kCAFillModeForwards
animation.isRemovedOnCompletion = false
timeLayer.add(animation, forKey: nil)
}
}
最佳答案
问题是 viewDidLayoutSubviews
可以被多次调用,而您没有指定 fromValue
所以这两个动画相互干扰。您可以通过以下任意组合解决此问题:
添加检查以查看您是否已经开始播放动画:
var hasStarted = false private func startAnimation() { guard !hasStarted else { return } hasStarted = true ... }
(注意,我建议您在覆盖这些方法时始终调用
super
。)在开始另一个动画之前删除动画。
在您的动画中指定一个
fromValue
:private func startAnimation() { let animation = CABasicAnimation(keyPath: "strokeEnd") animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) animation.duration = ... animation.fromValue = 1 // by setting this, it won't get confused if you start the animation again animation.toValue = 0 ... shapeLayer.add(animation, forKey: nil) }
推迟到
viewDidAppear
:override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) // start your animation here }
就我个人而言,#3 和#4 我都会做,但您可以做任何最适合您的事情。
关于swift - 线性 CABasicAnimation 在容器 View 中不是线性执行的,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49697216/