DispatchQueue.main,CAEmitterCell,CACurrentMediaTime的问题,发射器动画在最初的几秒钟内随机延迟或不可见

时间:2018-10-24 06:58:36

标签: ios swift core-animation grand-central-dispatch caemitterlayer

动画大部分时间都可以工作,但是一段时间后,发射器动画(对于ConfettiView)开始延迟,并且只发射通常发射的一部分。就像累了。无法可靠地重新创建它,并且已经将其追逐了将近一年。

在模拟器和设备上均发生。 我已经清除DerivedData/,清除了模拟器的所有内容和设置,五彩纸屑在模拟器上仍然很累。

然后在真实设备上正确显示五彩纸屑。然后几分钟或几小时后,会开始变得疲倦。几分钟或几小时后再恢复正常。每隔几天,它就会累一次,然后在我宣誓就职于我们的黑暗霸主之后的几周内再也不会累。我尝试重新安装该应用程序,调用layer.setNeedsLayout(),在动画制作之前在背景中创建图像,但是纸屑仍然很累。

我怀疑是CACurrentMediaTime,因为this article描述的时间不准确,但是即使不使用它,五彩纸屑仍然很累。

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

0 个答案:

没有答案