Search code examples
iosswiftpositioncashapelayercabasicanimation

Incorrect position of CAShapeLayer


I have a UIView called viewProgress. It is the white box in the image. I want a circular progress bar, which is the green circle in the image. The progress bar should stay within the white box, but as you can see it is way off.

How do I make it stay inside the viewProgress? Here you have my animation function:

func animateView() {

        let circle = viewProgress // viewProgress is a UIView

        var progressCircle = CAShapeLayer()

        let circlePath = UIBezierPath(arcCenter: circle.center, radius: circle.bounds.midX, startAngle: -CGFloat(M_PI_2), endAngle: CGFloat(3.0 * M_PI_2), clockwise: true)

        progressCircle = CAShapeLayer ()
        progressCircle.path = circlePath.CGPath
        progressCircle.strokeColor = UIColor.whiteColor().CGColor
        progressCircle.fillColor = UIColor.clearColor().CGColor
        progressCircle.lineWidth = 10.0

        circle.layer.addSublayer(progressCircle)

        let animation = CABasicAnimation(keyPath: "strokeEnd")
        animation.fromValue = 0
        animation.toValue = 0.4
        animation.duration = 1
        animation.fillMode = kCAFillModeForwards
        animation.removedOnCompletion = false

        progressCircle.addAnimation(animation, forKey: "ani")
    }

And I'm just calling the function inside viewDidLoad()

enter image description here


Solution

  • Basically u have few problems:

    1. incorrect position of your layer - frame should be equal to bounds of parent layer in parentView
    2. you animation will play only once - if u want to make it infinite add something like animation.repeatCount = MAXFLOAT
    3. to make sure that all size correct - in layoutSubviews recreate layers, with removing previously created one
    4. UIBezierPath(arcCenter... will start path from rightSide, u can also use simpler variant like UIBezierPath(ovalInRect. With this in mind u also need to rotate layer for M_PI_2 if u want to make start of drawing in the very top of circle
    5. U should also take in mind that with line width 10, half of u'r circle path will be catted by clipSubviews of parentView. To prevent this use modified parentLayer rect
    6. U can also maybe want to improve visualization of lineCap, buy setting it to "round" style
    7. progressCircle.addAnimation(animation, forKey: "ani") key for anim not required if u dont want to get completion event from animation - i your case i see there is no delegate and no removeOnCompletion flag setted to false, so make assumption that u don`t actually need it

    Keeping this all in mind code can be like :

    let circle = UIView(frame: CGRectMake(100, 100, 100, 100)) // viewProgress is a UIView
    circle.backgroundColor = UIColor.greenColor()
    view.addSubview(circle)
    
    var progressCircle = CAShapeLayer()
    progressCircle.frame = view.bounds
    
    let lineWidth:CGFloat = 10
    let rectFofOval = CGRectMake(lineWidth / 2, lineWidth / 2, circle.bounds.width - lineWidth, circle.bounds.height - lineWidth)
    
    let circlePath = UIBezierPath(ovalInRect: rectFofOval)
    
    progressCircle = CAShapeLayer ()
    progressCircle.path = circlePath.CGPath
    progressCircle.strokeColor = UIColor.whiteColor().CGColor
    progressCircle.fillColor = UIColor.clearColor().CGColor
    progressCircle.lineWidth = 10.0
    progressCircle.frame = view.bounds
    progressCircle.lineCap = "round"
    
    circle.layer.addSublayer(progressCircle)
    circle.transform = CGAffineTransformRotate(circle.transform, CGFloat(-M_PI_2))
    
    let animation = CABasicAnimation(keyPath: "strokeEnd")
    animation.fromValue = 0
    animation.toValue = 1.1
    animation.duration = 1
    animation.repeatCount = MAXFLOAT
    animation.fillMode = kCAFillModeForwards
    animation.removedOnCompletion = false
    animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
    
    progressCircle.addAnimation(animation, forKey: nil)
    

    and result actually like:

    enter image description here

    Note: If u want to animate rotation of animated layer u should also add additional animation in to anim group