Animate your iOS app with particle systems



Introduction

Apple introduced with iOS 5 an entire family of specialized layers, subclasses of the CALayer class: CAGradientLayer, CAShapeLayer, CAEmitterLayer.

Each of those classes has been designed for a very specific task. Particles can be used to create real-time animations for effects like fire, rain, snow, etc.

Wait… Particle what?

Well, It’s not that unknown. You’ve probably seen it already in many iOS apps.

The Keynote mac application has also a fire animation

Facebook use it for its Facebook Live feature

Particle animations are made up of two components: CAEmitterLayer, and CAEmitterCell.

I like to think that it’s like a bread. You have the bread, which the the CAEmitterLayer and the breadcrumb, the CAEmitterCells.

CAEmitterLayer acts as a container for a collection of CAEmitterCell. CAEmitterCell determines how it looks and moves, how fast to create, how long the particles should live.

I think this is a really cool Core Animation tool you can use in your projects.

Code

I created an Animable protocol so every animation we want to create need to be conform to :

protocol Animable {  
    var icons: [UIImage] { get }
    var orientation: Double { get }
    var position: CGPoint { get }
    var shape: String { get }
    var size: CGSize { get }
    var renderMode: String { get }

    /// The color tint to apply to the contents
    var colors: [UIColor] { get }

    /// The number of emitted particles per second
    var birthRate: Float { get }

    /// It seems to be the speed at a given moment. So the bigger the value, the quicker the animation will be
    var velocity: CGFloat { get }

}

extension Animable {  
    var shape: String {
        return kCAEmitterLayerLine
    }

    var colors: [UIColor] {
        return []
    }

    var position: CGPoint {
        return .zero
    }

    var renderMode: String {
        return kCAEmitterLayerUnordered
    }
}

And this is what an animation like the Snow looks like :

struct SnowAnimation: Animable {

    static let snowIcons = [#imageLiteral(resourceName: "snowing")]

    var icons: [UIImage] {
        return SnowAnimation.snowIcons
    }

    var orientation: Double {
        return M_PI
    }

    var position: CGPoint {
        return CGPoint(x: UIScreen.main.bounds.width/2.0, y: 0)
    }

    var size: CGSize {
        return CGSize(width: UIScreen.main.bounds.size.width, height: 1)
    }

    var birthRate: Float {
        return 5
    }

    var velocity: CGFloat {
        return 120
    }

}

Behind the scene, those are the few lines of codes you need to write :

extension Animable {

    func emitter() -> CAEmitterLayer {

        let emitter = CAEmitterLayer()

        emitter.emitterShape    = self.shape
        emitter.emitterPosition = self.position
        emitter.emitterSize     = self.size
        emitter.renderMode      = self.renderMode
        emitter.emitterCells    = self.icons.map { emitterCell(withImage: $0) }

        if self.colors.isEmpty {
            emitter.emitterCells  = self.icons.map { emitterCell(withImage: $0) }

        } else {
            emitter.emitterCells  = self.colors.enumerated().map { emitterCell(withImage: self.icons.first!, usingColor: $0.element) }
        }

        return emitter
    }

    func emitterCell(withImage image: UIImage, usingColor color: UIColor? = nil) -> CAEmitterCell {

        let emitterCell = CAEmitterCell()

        emitterCell.birthRate         = self.birthRate
        emitterCell.lifetime          = 7
        emitterCell.velocity          = self.velocity // the bigger the value is, the quicker the fruit move
        emitterCell.emissionLongitude = CGFloat(self.orientation)
        emitterCell.emissionRange     = CGFloat(M_1_PI) // 🤡😐
        emitterCell.contents          = image.cgImage
        emitterCell.scaleSpeed        = -0.1 // The bigger the value is, the quicker the fruit size grow

        if let color = color {
            emitterCell.color = color.cgColor
        }

        return emitterCell
    }
}

And in your view controller, you add :

let animationType = SnowAnimation()  
let emitterLayer = animationType.emitter()  
view.layer.addSublayer(emitterLayer)