Анимация работает большую часть времени, но через некоторое время анимация излучателя (для 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)
}
}