动画大部分时间都可以工作,但是一段时间后,发射器动画(对于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)
}
}