Fattie
Fattie

Reputation: 12648

iOS, how to continuously animate a line "running" ("marching ants" effect)?

I must admit I have no clue how to do this in iOS -

Here's some code that makes a nice dotted line:

enter image description here

Now, I want that line to "run" upwards:

So, every one second it will move upwards by, itemLength * 2.0.

Of course, it would wrap around top to bottom.

So, DottedVertical should just do this completely on its own.

Really, how do you do this in iOS?

It would be great if the solution is general and will "scroll" any I suppose layer or drawn thing.

In say a game engine it's trivial, you just animate the offset of the texture. Can you perhaps offset the layer, or something, in iOS?

What's the best way?

I guess you'd want to use the GPU (layer animation right?) to avoid melting the cpu.

@IBDesignable class DottedVertical: UIView {

    @IBInspectable var dotColor: UIColor = UIColor.faveColor

    override func draw(_ rect: CGRect) {

        // say you want 8 dots, with perfect fenceposting:
        let totalCount = 8 + 8 - 1
        let fullHeight = bounds.size.height
        let width = bounds.size.width
        let itemLength = fullHeight / CGFloat(totalCount)
        let beginFromTop = !lowerHalfOnly ? 0.0 : (fullHeight * 8.0 / 15.0)
        let top = CGPoint(x: width/2, y: beginFromTop)
        let bottom = CGPoint(x: width/2, y: fullHeight)

        let path = UIBezierPath()
        path.move(to: top)
        path.addLine(to: bottom)
        path.lineWidth = width
        let dashes: [CGFloat] = [itemLength, itemLength]
        path.setLineDash(dashes, count: dashes.count, phase: 0)
        dotColor.setStroke()
        path.stroke()
}

(Bonus - if you had a few of these on screen, they'd have to be synced of course. There'd need to be a "sync" call that starts the running animation, so you can start them all at once with a notification or other message.)

Upvotes: 4

Views: 1034

Answers (1)

Fattie
Fattie

Reputation: 12648

Hate to answer my own question, here's a copy and paste solution based on the Men's suggestions above!

Good one! Superb effect...

@IBDesignable class DottedVertical: UIView {

    @IBInspectable var dotColor: UIColor = sfBlack6 { didSet {setup()} }

    override func layoutSubviews() { setup() }

    var s:CAShapeLayer? = nil

    func setup() {
        // say you want 8 dots, with perfect fenceposting:
        - calculate exactly as in the example in the question above -

        // marching ants...

        if (s == nil) {
            s = CAShapeLayer()
            self.layer.addSublayer(s!)
        }

        s!.strokeColor = dotColor.cgColor
        s!.fillColor = backgroundColor?.cgColor
        s!.lineWidth = width
        let ns = NSNumber(value: Double(itemLength))
        s!.lineDashPattern = [ns, ns]

        let path = CGMutablePath()
        path.addLines(between: [top, bottom])
        s!.path = path

        let anim = CABasicAnimation(keyPath: "lineDashPhase")
        anim.fromValue = 0
        anim.toValue = ns + ns
        anim.duration = 1.75 // seconds
        anim.repeatCount = Float.greatestFiniteMagnitude

        s!.add(anim, forKey: nil)

        self.layer.addSublayer(s!)
    }
}

Upvotes: 3

Related Questions