线性CABasicAnimation不在Container View中线性执行

时间:2018-04-06 16:26:42

标签: swift animation cabasicanimation

即使将timingFunction明确设置为 linear ,动画也不会线性执行。

我使用以下代码初始化动画。

进一步向下是整个类的实现以及如何在InterfaceBuilder中设置ViewController

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)
}

视图如下所示。 enter image description here

总动画持续时间为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存在于容器视图中时会出现问题。

我的猜测是它可能与默认的 UIViewAnimationCurve 有关,但我不确定,我不知道从哪里开始测试:(

enter image description here

容器视图的所有边都固定在安全区域。 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)
    }

}

2 个答案:

答案 0 :(得分:1)

问题是viewDidLayoutSubviews可以多次调用,而您没有指定fromValue,因此这两个动画相互干扰。您可以通过以下任意组合解决此问题:

  1. 添加一项检查以确定您是否已启动动画:

    var hasStarted = false
    
    private func startAnimation() {            
        guard !hasStarted else { return }
    
        hasStarted = true
    
        ...
    }
    

    (注意,我建议您在覆盖这些方法时始终致电super。)

  2. 在开始播放另一个动画之前删除动画。

  3. 在动画中指定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)
    }
    
  4. 将此推迟到viewDidAppear

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
    
        // start your animation here
    }
    
  5. 就个人而言,我会同时做#3和#4,但你可以做任何最适合你的事情。

答案 1 :(得分:0)

@Rob非常感谢你的答案,这真让我疯狂...... :)

现在一切都很完美。我只是想为那些使用扩展并且不想在基类上使用局部变量或者无法访问基类的人添加#1的内容。

private func animateTimeLayer() {
    let animationKey = "animateTimeLayerKey"

    guard nil == timeLayer.animation(forKey: animationKey) else { return }

    let animation = CABasicAnimation()

    ...

    timeLayer.add(animation, forKey: animationKey)
}