Ошибка с `DispatchQueue.main`,` CAEmitterCell`, `CACurrentMediaTime`, анимация эмиттера случайно запаздывает или не видна в течение первых нескольких секунд - PullRequest
0 голосов
/ 24 октября 2018

Анимация работает большую часть времени, но через некоторое время анимация излучателя (для ConfettiView) запускается поздно и испускает только часть того, что обычно излучается.Как будто это устает.Не могу воссоздать его надежно, и вот уже почти год гонюсь за ним.

Бывает как на симуляторе, так и на устройстве.Я очистил DerivedData/, удалил все содержимое и настройки для симулятора, конфетти все еще устал на симуляторе.

Затем на реальном устройстве конфетти отображается правильно.Затем через несколько минут или часов начнут уставать.Затем снова через несколько минут или часов.Каждые несколько дней он устает один раз, а потом уже несколько недель после того, как я поклялся нашим темным повелителям.Я пытался переустановить приложение, вызывая layer.setNeedsLayout(), создавая изображения в фоновом режиме перед анимацией, но конфетти все еще устает.

Я подозреваю, что это CACurrentMediaTime из-за этой статьи , описывающейнеточные времена, но даже без использования конфетти все равно устает.

/// Displays colorful confetti falling from the top.
class ConfettiView: UIView {

    /// The different shapes of confetto to draw.
    @objc public enum ConfettiShape: Int {
        case rectangle, circle, triangle, spiral
    }

    /// The time to emit confetti. Confetti may still be falling up to 3 seconds after duration.
    @IBInspectable @objc
    public var duration: Double = 2

    /// The size to draw confetti `shapes` for `content`.
    @IBInspectable @objc
    public var size: CGSize = CGSize(width: 9, height: 6)

    /// Confetti shapes of `size` will be drawn for `content`.
    public var shapes: [ConfettiShape] = [.rectangle, .rectangle, .circle, .triangle, .spiral]

    /// Confetto content are emitted for all `colors`.
    public var content: [CGImage]?

    /// Colors to emit confetto `content`.
    @objc
    public var colors: [UIColor] = [UIColor.from(rgb: "4d81fb", alpha: 0.8) ?? UIColor.purple,
                                    UIColor.from(rgb: "4ac4fb", alpha: 0.8) ?? UIColor.blue,
                                    UIColor.from(rgb: "9243f9", alpha: 0.8) ?? UIColor.purple,
                                    UIColor.from(rgb: "fdc33b", alpha: 0.8) ?? UIColor.orange,
                                    UIColor.from(rgb: "f7332f", alpha: 0.8) ?? UIColor.red ]

    var burstEmitter: CAEmitterLayer?
    var showerEmitter: CAEmitterLayer?

    /// Start a confetti effect sequence.
    ///
    ///
    /// Confetti is emitted in 2 phases, a short burst followed by a shower until `duration` is over.
    override public func start(completion: @escaping () -> Void = {}) {
        if content == nil {
            content = shapes.map({$0.createImage(size: size)}) // uses `CGContext` to draw images
        }
        confettiBurst {
            self.confettiShower {
                completion()
            }
        }
    }

    fileprivate let burstDuration = 0.8
    fileprivate var showerDuration: Double { return max(0, duration - burstDuration) }

    func confettiBurst(startedHandler: @escaping () -> Void) {
        /* Create bursting confetti */
        let burstEmitter = CAEmitterLayer()
        self.burstEmitter  = burstEmitter
        self.setEmitterPositionAndSize(burstEmitter)

        var cells: [CAEmitterCell] = []
        for confettoImage in self.content ?? [] {
            for color in colors {
                let cell = CAEmitterCell()
                cell.contents = confettoImage.copy()
                cell.color = color.cgColor
                cell.setValuesForBurstPhase1()
                cells.append(cell)
            }
        }
        burstEmitter.emitterCells = cells

        /* Start showing the confetti */
        burstEmitter.beginTime = CACurrentMediaTime()
        self.layer.addSublayer(burstEmitter)
        self.layer.setNeedsLayout()
        startedHandler()

        /* Remove the burst effect */
        DispatchQueue.main.asyncAfter(deadline: .now() + burstDuration / 2.0) {
            if let cells = burstEmitter.emitterCells {
                for cell in cells {
                    cell.setValuesForBurstPhase2()
                }
            }

            /* Remove the confetti emitter */
            DispatchQueue.main.asyncAfter(deadline: .now() + self.burstDuration / 2.0) {
                burstEmitter.birthRate = 0

                let delay = TimeInterval(burstEmitter.emitterCells?.first?.lifetimeMax ?? 0) // it's always 3.0
                DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
                    burstEmitter.removeFromSuperlayer()
                    self.burstEmitter = nil
                }
            }
        }
    }

    func confettiShower(completion: @escaping () -> Void) {
        /* Create showering confetti */
        let showerEmitter = CAEmitterLayer()
        self.showerEmitter = showerEmitter
        self.setEmitterPositionAndSize(showerEmitter)

        var cells: [CAEmitterCell] = []
        for confettoImage in self.content ?? [] {
            for color in colors {
                let cell = CAEmitterCell()
                cell.contents = confettoImage.copy()
                cell.color = color.cgColor
                cell.setValuesForShower()
                cells.append(cell)
                /* Create some blurred confetti for depth perception */
                let rand = Int(arc4random_uniform(2))
                if rand != 0 {
                    let blurredCell = CAEmitterCell()
                    blurredCell.contents = confettoImage.blurImage(radius: rand)
                    blurredCell.color = color.cgColor
                    blurredCell.setValuesForShowerBlurred(scale: rand)
                    cells.append(blurredCell)
                }
            }
        }
        showerEmitter.emitterCells = cells

        /* Start showing the confetti */
        showerEmitter.beginTime = CACurrentMediaTime()
        self.layer.addSublayer(showerEmitter)
        self.layer.setNeedsLayout()

        /* Remove the confetti emitter */
        DispatchQueue.main.asyncAfter(deadline: .now() + showerDuration) {
            showerEmitter.birthRate = 0
            let delay = TimeInterval(showerEmitter.emitterCells?.first?.lifetimeMax ?? 0) // it's always 3.0
            DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
                showerEmitter.removeFromSuperlayer()
                self.showerEmitter = nil
                completion()
            }
        }
    }

    open override func layoutSublayers(of layer: CALayer) {
        super.layoutSublayers(of: layer)

        if let emitter = burstEmitter {
            setEmitterPositionAndSize(emitter)
        }
        if let emitter = showerEmitter {
            setEmitterPositionAndSize(emitter)
        }
    }

    fileprivate func setEmitterPositionAndSize(_ emitter: CAEmitterLayer) {
        emitter.emitterPosition = CGPoint(x: bounds.width / 2, y: -30)
        emitter.emitterShape = .line
        emitter.emitterSize = CGSize(width: bounds.width, height: 0)
    }
}
...