Anonymous
Caemitterlayer случайным образом не испускает частицы
Сообщение
Anonymous » 07 окт 2025, 22:40
Я реализую эффект конфетти с помощью частиц. Я использовал для этого CAEmitterLayer, и он работает почти хорошо, но иногда частицы не испускаются. Я старался изо всех сил и отладил его (установил цвета для слоев эмиттера) и знаю, что слои добавлены. Вот полный код:
Код: Выделить всё
import UIKit
final class ViewController: UIViewController {
// MARK: - UI
private lazy var actionButton: UIButton = { [weak self] in
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("Start confetti", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .systemBlue
button.layer.cornerRadius = 16
button.addAction(UIAction { _ in
self?.startConfettiShow()
}, for: .touchUpInside)
return button
}()
private let confettiContainer: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .white
view.clipsToBounds = true
return view
}()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .gray
view.addSubview(confettiContainer)
view.addSubview(actionButton)
NSLayoutConstraint.activate([
confettiContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor),
confettiContainer.bottomAnchor.constraint(equalTo: actionButton.topAnchor, constant: -50),
confettiContainer.widthAnchor.constraint(equalToConstant: 300),
confettiContainer.heightAnchor.constraint(equalToConstant: 300),
actionButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
actionButton.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 200),
actionButton.widthAnchor.constraint(equalToConstant: 150),
actionButton.heightAnchor.constraint(equalToConstant: 40),
])
}
// MARK: - Confetti Logic
private func startConfettiShow() {
// Remove old emitters
confettiContainer.layer.sublayers?.removeAll(where: { $0 is CAEmitterLayer })
view.layoutIfNeeded()
let xOffset: CGFloat = 90
let emissionOffset: CGFloat = 0.1
let left = makeEmitter(xOffset: -xOffset, emissionLongitudeOffset: emissionOffset)
let right = makeEmitter(xOffset: xOffset, emissionLongitudeOffset: -emissionOffset)
confettiContainer.layer.addSublayer(left)
confettiContainer.layer.addSublayer(right)
debugEmitter(left, color: .red)
debugEmitter(right, color: .yellow)
DispatchQueue.main.async {
CATransaction.begin()
CATransaction.setDisableActions(true)
let now = CACurrentMediaTime()
left.beginTime = now
right.beginTime = now
left.birthRate = 1
right.birthRate = 1
CATransaction.commit()
}
// Stop emission
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
left.birthRate = 0
right.birthRate = 0
}
// Remove the layers
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
left.removeFromSuperlayer()
right.removeFromSuperlayer()
}
}
private func makeEmitter(xOffset: CGFloat,
emissionLongitudeOffset: CGFloat) -> CAEmitterLayer {
let emitter = CAEmitterLayer()
emitter.frame = confettiContainer.bounds
emitter.emitterShape = .point
emitter.emitterPosition = CGPoint(
x: confettiContainer.bounds.midX + xOffset,
y: confettiContainer.bounds.maxY - 10
)
emitter.birthRate = 0
let cells: [CAEmitterCell] = ViewController.colors.flatMap { color in
ViewController.shapes.map { shape in
let cell = CAEmitterCell()
cell.birthRate = 15
cell.lifetime = 3.5
cell.velocity = .random(in: 250...400)
cell.velocityRange = 80
cell.emissionLongitude = -.pi / 2 + emissionLongitudeOffset
cell.emissionRange = .pi / 8
cell.yAcceleration = 200
cell.spin = .random(in: -3...3)
cell.spinRange = 4
cell.scale = .random(in: 0.08...0.14)
cell.alphaSpeed = -0.4
cell.color = color.cgColor
cell.contents = shape
return cell
}
}
emitter.emitterCells = cells
return emitter
}
private func debugEmitter(_ emitter: CAEmitterLayer, color: UIColor) {
emitter.borderColor = color.cgColor
emitter.borderWidth = 1
emitter.backgroundColor = color.withAlphaComponent(0.15).cgColor
print("Emitter frame:", emitter.frame)
print("Emitter position:", emitter.emitterPosition)
}
}
// MARK: - Static resources
private extension ViewController {
static let colors: [UIColor] = [
.systemPink, .systemYellow, .systemBlue, .systemGreen, .systemPurple
]
static let shapes: [CGImage] = [
CGImage.cgImageCircle(diameter: 36),
CGImage.cgImageSquare(side: 32),
CGImage.cgImageTriangle(size: 36),
].compactMap { $0 }
}
// MARK: - Shape generators
extension CGImage {
static func cgImageCircle(diameter: CGFloat) -> CGImage? {
let size = CGSize(width: diameter, height: diameter)
return UIGraphicsImageRenderer(size: size).image { ctx in
UIColor.white.setFill()
ctx.cgContext.fillEllipse(in: CGRect(origin: .zero, size: size))
}.cgImage
}
static func cgImageSquare(side: CGFloat) -> CGImage? {
let size = CGSize(width: side, height: side)
return UIGraphicsImageRenderer(size: size).image { ctx in
UIColor.white.setFill()
ctx.cgContext.fill(CGRect(origin: .zero, size: size))
}.cgImage
}
static func cgImageTriangle(size: CGFloat) -> CGImage? {
let s = CGSize(width: size, height: size)
return UIGraphicsImageRenderer(size: s).image { ctx in
UIColor.white.setFill()
let path = UIBezierPath()
path.move(to: CGPoint(x: s.width / 2, y: 0))
path.addLine(to: CGPoint(x: s.width, y: s.height))
path.addLine(to: CGPoint(x: 0, y: s.height))
path.close()
path.fill()
}.cgImage
}
}
Я настройка Betrintime и рождение После добавления излучателей в уровень просмотра я даже использовал catransaction.begin () и catransaction.commit () (советы CHATGPT), но это не сработало. Со всеми этими улучшениями он начал работать лучше, я вижу меньше излучателей, которые не работают, но все же иногда он не работает, поэтому элемент нестабилен. < /P>
Проверьте скриншот. Есть два излучателя, но только один излучает. Редко они не излучают оба.>
Подробнее здесь:
https://stackoverflow.com/questions/797 ... -particles
1759866030
Anonymous
Я реализую эффект конфетти с помощью частиц. Я использовал для этого CAEmitterLayer, и он работает почти хорошо, но иногда частицы не испускаются. Я старался изо всех сил и отладил его (установил цвета для слоев эмиттера) и знаю, что слои добавлены. Вот полный код: [code]import UIKit final class ViewController: UIViewController { // MARK: - UI private lazy var actionButton: UIButton = { [weak self] in let button = UIButton(type: .system) button.translatesAutoresizingMaskIntoConstraints = false button.setTitle("Start confetti", for: .normal) button.setTitleColor(.white, for: .normal) button.backgroundColor = .systemBlue button.layer.cornerRadius = 16 button.addAction(UIAction { _ in self?.startConfettiShow() }, for: .touchUpInside) return button }() private let confettiContainer: UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false view.backgroundColor = .white view.clipsToBounds = true return view }() // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .gray view.addSubview(confettiContainer) view.addSubview(actionButton) NSLayoutConstraint.activate([ confettiContainer.centerXAnchor.constraint(equalTo: view.centerXAnchor), confettiContainer.bottomAnchor.constraint(equalTo: actionButton.topAnchor, constant: -50), confettiContainer.widthAnchor.constraint(equalToConstant: 300), confettiContainer.heightAnchor.constraint(equalToConstant: 300), actionButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), actionButton.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 200), actionButton.widthAnchor.constraint(equalToConstant: 150), actionButton.heightAnchor.constraint(equalToConstant: 40), ]) } // MARK: - Confetti Logic private func startConfettiShow() { // Remove old emitters confettiContainer.layer.sublayers?.removeAll(where: { $0 is CAEmitterLayer }) view.layoutIfNeeded() let xOffset: CGFloat = 90 let emissionOffset: CGFloat = 0.1 let left = makeEmitter(xOffset: -xOffset, emissionLongitudeOffset: emissionOffset) let right = makeEmitter(xOffset: xOffset, emissionLongitudeOffset: -emissionOffset) confettiContainer.layer.addSublayer(left) confettiContainer.layer.addSublayer(right) debugEmitter(left, color: .red) debugEmitter(right, color: .yellow) DispatchQueue.main.async { CATransaction.begin() CATransaction.setDisableActions(true) let now = CACurrentMediaTime() left.beginTime = now right.beginTime = now left.birthRate = 1 right.birthRate = 1 CATransaction.commit() } // Stop emission DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { left.birthRate = 0 right.birthRate = 0 } // Remove the layers DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) { left.removeFromSuperlayer() right.removeFromSuperlayer() } } private func makeEmitter(xOffset: CGFloat, emissionLongitudeOffset: CGFloat) -> CAEmitterLayer { let emitter = CAEmitterLayer() emitter.frame = confettiContainer.bounds emitter.emitterShape = .point emitter.emitterPosition = CGPoint( x: confettiContainer.bounds.midX + xOffset, y: confettiContainer.bounds.maxY - 10 ) emitter.birthRate = 0 let cells: [CAEmitterCell] = ViewController.colors.flatMap { color in ViewController.shapes.map { shape in let cell = CAEmitterCell() cell.birthRate = 15 cell.lifetime = 3.5 cell.velocity = .random(in: 250...400) cell.velocityRange = 80 cell.emissionLongitude = -.pi / 2 + emissionLongitudeOffset cell.emissionRange = .pi / 8 cell.yAcceleration = 200 cell.spin = .random(in: -3...3) cell.spinRange = 4 cell.scale = .random(in: 0.08...0.14) cell.alphaSpeed = -0.4 cell.color = color.cgColor cell.contents = shape return cell } } emitter.emitterCells = cells return emitter } private func debugEmitter(_ emitter: CAEmitterLayer, color: UIColor) { emitter.borderColor = color.cgColor emitter.borderWidth = 1 emitter.backgroundColor = color.withAlphaComponent(0.15).cgColor print("Emitter frame:", emitter.frame) print("Emitter position:", emitter.emitterPosition) } } // MARK: - Static resources private extension ViewController { static let colors: [UIColor] = [ .systemPink, .systemYellow, .systemBlue, .systemGreen, .systemPurple ] static let shapes: [CGImage] = [ CGImage.cgImageCircle(diameter: 36), CGImage.cgImageSquare(side: 32), CGImage.cgImageTriangle(size: 36), ].compactMap { $0 } } // MARK: - Shape generators extension CGImage { static func cgImageCircle(diameter: CGFloat) -> CGImage? { let size = CGSize(width: diameter, height: diameter) return UIGraphicsImageRenderer(size: size).image { ctx in UIColor.white.setFill() ctx.cgContext.fillEllipse(in: CGRect(origin: .zero, size: size)) }.cgImage } static func cgImageSquare(side: CGFloat) -> CGImage? { let size = CGSize(width: side, height: side) return UIGraphicsImageRenderer(size: size).image { ctx in UIColor.white.setFill() ctx.cgContext.fill(CGRect(origin: .zero, size: size)) }.cgImage } static func cgImageTriangle(size: CGFloat) -> CGImage? { let s = CGSize(width: size, height: size) return UIGraphicsImageRenderer(size: s).image { ctx in UIColor.white.setFill() let path = UIBezierPath() path.move(to: CGPoint(x: s.width / 2, y: 0)) path.addLine(to: CGPoint(x: s.width, y: s.height)) path.addLine(to: CGPoint(x: 0, y: s.height)) path.close() path.fill() }.cgImage } } [/code] Я настройка Betrintime и рождение После добавления излучателей в уровень просмотра я даже использовал catransaction.begin () и catransaction.commit () (советы CHATGPT), но это не сработало. Со всеми этими улучшениями он начал работать лучше, я вижу меньше излучателей, которые не работают, но все же иногда он не работает, поэтому элемент нестабилен. < /P> Проверьте скриншот. Есть два излучателя, но только один излучает. Редко они не излучают оба.> Подробнее здесь: [url]https://stackoverflow.com/questions/79784879/caemitterlayer-randomly-doesnt-emit-particles[/url]