Reputation: 4519
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")
}
}
Upvotes: 2
Views: 3158
Reputation: 9649
In swift ui. Draws a clock wise arc, with a progress:
This is the implementation:
struct ArcShape: Shape {
var width: CGFloat
var height: CGFloat
var progress: Double
func path(in rect: CGRect) -> Path {
let bezierPath = UIBezierPath()
let endAngle = 360.0 * progress - 90.0
bezierPath.addArc(withCenter: CGPoint(x: width / 2, y: height / 2),
radius: width / 3,
startAngle: CGFloat(-90 * Double.pi / 180),
endAngle: CGFloat(endAngle * Double.pi / 180),
clockwise: true)
return Path(bezierPath.cgPath)
}
}
Usage:
ArcShape(width: geometry.size.width, height: geometry.size.height, progress: 0.75)
.stroke(lineWidth: 5)
.fill(Color(R.color.colorBlue.name))
.frame(width: geometry.size.width , height: geometry.size.height)
Upvotes: 0
Reputation: 535138
The easiest way is to mask or clip out the half of the circle you don't want.
class HalfCircleView : UIView {
override func draw(_ rect: CGRect) {
let p = UIBezierPath(rect: CGRect(x: 50, y: 0, width: 50, height: 100))
p.addClip()
let p2 = UIBezierPath(ovalIn: CGRect(x: 10, y: 10, width: 80, height: 80))
p2.lineWidth = 2
p2.stroke()
}
}
Of course, where you put the clipping is where the circle half will appear. And once you have the half-circle view of course you can rotate it etc.
Upvotes: 1
Reputation: 1094
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")
}
Upvotes: 3