7

I am trying to make a circle with dash lines. I am able to make lines in rectangle but I don't know how to make these in circle. Here is answer I got but it's in Objective-C: UIView Draw Circle with Dotted Line Border

Here is my code which makes a rectangle with dashed lines.

func addDashedBorder() {
    let color = UIColor.red.cgColor

    let shapeLayer:CAShapeLayer = CAShapeLayer()
    let frameSize = self.frame.size
    let shapeRect = CGRect(x: 0, y: 0, width: frameSize.width, height: frameSize.height)

    shapeLayer.bounds = shapeRect
    shapeLayer.position = CGPoint(x: frameSize.width/2, y: frameSize.height/2)
    shapeLayer.fillColor = UIColor.clear.cgColor
    shapeLayer.strokeColor = color
    shapeLayer.lineWidth = 2
    shapeLayer.lineJoin = CAShapeLayerLineJoin.round
    shapeLayer.lineDashPattern = [6,3]
    shapeLayer.path = UIBezierPath(roundedRect: shapeRect, cornerRadius: 5).cgPath

    self.layer.addSublayer(shapeLayer)
}
rmaddy
  • 314,917
  • 42
  • 532
  • 579
Shahzad Ali
  • 123
  • 1
  • 7
  • 3
    Did you try using your exact code but change the `UIBezierPath` call to create a circle instead of a rounded rectangle? – rmaddy May 28 '19 at 16:06

3 Answers3

12

Certainly you can just render your circular UIBezierPath with the selected dash pattern:

class DashedCircleView: UIView {
    private var shapeLayer: CAShapeLayer = {
        let shapeLayer = CAShapeLayer()
        shapeLayer.strokeColor = UIColor.red.cgColor
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.lineWidth = 10
        shapeLayer.lineCap = .round
        shapeLayer.lineDashPattern = [20, 60]
        return shapeLayer
    }()

    override init(frame: CGRect = .zero) {
        super.init(frame: frame)
        configure()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        configure()
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        updatePath()
    }
}

private extension DashedCircleView {
    func configure() {
        layer.addSublayer(shapeLayer)
    }

    func updatePath() {
        let rect = bounds.insetBy(dx: shapeLayer.lineWidth / 2, dy: shapeLayer.lineWidth / 2)
        let radius = min(rect.width, rect.height) / 2
        let center = CGPoint(x: rect.midX, y: rect.midY)
        let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: .pi * 2, clockwise: true)
        shapeLayer.path = path.cgPath
    }
}

That yields:

enter image description here

The problem with that approach is that it’s hard to get the dashed pattern to line up (notice the awkward dashing at the “3 o’clock” position). You can fix that by making sure that the two values of lineDashPattern add up to some number that evenly divides into the circumference of the circle (e.g. 2π × radius):

let circumference: CGFloat = 2 * .pi * radius
let count = 30
let relativeDashLength: CGFloat = 0.25
let dashLength = circumference / CGFloat(count)
shapeLayer.lineDashPattern = [dashLength * relativeDashLength, dashLength * (1 - relativeDashLength)] as [NSNumber]

Alternatively, rather than using lineDashPattern at all, you can instead keep a solid stroke and make the path, itself, as a series of small arcs. That way I can achieve the desired dashed effect, but ensuring it’s evenly split into count little arcs as we rotate from 0 to 2π:

class DashedCircleView: UIView {
    private var shapeLayer: CAShapeLayer = {
        let shapeLayer = CAShapeLayer()
        shapeLayer.strokeColor = UIColor.red.cgColor
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.lineWidth = 10
        shapeLayer.lineCap = .round
        return shapeLayer
    }()

    override init(frame: CGRect = .zero) {
        super.init(frame: frame)
        configure()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        configure()
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        updatePath()
    }
}

private extension DashedCircleView {
    func configure() {
        layer.addSublayer(shapeLayer)
    }

    func updatePath() {
        let rect = bounds.insetBy(dx: shapeLayer.lineWidth / 2, dy: shapeLayer.lineWidth / 2)
        let radius = min(rect.width, rect.height) / 2
        let center = CGPoint(x: rect.midX, y: rect.midY)

        let path = UIBezierPath()
        let count = 30
        let relativeDashLength: CGFloat = 0.25 // a value between 0 and 1
        let increment: CGFloat = .pi * 2 / CGFloat(count)

        for i in 0 ..< count {
            let startAngle = increment * CGFloat(i)
            let endAngle = startAngle + relativeDashLength * increment
            path.move(to: CGPoint(x: center.x + radius * cos(startAngle), 
                                  y: center.y + radius * sin(startAngle)))
            path.addArc(withCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
        } 
        shapeLayer.path = path.cgPath
    }
}

That yields:

enter image description here

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • when i put image in this circle , the contraint doest set to the circle – Shahzad Ali May 29 '19 at 11:13
  • I’d suggest using the view debugger and confirm the frame of both the circle view and the image view, and see whether the problem that one or more of the frames is wrong (i.e. you didn’t set up constraints properly) or whether the `CAShapeLayer` for the circle view is is wrong (e.g. you didn’t update the shape layer in `layoutSubviews` or `viewDidLayoutSubviews` like I have above). Once you’ve figured out which of these two issues applies, you can always then post a new, separate question with your findings. – Rob May 29 '19 at 15:42
3

You can use UIBezierPath(ovalIn:) to create a circle path in a square view.

extension UIView {
    func addDashedCircle() {
        let circleLayer = CAShapeLayer()
        circleLayer.path = UIBezierPath(ovalIn: bounds).cgPath
        circleLayer.lineWidth = 2.0
        circleLayer.strokeColor =  UIColor.red.cgColor//border of circle
        circleLayer.fillColor = UIColor.white.cgColor//inside the circle
        circleLayer.lineJoin = .round
        circleLayer.lineDashPattern = [6,3]
        layer.addSublayer(circleLayer)
    }
}

Set view background color .clear and fill color of the layer .white

class View1: UIViewController {
    @IBOutlet weak var circleView: UIView!
    override func viewDidLoad() {
        super.viewDidLoad()
        circleView.backgroundColor = .clear//outside the circle
        circleView.addDashedCircle()
    }
}

Or using UIBezierPath(arcCenter:radius:startAngle:endAngle:clockwise:)

circleLayer.path = UIBezierPath(arcCenter: CGPoint(x: frame.size.width/2, y: frame.size.height/2),
                            radius: min(frame.size.height,frame.size.width)/2,
                            startAngle: 0,
                            endAngle: .pi * 2,
                            clockwise: true).cgPath

enter image description here

RajeshKumar R
  • 15,445
  • 2
  • 38
  • 70
0

Draw the path using the circle path variant of UIBezierPath

shapeLayer.path = UIBezierPath(arcCenter: CGPoint(x: frame.size.width * 0.5, y: frame.size.height * 0.5), radius: frame.size.width * 0.5, startAngle: 0, endAngle: .pi * 2, clockwise: true)
Cole James
  • 200
  • 1
  • 1
  • 13