2
votes

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

3

3 Answers

1
votes

You can try using CAGradientLayer along with CAAnimation. Add gradient to your view:

override func viewDidAppear(animated: Bool) {

    self.gradient = CAGradientLayer()
    self.gradient?.frame = self.view.bounds
    self.gradient?.colors = [ UIColor.white.cgColor, UIColor.white.cgColor]
    self.view.layer.insertSublayer(self.gradient, atIndex: 0)
    animateLayer()
}

func animateLayer(){

    var fromColors = self.gradient.colors
    var toColors: [AnyObject] = [UIColor.blue.cgColor,UIColor.blue.cgColor]
    self.gradient.colors = toColors
    var animation : CABasicAnimation = CABasicAnimation(keyPath: "colors")

    animation.fromValue = fromColors
    animation.toValue = toColors
    animation.duration = 3.00
    animation.isRemovedOnCompletion = true
    animation.fillMode = CAMediaTimingFillMode.forwards
    animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
    animation.delegate = self

    self.gradient?.addAnimation(animation, forKey:"animateGradient")
}

and to continue gradient animation in cyclic order like:

override func animationDidStop(anim: CAAnimation!, finished flag: Bool) {
    self.toColors = self.fromColors;
    self.fromColors = self.gradient?.colors
    animateLayer()
}
0
votes

You could put your arrow inside a UIView object. You could then have the UIView on a timer where it changed the alpha properly. Depending on blinking or fading, nonetheless, you could use UIView Animations to complete your desired animation.

I say a uiview, instead of using the scrollview because you don’t want to hide the scrollview. So you’d put it inside a view in which is only a container for the bezierpath.

0
votes

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)

}