How do I draw a circle in iOS Swift?

let block = UIView(frame: CGRectMake(cellWidth-25, cellHeight/2-8, 16, 16))
block.backgroundColor = UIColor(netHex: 0xff3b30)
block.layer.cornerRadius = 9
block.clipsToBounds = true

This is what I have right now, but it's obviously not the right way to do it.

What's the simplest way to do it?


Solution 1:

WARNING! This is an incorrect solution. layers are added infinitely in the drawRect method (every time the view is drawn). You should NEVER add layers in the drawRect method. Use layoutSubview instead.

You can draw a circle with this (Swift 3.0+):

let circlePath = UIBezierPath(arcCenter: CGPoint(x: 100, y: 100), radius: CGFloat(20), startAngle: CGFloat(0), endAngle: CGFloat(Double.pi * 2), clockwise: true)
    
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
    
// Change the fill color
shapeLayer.fillColor = UIColor.clear.cgColor
// You can change the stroke color
shapeLayer.strokeColor = UIColor.red.cgColor
// You can change the line width
shapeLayer.lineWidth = 3.0
    
view.layer.addSublayer(shapeLayer)

With the code you have posted you are cropping the corners of the UIView, not adding a circle to the view.


Here's a full example of using that method:

/// A special UIView displayed as a ring of color
class Ring: UIView {
    override func drawRect(rect: CGRect) {
        drawRingFittingInsideView()
    }
    
    internal func drawRingFittingInsideView() -> () {
        let halfSize:CGFloat = min( bounds.size.width/2, bounds.size.height/2)
        let desiredLineWidth:CGFloat = 1 // your desired value
            
        let circlePath = UIBezierPath(
                arcCenter: CGPoint(x:halfSize,y:halfSize),
                radius: CGFloat( halfSize - (desiredLineWidth/2) ),
                startAngle: CGFloat(0),
                endAngle:CGFloat(M_PI * 2),
                clockwise: true)
    
         let shapeLayer = CAShapeLayer()
         shapeLayer.path = circlePath.CGPath
            
         shapeLayer.fillColor = UIColor.clearColor().CGColor
         shapeLayer.strokeColor = UIColor.redColor().CGColor
         shapeLayer.lineWidth = desiredLineWidth
    
         layer.addSublayer(shapeLayer)
     }
}

A circle outlined in red and filled with yellow on a yellow background.


Note, however there's an incredibly handy call:

let circlePath = UIBezierPath(ovalInRect: rect)

which does all the work of making the path. (Don't forget to inset it for the line thickness, which is also incredibly easy with CGRectInset.)

internal func drawRingFittingInsideView(rect: CGRect) {
    let desiredLineWidth:CGFloat = 4    // Your desired value
    let hw:CGFloat = desiredLineWidth/2
    
    let circlePath = UIBezierPath(ovalInRect: CGRectInset(rect,hw,hw))
    let shapeLayer = CAShapeLayer()
    shapeLayer.path = circlePath.CGPath
    shapeLayer.fillColor = UIColor.clearColor().CGColor
    shapeLayer.strokeColor = UIColor.redColor().CGColor
    shapeLayer.lineWidth = desiredLineWidth
    layer.addSublayer(shapeLayer)
}

An ellipses (oval-like) outlined in red and filled with yellow on a yellow background.


In practice these days in Swift, you would certainly use @IBDesignable and @IBInspectable. Using these you can actually see and change the rendering, in Storyboard!

As you can see, it actually adds new features to the Inspector on the Storyboard, which you can change on the Storyboard:

Xcode Storyboard Attributes Inspector with custom fields.

/// A dot with a border, which you can control completely in Storyboard
@IBDesignable class Dot: UIView {

    @IBInspectable var mainColor: UIColor = UIColor.blueColor() {
        didSet { 
             print("mainColor was set here")
        }
    }

    @IBInspectable var ringColor: UIColor = UIColor.orangeColor() {
         didSet {
             print("bColor was set here") 
        }
    }

    @IBInspectable var ringThickness: CGFloat = 4 {
        didSet { 
            print("ringThickness was set here")
        }
    }
    
    @IBInspectable var isSelected: Bool = true
    
    override func drawRect(rect: CGRect) {
        let dotPath = UIBezierPath(ovalInRect:rect)
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = dotPath.CGPath
        shapeLayer.fillColor = mainColor.CGColor
        layer.addSublayer(shapeLayer)
        
        if (isSelected) { 
            drawRingFittingInsideView(rect)
        }
    }
    
    internal func drawRingFittingInsideView(rect: CGRect) {
        let hw:CGFloat = ringThickness/2
        let circlePath = UIBezierPath(ovalInRect: CGRectInset(rect,hw,hw) )
        
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = circlePath.CGPath
        shapeLayer.fillColor = UIColor.clearColor().CGColor
        shapeLayer.strokeColor = ringColor.CGColor
        shapeLayer.lineWidth = ringThickness
        layer.addSublayer(shapeLayer)
    }
}

Finally, note that if you have a UIView (which is square, and which you set to say red in Storyboard) and you simply want to turn it in to a red circle, you can just do the following:

// Makes a UIView into a circular dot of color
class Dot: UIView {
    override func layoutSubviews() {
        layer.cornerRadius = bounds.size.width/2
    }
}

Solution 2:

Make a class UIView and assign it this code for a simple circle

import UIKit
@IBDesignable
class DRAW: UIView {

    override func draw(_ rect: CGRect) {

        var path = UIBezierPath()
        path = UIBezierPath(ovalIn: CGRect(x: 50, y: 50, width: 100, height: 100))
        UIColor.yellow.setStroke()
        UIColor.red.setFill()
        path.lineWidth = 5
        path.stroke()
        path.fill()


    }


}

Solution 3:

If you want to use a UIView to draw it, then you need to make the radius / of the height or width.

so just change:

block.layer.cornerRadius = 9

to:

block.layer.cornerRadius = block.frame.width / 2

You'll need to make the height and width the same however. If you'd like to use coregraphics, then you'll want to do something like this:

CGContextRef ctx= UIGraphicsGetCurrentContext();
CGRect bounds = [self bounds];

CGPoint center;
center.x = bounds.origin.x + bounds.size.width / 2.0;
center.y = bounds.origin.y + bounds.size.height / 2.0;
CGContextSaveGState(ctx);

CGContextSetLineWidth(ctx,5);
CGContextSetRGBStrokeColor(ctx,0.8,0.8,0.8,1.0);
CGContextAddArc(ctx,locationOfTouch.x,locationOfTouch.y,30,0.0,M_PI*2,YES);
CGContextStrokePath(ctx);

Solution 4:

Swift 4 version of accepted answer:

@IBDesignable
class CircledDotView: UIView {

    @IBInspectable var mainColor: UIColor = .white {
        didSet { print("mainColor was set here") }
    }
    @IBInspectable var ringColor: UIColor = .black {
        didSet { print("bColor was set here") }
    }
    @IBInspectable var ringThickness: CGFloat = 4 {
        didSet { print("ringThickness was set here") }
    }

    @IBInspectable var isSelected: Bool = true

    override func draw(_ rect: CGRect) {
        let dotPath = UIBezierPath(ovalIn: rect)
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = dotPath.cgPath
        shapeLayer.fillColor = mainColor.cgColor
        layer.addSublayer(shapeLayer)

        if (isSelected) {
            drawRingFittingInsideView(rect: rect)
        }
    }

    internal func drawRingFittingInsideView(rect: CGRect) {
        let hw: CGFloat = ringThickness / 2
        let circlePath = UIBezierPath(ovalIn: rect.insetBy(dx: hw, dy: hw))

        let shapeLayer = CAShapeLayer()
        shapeLayer.path = circlePath.cgPath
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeColor = ringColor.cgColor
        shapeLayer.lineWidth = ringThickness
        layer.addSublayer(shapeLayer)
    }
}

Solution 5:

Here is my version using Swift 5 and Core Graphics.

I have created a class to draw two circles. The first circle is created using addEllipse(). It puts the ellipse into a square, thus creating a circle. I find it surprising that there is no function addCircle(). The second circle is created using addArc() of 2pi radians

import UIKit

@IBDesignable
class DrawCircles: UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

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

    override func draw(_ rect: CGRect) {

        guard let context = UIGraphicsGetCurrentContext() else {
            print("could not get graphics context")
            return
        }

        context.setLineWidth(2)

        context.setStrokeColor(UIColor.blue.cgColor)

        context.addEllipse(in: CGRect(x: 30, y: 30, width: 50.0, height: 50.0))

        context.strokePath()

        context.setStrokeColor(UIColor.red.cgColor)

        context.beginPath() // this prevents a straight line being drawn from the current point to the arc

        context.addArc(center: CGPoint(x:100, y: 100), radius: 20, startAngle: 0, endAngle: 2.0*CGFloat.pi, clockwise: false)

        context.strokePath()
    }
}

in your ViewController's didViewLoad() add the following:

let myView = DrawCircles(frame: CGRect(x: 50, y: 50, width: 300, height: 300))

self.view.addSubview(myView)

When it runs it should look like this. I hope you like my solution!

enter image description here