Search code examples
iosswiftuibezierpath

Blink Arrow draw with UIBezierPath


I have an UIScrollView which has a long text in it. I want to inform users that It has more content to read. Therefore, I added an arrow with UIBezierPath on bottom of it.

class ArrowView: UIView {
    var arrowPath: UIBezierPath!
    // Only override draw() if you perform custom drawing.
    // An empty implementation adversely affects performance during animation.
    override func draw(_ rect: CGRect) {
        // Drawing code
        self.drawArrow(from: CGPoint(x: rect.midX, y: rect.minY), to: CGPoint(x: rect.midX, y: rect.maxY),
                       tailWidth: 10, headWidth: 25, headLength: 20)

        //arrowPath.fill()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)

        self.backgroundColor = UIColor.darkGray
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    private func drawArrow(from start: CGPoint, to end: CGPoint, tailWidth: CGFloat, headWidth: CGFloat, headLength: CGFloat){
        let length = hypot(end.x - start.x, end.y - start.y)
        let tailLength = length - headLength

        func p(_ x: CGFloat, _ y: CGFloat) -> CGPoint { return CGPoint(x: x, y: y) }
        let points: [CGPoint] = [
            p(0, tailWidth / 2),
            p(tailLength, tailWidth / 2),
            p(tailLength, headWidth / 2),
            p(length, 0),
            p(tailLength, -headWidth / 2),
            p(tailLength, -tailWidth / 2),
            p(0, -tailWidth / 2)
        ]

        let cosine = (end.x - start.x) / length
        let sine = (end.y - start.y) / length
        let transform = CGAffineTransform(a: cosine, b: sine, c: -sine, d: cosine, tx: start.x, ty: start.y)

        let path = CGMutablePath()
        path.addLines(between: points, transform: transform)
        path.closeSubpath()

        arrowPath = UIBezierPath.init(cgPath: path)
    }
}

My question: How can I achieve a blink animation on arrow. Assume that It has a gradient layer from white to blue. It should start with white to blue then blue should seen on start point then white should start to seen on finish point of Arrow and this circle should continue.

On final, this animation should inform users that they can scroll the view.

How can I achieve this?

enter image description here


Solution

  • I solved it by using CABasicAnimation(). This first add gradient layer to custom UIView then apply mask on it then add animation.

    override func draw(_ rect: CGRect) {
        // Drawing code
        self.drawArrow(from: CGPoint(x: rect.midX, y: rect.minY), to: CGPoint(x: rect.midX, y: rect.maxY),
                       tailWidth: 10, headWidth: 20, headLength: 15)
    
        //arrowPath.fill()
    
        let startColor = UIColor(red:0.87, green:0.87, blue:0.87, alpha:1.0).cgColor
        let finishColor = UIColor(red:0.54, green:0.54, blue:0.57, alpha:1.0).withAlphaComponent(0.5).cgColor
    
        let gradient = CAGradientLayer()
        gradient.frame = self.bounds
        gradient.colors = [startColor,finishColor]
    
        let shapeMask = CAShapeLayer()
        shapeMask.path = arrowPath.cgPath
    
        gradient.mask = shapeMask
        let animation = CABasicAnimation(keyPath: "colors")
        animation.fromValue = [startColor, finishColor]
        animation.toValue = [finishColor, startColor]
        animation.duration = 2.0
        animation.autoreverses = true
        animation.repeatCount = Float.infinity
    
        //add the animation to the gradient
        gradient.add(animation, forKey: nil)
    
        self.layer.addSublayer(gradient)
    
    }