da1lbi3
da1lbi3

Reputation: 4519

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")
    }

}

Upvotes: 2

Views: 3158

Answers (3)

In swift ui. Draws a clock wise arc, with a progress:

enter image description here

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

matt
matt

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()
    }
}

enter image description here

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

ncke
ncke

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

Related Questions