2

Basically I am wanting to create a timer app. That's why I need a circle to show progress of timer. if timer is for 10 sec the circle will be divided into 10 segment and each segment will be appeared after one second. Ok fine.

I have successfully created it from SO post -> draw-a-circular-segment-progress-in-swift but a little bit problem. My circle's segment begins from fourth quadrent but it is not expected. I want to show the segment from first quadrent. Here I have created this function for 8 segments.

Please provide suggestion that can be used dynamically.

 func createCircle(){

        let circlePath = UIBezierPath(arcCenter: CGPoint(x: view.frame.size.width/2,y: view.frame.size.height/2), radius: CGFloat(90), startAngle: CGFloat(0), endAngle:CGFloat(M_PI * 2), clockwise: true)


        let segmentAngle: CGFloat = (360 * 0.125) / 360

        for i in 0 ..< 8 {

            let circleLayer = CAShapeLayer()
            circleLayer.path = circlePath.CGPath

            // start angle is number of segments * the segment angle
            circleLayer.strokeStart = segmentAngle * CGFloat(i) //I think all is for this line of code.

            print("\nStroke \(i): ",segmentAngle * CGFloat(i))

            // end angle is the start plus one segment, minus a little to make a gap
            // you'll have to play with this value to get it to look right at the size you need
            let gapSize: CGFloat = 0.008

            circleLayer.strokeEnd = circleLayer.strokeStart + segmentAngle - gapSize

            circleLayer.lineWidth = 10

            circleLayer.strokeColor = UIColor(red:0,  green:0.004,  blue:0.549, alpha:1).CGColor
            circleLayer.fillColor = UIColor.clearColor().CGColor

            // add the segment to the segments array and to the view
            segments.insert(circleLayer, atIndex: i)

        }

        for i in 0 ..< 8{
            view.layer.addSublayer(segments[i])
        }
    }
Community
  • 1
  • 1
KhanShaheb
  • 714
  • 2
  • 11
  • 26

3 Answers3

7

You must offset your start angle:

circleLayer.strokeStart = segmentAngle * CGFloat(i) - M_PI / 2.0

While a transformation could also do the trick, I would not recommend using one is this case. If you later want to do a transformation for another reason (animation, etc) then you would might also have to account for any current transformations, which potentially makes things more complicated down the road.

Edit:

I think some of your other code might be off as well. Rather than figure out what's going on, I went ahead and rewrote it from scratch (in Swift 3 syntax):

let count: Int = 8
let gapSize: CGFloat = 0.008
let segmentAngleSize: CGFloat = (2.0 * CGFloat(M_PI) - CGFloat(count) * gapSize) / CGFloat(count)
let center = CGPoint(x: view.frame.size.width / 2.0, y: view.frame.size.height / 2.0)
let radius: CGFloat = 90
let lineWidth: CGFloat = 10
let strokeColor = UIColor(red:0,  green:0.004,  blue:0.549, alpha:1).cgColor

for i in 0 ..< count {
    let start = CGFloat(i) * (segmentAngleSize + gapSize) - CGFloat(M_PI / 2.0)
    let end = start + segmentAngleSize
    let segmentPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: start, endAngle: end, clockwise: true)

    let arcLayer = CAShapeLayer()
    arcLayer.path = segmentPath.cgPath
    arcLayer.fillColor = UIColor.clear.cgColor
    arcLayer.strokeColor = strokeColor
    arcLayer.lineWidth = lineWidth

    self.view.layer.addSublayer(arcLayer)
}

example code screenshot

Jake
  • 13,097
  • 9
  • 44
  • 73
  • I used your line but there is no segment. so i printed your value and it shows negative value but the value will be [0,1].....That's why its not working. – KhanShaheb Nov 21 '16 at 01:04
2

Swift 4 version based on @Jake answer.

class SegmentView: UIView {
    @IBInspectable var segmentNumber: Int = 1
    @IBInspectable var lineWidth: CGFloat = 10.0
    @IBInspectable var strokeColor: UIColor = .red

    var gapSize: CGFloat = 0.07
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        let segmentAngleSize: CGFloat = (2.0 * CGFloat.pi - CGFloat(segmentNumber) * gapSize) / CGFloat(segmentNumber)
        let center = CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0)

        for i in 0..<segmentNumber {
            let start = CGFloat(i) * (segmentAngleSize + gapSize) - .pi / 2.0
            let end = start + segmentAngleSize
            let segmentPath = UIBezierPath(arcCenter: center, radius: self.frame.width / 2 - lineWidth / 2, startAngle: start, endAngle: end, clockwise: true)

            let arcLayer = CAShapeLayer()
            arcLayer.path = segmentPath.cgPath
            arcLayer.fillColor = UIColor.clear.cgColor
            arcLayer.strokeColor = strokeColor.cgColor
            arcLayer.lineWidth = lineWidth

            layer.addSublayer(arcLayer)
        }

    }
}
Arek
  • 375
  • 5
  • 5
0

Use a rotation transform to rotate the view / layer so that the zero point of the shape layer's path is where you want it.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/bk2ch12p566customThermometer/ch25p840customThermometer/MyCircularProgressButton.swift – matt Nov 20 '16 at 12:42