Muhammad Umer
Muhammad Umer

Reputation: 78

iOS Swift - Animate drawing of arc filled, not the border stroke

I am trying to Implement drawing of Arc from certain start angle to certain end angle. I am trying to animate drawing of arc but have no luck.

I have looked for couple of implementations but all of them refers to be drawing of animating boder of circle.

Like drawing a pie but with one color. With certain start and end angle. Animated drawing of pie.

What I like is to have have animated filling while arc is being animated from start angle to end angle.

Like arc is being drawn clockwise and while drawing it is filling its inside color.

Any idea How can I acheive this in Swift?

Thanks in advance.

Edit

This is what I have drawn:

(gray color shows area covered by drawing pie)

enter image description here

Code for drawing Pie

import Foundation
import UIKit

@IBDesignable class PieChart : UIView {

    @IBInspectable var percentage: CGFloat = 50 {
        didSet {
            self.setNeedsDisplay()
        }
    }

    @IBInspectable var outerBorderWidth: CGFloat = 2 {
        didSet {
            self.setNeedsDisplay()
        }
    }

    @IBInspectable var outerBorderColor: UIColor = UIColor.white {
        didSet {
            self.setNeedsDisplay()
        }
    }

    @IBInspectable var percentageFilledColor: UIColor = UIColor.primary {
        didSet {
            self.setNeedsDisplay()
        }
    }

    let circleLayer = CAShapeLayer()

    override func draw(_ rect: CGRect) {
        drawPie(rect: rect, endPercent: percentage, color: UIColor.primary)
    }

    func drawPie(rect: CGRect, endPercent: CGFloat = 70, color: UIColor) {
        let center = CGPoint(x: rect.origin.x + rect.width / 2, y: rect.origin.y + rect.height / 2)
        let radius = min(rect.width, rect.height) / 2

        let gpath = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: CGFloat(360.degreesToRadians), clockwise: true)
        UIColor.white.setFill()
        gpath.fill()

        let circlePath = UIBezierPath(arcCenter: center, radius: radius, startAngle: CGFloat(0), endAngle:CGFloat(360.degreesToRadians), clockwise: true)

        circleLayer.path = circlePath.cgPath

        circleLayer.fillColor = UIColor.clear.cgColor
        circleLayer.strokeColor = outerBorderColor.cgColor
        circleLayer.lineWidth = outerBorderWidth
        circleLayer.borderColor = outerBorderColor.cgColor

        self.layer.addSublayer(circleLayer)

        let π: CGFloat = 3.14

        let halfPi: CGFloat = π / 2

        let startPercent: CGFloat = 0.0

        let startAngle = (startPercent / 100 * π  * 2 - π ) + halfPi
        let endAngle = (endPercent / 100 * π  * 2 - π ) + halfPi

        let path = UIBezierPath()
        path.move(to: center)
        path.addArc(withCenter: center, radius: radius, startAngle: CGFloat(startAngle), endAngle: CGFloat(endAngle), clockwise: true)
        path.close()
        percentageFilledColor.setFill()
        path.fill()
    }
}

Upvotes: 1

Views: 1986

Answers (1)

Danilo Gomes
Danilo Gomes

Reputation: 757

I've created this code using playground and swift. This code will draw a pie chart animating the filling color. Make sure you have the Timeline pane open on Xcode to see the animation working. I couldn't find a way to pass the path function directly to CAAnimation so my workaround was to create a list of steps, each one containing the path for a percentage stage. You can change the steps for a smother animation.

//: Playground - noun: a place where people can play

import UIKit
import XCPlayground

let STEPS_ANIMATION = 50

let initialPercentage : CGFloat = 0.10
let finalPercentage : CGFloat = 0.75


func buildPiePath(frame : CGRect, percentage : CGFloat) -> UIBezierPath {

    let newPath = UIBezierPath()

    let startPoint = CGPoint(x: frame.size.width, y: frame.height / 2.0)
    let centerPoint = CGPoint(x: frame.size.width / 2.0, y: frame.height / 2.0)
    let startAngle : CGFloat = 0
    let endAngle : CGFloat = percentage * 2 * CGFloat(M_PI)

    newPath.move(to: centerPoint)
    newPath.addLine(to: startPoint)
    newPath.addArc(withCenter: centerPoint,
                   radius: frame.size.width / 2.0,
                   startAngle: startAngle,
                   endAngle: endAngle,
                   clockwise: true)
    newPath.addLine(to: centerPoint)
    newPath.close()
    return newPath
}

func buildPiePathList(frame: CGRect,
                      startPercentage: CGFloat,
                      finalPercentage: CGFloat) -> [AnyObject] {

    var listValues = [AnyObject]()

    for index in 1...STEPS_ANIMATION {

        let delta = finalPercentage - startPercentage
        let currentPercentage = CGFloat(index) / CGFloat(STEPS_ANIMATION)

        let percentage =  CGFloat(startPercentage + (delta * currentPercentage))
        listValues.append(buildPiePath(frame: frame,
                                       percentage: percentage)
            .cgPath)

    }

    return listValues
}




// Container for pie chart

let container = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
container.backgroundColor = UIColor.gray
XCPShowView(identifier: "Container View", view: container)

let circleFrame = CGRect(x: 20, y: 20, width: 360, height: 360)



// Red background
let background = CAShapeLayer()
background.frame = circleFrame
background.fillColor = UIColor.red.cgColor
background.path = buildPiePath(frame: circleFrame, percentage: 1.0).cgPath

container.layer.addSublayer(background)




// Green foreground that animates
let foreground = CAShapeLayer()
foreground.frame = circleFrame
foreground.fillColor = UIColor.green.cgColor
foreground.path = buildPiePath(frame: circleFrame, percentage: initialPercentage).cgPath

container.layer.addSublayer(foreground)




// Filling animation
let fillAnimation = CAKeyframeAnimation(keyPath: "path")
fillAnimation.values = buildPiePathList(frame: circleFrame,
                                        startPercentage: initialPercentage,
                                        finalPercentage: finalPercentage)
fillAnimation.duration = 3
fillAnimation.calculationMode = kCAAnimationDiscrete
foreground.add(fillAnimation, forKey:nil)

Upvotes: 1

Related Questions