Search code examples
iosuikitcore-graphics

Creating an animating indicator arrow on iOS


I am trying to achieve a similar effect to the indicator arrow found in iOS's Music app, as you scroll past a certain point it changes from an arrow to a single line. I've attached a slowed down example below.

Example animation

The point I'm struggling to understand is the arrow itself, I've tried to use two rotated UIViews as lines positioned together with constraints and have successfully created an animation using UIViewPropertyAnimator and keyframes. But unfortunately the lines intrude over each other when going from an arrow to line.

I'm unsure of the correct method to achieve this example. Any push in the right direction to achieving this would be highly appreciated.


Solution

  • The simplest way is to animate the path of a CAShapeLayer. This gives you the rounded ends and the mitred join for free. You need a straight path and a bent path; switch from one to the other. The straight path must consist of two segments to match the two segments of the bent path.

    Here's my simulation of your animation:

    enter image description here

    Here's the configuration of the shape layer and its paths:

        let shape = CAShapeLayer()
        shape.frame = // ...        
        shape.lineCap = .round
        shape.lineJoin = .miter
        shape.lineWidth = 6
        shape.strokeColor = UIColor.gray.cgColor
        shape.fillColor = UIColor.clear.cgColor
    
        let p1 = UIBezierPath()
        p1.move(to: CGPoint(x:0, y:35))
        p1.addLine(to: CGPoint(x:20, y:35))
        p1.addLine(to: CGPoint(x:40, y:35))
        let straight = p1.cgPath
    
        let p2 = UIBezierPath()
        p2.move(to: CGPoint(x:0, y:25))
        p2.addLine(to: CGPoint(x:20, y:30))
        p2.addLine(to: CGPoint(x:40, y:25))
        let bent = p2.cgPath
    

    Then it's just a matter of alternating the path of shape between straight and bent as desired.