create an Arc in swift for Ui collection view - ios

Hi i like to create the following view in ios swift for adding in UIcollection view , how can i achieve this ?
my current code to draw which is returning circle view
#IBInspectable public var fillColor: UIColor = #colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1) { didSet { setNeedsLayout() } }
#IBInspectable public var strokeColor: UIColor = #colorLiteral(red: 0.9254902005, green: 0.2352941185, blue: 0.1019607857, alpha: 1) { didSet { setNeedsLayout() } }
#IBInspectable public var lineWidth: CGFloat = 0 { didSet { setNeedsLayout() } }
lazy private var shapeLayer: CAShapeLayer = {
let _shapeLayer = CAShapeLayer()
self.layer.insertSublayer(_shapeLayer, at: 0)
return _shapeLayer
}()
override func layoutSubviews() {
super.layoutSubviews()
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let radius = (min(bounds.size.width, bounds.size.height) - lineWidth) / 2
shapeLayer.path = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: 90, clockwise: true).cgPath
shapeLayer.fillColor = fillColor.cgColor
shapeLayer.strokeColor = strokeColor.cgColor
shapeLayer.lineWidth = lineWidth
}

You can draw the arc in view using BezierPath. Add this arcView in your collection view cell.
Let's see the ArcView :
class ArcView : UIView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
super.draw(rect)
createArc(rect: rect)
}
private func createArc(rect : CGRect) {
let center = CGPoint(x: rect.width/2, y: rect.height/2)
let lineWidth : CGFloat = 50.0
let radius = rect.width / 2 - lineWidth
let startingAngle = CGFloat(-10.0/180) * CGFloat.pi
let endingAngle = CGFloat(-80/180.0) * CGFloat.pi
let bezierPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: startingAngle , endAngle: endingAngle, clockwise: false)
bezierPath.lineWidth = lineWidth
UIColor(red: 249/255.0, green: 179/255.0, blue: 127/255.0, alpha: 1.0).setStroke()
bezierPath.stroke()
}
}
The center of arc is the middle of view.
Drawing the arc from -10 degree to -80 degree in anti-clockwise direction.
Adding the ArcView in ViewController's view Hierarchy (You will add this view in your collectionView Cell)
import UIKit
class ViewController: UIViewController {
private var arcView : ArcView!
override func viewDidLoad() {
super.viewDidLoad()
arcView = ArcView(frame: view.frame)
view.addSubview(arcView)
arcView.setNeedsDisplay()
}
}
Output :
The sample code is here

Try this and set angle as you want
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)

Related

How to draw PieChart, Obtained Marks fill with some colour out of Total Marks without 3rd party library iOS Swift

I want like this
Any code example will be appreciated
Here is a starting point ... created a circular progress view for your help
import UIKit
public class CircularProgressView: UIView {
// First create two layer properties
private lazy var circleLayer : CAShapeLayer = {
let shape = CAShapeLayer()
shape.fillColor = UIColor.clear.cgColor
shape.lineCap = .round
shape.lineWidth = 30.0
shape.strokeColor = #colorLiteral(red: 0.7999292612, green: 0.8000453115, blue: 0.7999040484, alpha: 1)
return shape
}()
private lazy var progressLayer : CAShapeLayer = {
let progress = CAShapeLayer()
progress.fillColor = UIColor.clear.cgColor
// progress.lineCap = .round
progress.lineWidth = 30.0
progress.strokeEnd = 0
progress.strokeColor = #colorLiteral(red: 0.07347138971, green: 0.5590900779, blue: 0.8216868043, alpha: 1)
return progress
}()
override init(frame: CGRect) {
super.init(frame: frame)
createCircularPath()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
createCircularPath()
}
private func createCircularPath() {
updatePath()
layer.addSublayer(circleLayer)
layer.addSublayer(progressLayer)
}
public override func layoutSubviews() {
updatePath()
}
private func updatePath() {
let circularPath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: 80, startAngle: -.pi / 2, endAngle: 3 * .pi / 2, clockwise: true)
circleLayer.path = circularPath.cgPath
progressLayer.path = circularPath.cgPath
}
}
public extension CircularProgressView {
func progressAnimation(_ percentage:Float) {
let circularProgressAnimation = CABasicAnimation(keyPath: "strokeEnd")
circularProgressAnimation.duration = 1
circularProgressAnimation.toValue = Float( percentage / 100 )
circularProgressAnimation.fillMode = .forwards
circularProgressAnimation.isRemovedOnCompletion = false
progressLayer.add(circularProgressAnimation, forKey: "progressAnim")
}
}
What you actually want is something custom. This can be built custom or you can use a thridparty library with certain modifications. You a bunch of them on cocoapods but the one i would suggest is
https://github.com/danielgindi/Charts

circle with dash lines uiview

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

swift: Change size of UIView

I use this class to create circular progress:
class ProgressBarView: UIView {
var bgPath: UIBezierPath!
var shapeLayer: CAShapeLayer!
var progressLayer: CAShapeLayer!
var progress: Float = 0 {
willSet(newValue)
{
progressLayer.strokeEnd = CGFloat(newValue)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
bgPath = UIBezierPath()
self.simpleShape()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
bgPath = UIBezierPath()
self.simpleShape()
}
func simpleShape()
{
createCirclePath()
shapeLayer = CAShapeLayer()
shapeLayer.path = bgPath.cgPath
shapeLayer.lineWidth = 2.5
shapeLayer.fillColor = nil
shapeLayer.strokeColor = UIColor.black.cgColor.copy(alpha: 0.2)
progressLayer = CAShapeLayer()
progressLayer.path = bgPath.cgPath
progressLayer.lineCap = kCALineCapRound
progressLayer.lineWidth = 2.5
progressLayer.fillColor = nil
progressLayer.strokeColor = UIColor(red: 0/255, green: 122/255, blue: 255/255, alpha: 1.0).cgColor
progressLayer.strokeEnd = 0.0
self.layer.addSublayer(shapeLayer)
self.layer.addSublayer(progressLayer)
}
private func createCirclePath()
{
let x = self.frame.width/2
let y = self.frame.height/2
let center = CGPoint(x: x, y: y)
print(x,y,center)
bgPath.addArc(withCenter: center, radius: x, startAngle: CGFloat(11), endAngle: CGFloat(17.28), clockwise: true)
bgPath.close()
}
}
And in my ViewController I use this class for my progressView. I want to change size of my progressView for iPhone SE. I trying create width constraint in storyboard and change size of progressView programmatically like:
if self.view.frame.height == 568 {
progressViewWidthConstraint.constant = 100
}
But it doesn't work. How to change size for iPhone SE?
Update
You need to re-layout
progressViewWidthConstraint.constant = 100
self.view.layoutIfNeeded()

iOS rounding UIView corners with draw enabled

When draw method is commented out inside the below code the view shows up circular, but when it is uncommented out the view shows up as a rectangle again. I could comment out the body of draw to have it just be an empty method, but the view would still show up as a rectangle.
I've tried moving the cornerRadius line to the end of draw, beginning of draw, and end of drawInnerCircle but to no avail. I was wondering if there was still a way to make the view rounded with draw method enabled?
import UIKit
class IconView: UIView {
override init(frame:CGRect) {
super.init(frame : frame)
self.backgroundColor = UIColor(red: 47/255, green: 49/255, blue: 53/255, alpha: 1.0)
self.layer.cornerRadius = self.frame.size.height / 2.0
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
drawInnerCircle()
}
internal func drawInnerCircle() -> () {
let halfSize:CGFloat = min( self.frame.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 - 3 ),
startAngle: CGFloat(0),
endAngle:CGFloat(M_PI * 2),
clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor(red: 96/255, green: 99/255, blue: 105/255, alpha: 1.0).cgColor
shapeLayer.lineWidth = desiredLineWidth
layer.addSublayer(shapeLayer)
}
}
Add the following:
self.layer.masksToBounds = true
below the line:
self.layer.cornerRadius = self.frame.size.height / 2.0
By the way there are some problem described by the above comments.

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?
Alert. This old answer is absolutely incorrect.
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)
}
}
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)
}
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:
/// 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
}
}
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()
}
}
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);
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!
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)
}
}
Updating #Dario's code approach for Xcode 8.2.2, Swift 3.x. Noting that in storyboard, set the Background color to "clear" to avoid a black background in the square UIView:
import UIKit
#IBDesignable
class Dot:UIView
{
#IBInspectable var mainColor: UIColor = UIColor.clear
{
didSet { print("mainColor was set here") }
}
#IBInspectable var ringColor: UIColor = UIColor.clear
{
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)
}
}
And if you want to control the start and end angles:
import UIKit
#IBDesignable
class Dot:UIView
{
#IBInspectable var mainColor: UIColor = UIColor.clear
{
didSet { print("mainColor was set here") }
}
#IBInspectable var ringColor: UIColor = UIColor.clear
{
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 halfSize:CGFloat = min( bounds.size.width/2, bounds.size.height/2)
let desiredLineWidth:CGFloat = ringThickness // your desired value
let circlePath = UIBezierPath(
arcCenter: CGPoint(x: halfSize, y: halfSize),
radius: CGFloat( halfSize - (desiredLineWidth/2) ),
startAngle: CGFloat(0),
endAngle:CGFloat(Double.pi),
clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = ringColor.cgColor
shapeLayer.lineWidth = ringThickness
layer.addSublayer(shapeLayer)
}
}
A much easier and resource friendly approach would be.
import UIKit
#IBDesignable
class CircleDrawView: UIView {
#IBInspectable var borderColor: UIColor = UIColor.red;
#IBInspectable var borderSize: CGFloat = 4
override func draw(_ rect: CGRect)
{
layer.borderColor = borderColor.cgColor
layer.borderWidth = borderSize
layer.cornerRadius = self.frame.height/2
}
}
With Border Color and Border Size and the default Background property you can define the appearance of the circle.
Please note, to draw a circle the view's height and width have to be equal in size.
The code is working for Swift >= 4 and Xcode >= 9.
I find Core Graphics to be pretty simple for Swift 3:
if let cgcontext = UIGraphicsGetCurrentContext() {
cgcontext.strokeEllipse(in: CGRect(x: center.x-diameter/2, y: center.y-diameter/2, width: diameter, height: diameter))
}
A simple function drawing a circle on the middle of your window frame, using a multiplicator percentage
/// CGFloat is a multiplicator from self.view.frame.width
func drawCircle(withMultiplicator coefficient: CGFloat) {
let radius = self.view.frame.width / 2 * coefficient
let circlePath = UIBezierPath(arcCenter: self.view.center, radius: radius, 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
shapeLayer.strokeColor = UIColor.darkGray.cgColor
shapeLayer.lineWidth = 2.0
view.layer.addSublayer(shapeLayer)
}
Add in view did load
//Circle Points
var CircleLayer = CAShapeLayer()
let center = CGPoint (x: myCircleView.frame.size.width / 2, y: myCircleView.frame.size.height / 2)
let circleRadius = myCircleView.frame.size.width / 2
let circlePath = UIBezierPath(arcCenter: center, radius: circleRadius, startAngle: CGFloat(M_PI), endAngle: CGFloat(M_PI * 4), clockwise: true)
CircleLayer.path = circlePath.cgPath
CircleLayer.strokeColor = UIColor.red.cgColor
CircleLayer.fillColor = UIColor.blue.cgColor
CircleLayer.lineWidth = 8
CircleLayer.strokeStart = 0
CircleLayer.strokeEnd = 1
Self.View.layer.addSublayer(CircleLayer)
2022, General example of how to actually draw using draw in a UIView.
It's not so easy to properly use UIView#draw.
General beginner tips, you can only draw inside a UIView, it is meaningless otherwise. Further, you can only use the draw commands (.fillEllipse etc) inside the draw call of a UIView.
You almost certainly want to set the intrinsic content size properly. It's important to fully understand how to use this on consumers views, in the two possible situations (a) you are using constraints (b) you are positioning the view by hand in layoutSubviews inside another view.
A huge gotchya is that you cannot draw outside the frame, no matter what. In contrast if you just use lazy vars with a layer to draw a shape (whether dot, circle, etc) it's no problem if you go outside the nominal frame (indeed you often just make the frame size zero so that everything centers easily in your consumer code). But once you start using draw you MUST be inside the frame. This is often confusing as in some cases you "don't know how big your drawing is going to be" until you draw it.
A huge gotchya is, when you are drawing either circles or edges, beginner programmers accidentally cut off half the thickness of that line, due to the fact that draw absolutely can't draw outside the frame. You have to inset the circle or rectangle, by, half the width of the line thickness.
Some code with correct 2022 syntax:
import UIKit
class ExampleDot: UIIView {
// setup ..
// clipsToBounds = false BUT SEE NOTES
// backgroundColor = .clear
override var intrinsicContentSize: CGSize {
return CGSize(width: 40, height: 40)
}
override func draw(_ rect: CGRect) {
guard let ctx = UIGraphicsGetCurrentContext() else { return }
// example of a dot
ctx.setFillColor(UIColor.black.cgColor)
ctx.fillEllipse(in: CGRect(x: 0, y: 0, width: 40, height: 40))
// example of a round circle BUT SEE NOTES
ctx.setStrokeColor(UIColor.systemYellow.cgColor)
ctx.setLineWidth(2)
ctx.strokeEllipse(in: CGRect(x: 1, y: 1, width: 40 - 4, height: 40 - 4))
// example of a smaller inner dot
ctx.setFillColor(UIColor.white.cgColor)
ctx.fillEllipse(in: CGRect(x: 10, y: 10, width: 20, height: 20))
}
}

Resources