da1lbi3 da1lbi3 - 3 months ago 14
Swift Question

Draw only half of circle

I want to draw only the left or right half of the circle.

I can draw the circle from 0 to 0.5 (right side) with no problems. But drawing the circle from 0.5 to 1 (left side) doesn't work.

call:

addCircleView(circle, isForeground: false, duration: 0.0, fromValue: 0.5, toValue: 1.0)


This is my code:

func addCircleView( myView : UIView, isForeground : Bool, duration : NSTimeInterval, fromValue: CGFloat, toValue : CGFloat ) -> CircleView {
let circleWidth = CGFloat(280)
let circleHeight = circleWidth

// Create a new CircleView
let circleView = CircleView(frame: CGRectMake(0, 0, circleWidth, circleHeight))

//Setting the color.
if (isForeground == true) {

circleView.setStrokeColor((UIColor(hexString: "#ffefdb")?.CGColor)!)
} else {
// circleLayer.strokeColor = UIColor.grayColor().CGColor
//Chose to use hexes because it's much easier.
circleView.setStrokeColor((UIColor(hexString: "#51acbc")?.CGColor)!)
}

myView.addSubview(circleView)

//Rotate the circle so it starts from the top.
circleView.transform = CGAffineTransformMakeRotation(-1.56)

// Animate the drawing of the circle
circleView.animateCircleTo(duration, fromValue: fromValue, toValue: toValue)

return circleView

}


Circle view class

import UIKit
extension UIColor {
/// UIColor(hexString: "#cc0000")
internal convenience init?(hexString:String) {
guard hexString.characters[hexString.startIndex] == Character("#") else {
return nil
}
guard hexString.characters.count == "#000000".characters.count else {
return nil
}
let digits = hexString.substringFromIndex(hexString.startIndex.advancedBy(1))
guard Int(digits,radix:16) != nil else{
return nil
}
let red = digits.substringToIndex(digits.startIndex.advancedBy(2))
let green = digits.substringWithRange(Range<String.Index>(digits.startIndex.advancedBy(2)..<digits.startIndex.advancedBy(4)))
let blue = digits.substringWithRange(Range<String.Index>(digits.startIndex.advancedBy(4)..<digits.startIndex.advancedBy(6)))
let redf = CGFloat(Double(Int(red, radix:16)!) / 255.0)
let greenf = CGFloat(Double(Int(green, radix:16)!) / 255.0)
let bluef = CGFloat(Double(Int(blue, radix:16)!) / 255.0)
self.init(red: redf, green: greenf, blue: bluef, alpha: CGFloat(1.0))
}
}

class CircleView: UIView {

var circleLayer: CAShapeLayer!
var from : CGFloat = 0.0;


override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clearColor()

// Use UIBezierPath as an easy way to create the CGPath for the layer.
// The path should be the entire circle.
let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(M_PI * 2.0), clockwise: true)

// Setup the CAShapeLayer with the path, colors, and line width
circleLayer = CAShapeLayer()
circleLayer.path = circlePath.CGPath
circleLayer.fillColor = UIColor.clearColor().CGColor

//I'm going to change this in the ViewController that uses this. Not the best way, I know but alas.
circleLayer.strokeColor = UIColor.redColor().CGColor

//You probably want to change this width
circleLayer.lineWidth = 3.0;

// Don't draw the circle initially
circleLayer.strokeEnd = 0.0

// Add the circleLayer to the view's layer's sublayers
layer.addSublayer(circleLayer)

}

func setStrokeColor(color : CGColorRef) {
circleLayer.strokeColor = color
}


// This is what you call if you want to draw a full circle.
func animateCircle(duration: NSTimeInterval) {
animateCircleTo(duration, fromValue: 0.0, toValue: 1.0)
}

// This is what you call to draw a partial circle.
func animateCircleTo(duration: NSTimeInterval, fromValue: CGFloat, toValue: CGFloat){
// We want to animate the strokeEnd property of the circleLayer
let animation = CABasicAnimation(keyPath: "strokeEnd")

// Set the animation duration appropriately
animation.duration = duration

// Animate from 0 (no circle) to 1 (full circle)
animation.fromValue = 0
animation.toValue = toValue

// Do an easeout. Don't know how to do a spring instead
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)

// Set the circleLayer's strokeEnd property to 1.0 now so that it's the
// right value when the animation ends.
circleLayer.strokeEnd = toValue

// Do the actual animation
circleLayer.addAnimation(animation, forKey: "animateCircle")
}


// required function
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

}

Answer

You need to set:

circleLayer.strokeStart = fromValue

in the animateCircleTo(duration...) function.

You set the end of the stroke, but not the beginning. Consequently, all circle animations begin from 0.0, even if you intend them to begin at a later part of the stroke.

Like this:

// This is what you call to draw a partial circle.
func animateCircleTo(duration: NSTimeInterval, fromValue: CGFloat, toValue: CGFloat){
    // We want to animate the strokeEnd property of the circleLayer
    let animation = CABasicAnimation(keyPath: "strokeEnd")

    // Set the animation duration appropriately
    animation.duration = duration

    // Animate from 0 (no circle) to 1 (full circle)
    animation.fromValue = 0
    animation.toValue = toValue

    // Do an easeout. Don't know how to do a spring instead
    animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)

    // Set the circleLayer's strokeEnd property to 1.0 now so that it's the
    // right value when the animation ends.
    circleLayer.strokeStart = fromValue
    circleLayer.strokeEnd = toValue

    // Do the actual animation
    circleLayer.addAnimation(animation, forKey: "animateCircle")
}