Глюк при запуске CABasicAnimation - PullRequest
2 голосов
/ 30 марта 2020

У меня есть анимация масштабирования / пульсации, которая работает с cgPaths в течение l oop, как показано ниже. Этот код работает, но только когда вы добавляете animatedLayers к пути circleLayer, когда circleLayer уже добавлен к subLayer, и это создает круг c stati (подобный сбою) перед анимацией ( DispatchQueue) запускается ...

...
self.layer.addSublayer(animatedLayer)
animatedLayers.append(animatedLayer)
...

enter image description here

Возможно ли добавить CAShapeLayer с аргументами в подуровень? Если нет, то какая-нибудь рекомендуемая альтернатива?

import UIKit
import Foundation

@IBDesignable
class AnimatedCircleView: UIView {

    // MARK: - Initializers

    var animatedLayers = [CAShapeLayer]()

    // MARK: - Methods

    override func draw(_ rect: CGRect) {

        // Animated circle
        for _ in 0...3 {
            let animatedPath = UIBezierPath(arcCenter: .zero, radius: self.layer.bounds.size.width / 2.3,
            startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
            let animatedLayer = CAShapeLayer()

            animatedLayer.path = animatedPath.cgPath
            animatedLayer.strokeColor = UIColor.black.cgColor
            animatedLayer.lineWidth = 0
            animatedLayer.fillColor = UIColor.gray.cgColor
            animatedLayer.lineCap = CAShapeLayerLineCap.round
            animatedLayer.position = CGPoint(x: self.layer.bounds.size.width / 2, y: self.layer.bounds.size.width / 2)
            self.layer.addSublayer(animatedLayer)
            animatedLayers.append(animatedLayer)
        }

        // Dispatch animation for circle _ 0...3
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            self.animateCircle(index: 0)
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
                self.animateCircle(index: 1)
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
                    self.animateCircle(index: 2)
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
                        self.animateCircle(index: 3)
                    }
                }
            }
        }
    }


    func animateCircle(index: Int) {

        let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
        scaleAnimation.duration = 1.8
        scaleAnimation.fromValue = 0
        scaleAnimation.toValue = 1
        scaleAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
        scaleAnimation.repeatCount = Float.infinity
        animatedLayers[index].add(scaleAnimation, forKey: "scale")

        let opacityAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
        opacityAnimation.duration = 1.8
        opacityAnimation.fromValue = 0.7
        opacityAnimation.toValue = 0
        opacityAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
        opacityAnimation.repeatCount = Float.infinity
        animatedLayers[index].add(opacityAnimation, forKey: "opacity")
    }
}

Ответы [ 2 ]

1 голос
/ 30 марта 2020

Ключевая проблема заключается в том, что ваша анимация запускается со смещенными задержками в диапазоне от 0,1 до 1,0 секунды. Пока не начнется последняя анимация, этот слой просто сидит там, в натуральную величину и с непрозрачностью 100%.

Поскольку вы анимируете масштаб преобразования от 0 до 1, я бы предложил установить начальное преобразование на 0 ( или изменив opacity на 0). Тогда вы не увидите их сидящими там, пока не начнутся соответствующие анимации.

Несколько других наблюдений:

  • draw(_:) не подходящее место для добавления слоев , запустить анимацию и т. д. c. Этот метод может вызываться несколько раз и должен представлять представление в данный момент времени. Я бы ушел на пенсию draw(_:), если это не то место, чтобы начать это, и вам вообще не нужен этот метод.

  • Вы запускаете свою первую анимацию через 0,1 секунды. Почему бы не запустить его немедленно?

  • Корректировки фрейма следует обрабатывать, откладывая настройку свойств path и position до layoutSubviews.

Таким образом:

@IBDesignable
class AnimatedCircleView: UIView {
    private var animatedLayers = [CAShapeLayer]()

    override init(frame: CGRect = .zero) {
        super.init(frame: frame)
        configure()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        configure()
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        let path = UIBezierPath(arcCenter: .zero, radius: bounds.width / 2.3, startAngle: 0, endAngle: 2 * .pi, clockwise: true)

        for animatedLayer in animatedLayers {
            animatedLayer.path = path.cgPath
            animatedLayer.position = CGPoint(x: bounds.midX, y: bounds.midY)
        }
    }
}

// MARK: - Methods

private extension AnimatedCircleView {
    func configure() {
        for _ in 0...3 {
            let animatedLayer = CAShapeLayer()
            animatedLayer.strokeColor = UIColor.black.cgColor
            animatedLayer.lineWidth = 0
            animatedLayer.fillColor = UIColor.gray.cgColor
            animatedLayer.lineCap = .round
            animatedLayer.transform = CATransform3DMakeScale(0, 0, 1)
            layer.addSublayer(animatedLayer)
            animatedLayers.append(animatedLayer)
        }

        self.animateCircle(index: 0)
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            self.animateCircle(index: 1)
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
                self.animateCircle(index: 2)
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
                    self.animateCircle(index: 3)
                }
            }
        }
    }

    func animateCircle(index: Int) {
        let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
        scaleAnimation.duration = 1.8
        scaleAnimation.fromValue = 0
        scaleAnimation.toValue = 1
        scaleAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
        scaleAnimation.repeatCount = .greatestFiniteMagnitude
        animatedLayers[index].add(scaleAnimation, forKey: "scale")

        let opacityAnimation = CABasicAnimation(keyPath: #keyPath(CALayer.opacity))
        opacityAnimation.duration = 1.8
        opacityAnimation.fromValue = 0.7
        opacityAnimation.toValue = 0
        opacityAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
        opacityAnimation.repeatCount = .greatestFiniteMagnitude
        animatedLayers[index].add(opacityAnimation, forKey: "opacity")
    }
}
0 голосов
/ 30 марта 2020

Пробовали ли вы начать с заливки как можно более прозрачной, а затем сделать ее серой в начале анимации?

    // Animated circle
    for _ in 0...3 {
        let animatedPath = UIBezierPath(arcCenter: .zero, radius: self.layer.bounds.size.width / 2.3,
        startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
        let animatedLayer = CAShapeLayer()

        animatedLayer.path = animatedPath.cgPath
        animatedLayer.strokeColor = UIColor.black.cgColor
        animatedLayer.lineWidth = 0
        animatedLayer.fillColor = UIColor.clear.cgColor
        animatedLayer.lineCap = CAShapeLayerLineCap.round
        animatedLayer.position = CGPoint(x: self.layer.bounds.size.width / 2, y: self.layer.bounds.size.width / 2)
        self.layer.addSublayer(animatedLayer)
        animatedLayers.append(animatedLayer)
    }

    // Dispatch animation for circle _ 0...3
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {

        self.animateCircle(index: 0)
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
            self.animateCircle(index: 1)
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
                self.animateCircle(index: 2)
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
                    self.animateCircle(index: 3)
                }
            }
        }
    }
}
...