Код: Выделить всё
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
}
}
Проверьте скриншот. Есть два излучателя, но только один излучает. Редко они не излучают оба.>
Подробнее здесь: https://stackoverflow.com/questions/797 ... -particles