How do I draw a circle in iOS Swift? - ios

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

Related

UIView diagonal border on one corner

I have a view that needs to be displayed with a slanted corner on one side. I've already done it when the view has a background color like this:
But I also need it to be displayed with a clear background. After setting its background to clear and adding a border to it this is the output:
Here is the code for the custom view that I'm using to create the diagonal corner:
class PointedView: UIImageView {
#IBInspectable var borderColor: UIColor = UIColor.clear {
didSet {
layer.borderColor = borderColor.cgColor
}
}
#IBInspectable var borderWidth: CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
#IBInspectable
/// Percentage of the slant based on the width
var slopeFactor: CGFloat = 15 {
didSet {
updatePath()
}
}
private let shapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.lineWidth = 0
// with masks, the color of the shape layer doesn’t matter;
// it only uses the alpha channel; the color of the view is
// dictate by its background color
shapeLayer.fillColor = UIColor.white.cgColor
return shapeLayer
}()
override func layoutSubviews() {
super.layoutSubviews()
updatePath()
}
private func updatePath() {
let path = UIBezierPath()
// Start from x = 0 but the mid point of y of the view
path.move(to: CGPoint(x: 0, y: bounds.midY*2))
// Create the top slanting line
path.addLine(to: CGPoint(x: bounds.minX, y: bounds.minY))
// Straight line from end of slant to the end of the view
path.addLine(to: CGPoint(x: bounds.maxX, y: bounds.minY))
// Straight line to come down to the bottom, perpendicular to view
path.addLine(to: CGPoint(x: bounds.maxX, y: ((bounds.maxY*3)/4) + 20))
// Go back to the slant end position but from the bottom
path.addLine(to: CGPoint(x: (bounds.maxX*3)/4, y: bounds.maxY))
// Close path back to where you started
path.close()
shapeLayer.path = path.cgPath
layer.mask = shapeLayer
}
}
Is there any possible solution to this?
class PointedView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
let shapeLayer = CAShapeLayer()
shapeLayer.path = createBezierPath().cgPath
shapeLayer.strokeColor = UIColor.blue.cgColor
shapeLayer.fillColor = UIColor.blue.cgColor
shapeLayer.lineWidth = 1.0
shapeLayer.position = CGPoint(x: 10, y: 10)
self.layer.addSublayer(shapeLayer)
}
func createBezierPath() -> UIBezierPath {
let path = UIBezierPath()
// Start from x = 0 but the mid point of y of the view
path.move(to: CGPoint(x: 0, y: bounds.midY*2))
// Create the top slanting line
path.addLine(to: CGPoint(x: bounds.minX, y: bounds.minY))
// Straight line from end of slant to the end of the view
path.addLine(to: CGPoint(x: bounds.maxX, y: bounds.minY))
// Straight line to come down to the bottom, perpendicular to view
path.addLine(to: CGPoint(x: bounds.maxX, y: ((bounds.maxY*3)/4) + 20))
// Go back to the slant end position but from the bottom
path.addLine(to: CGPoint(x: (bounds.maxX*3)/4, y: bounds.maxY))
// Close path back to where you started
path.close() // draws the final line to close the path
return path
}
}
Managed to solve it by drawing another CAShapeLayer() following the same path as the original shape.
let borderLayer = CAShapeLayer()
borderLayer.path = path.cgPath
borderLayer.lineWidth = 2
borderLayer.strokeColor = borderColor.cgColor
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.frame = bounds
layer.addSublayer(borderLayer)
if you are using StoryBoard with #IBInspectable you can try like thisenter image description here
Please use this method
func roundCorners(corners: UIRectCorner = .allCorners, radius: CGFloat = 0.0, borderColor: UIColor = .clear, borderWidth: CGFloat = 0.0, clipToBonds: Bool = true) {
clipsToBounds = clipToBonds
layer.cornerRadius = radius
layer.borderWidth = borderWidth
layer.borderColor = borderColor.cgColor
if corners.contains(.allCorners){
layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
return
}
var maskedCorners = CACornerMask()
if corners.contains(.topLeft) { maskedCorners.insert(.layerMinXMinYCorner) }
if corners.contains(.topRight) { maskedCorners.insert(.layerMaxXMinYCorner) }
if corners.contains(.bottomLeft) { maskedCorners.insert(.layerMinXMaxYCorner) }
if corners.contains(.bottomRight) { maskedCorners.insert(.layerMaxXMaxYCorner) }
layer.maskedCorners = maskedCorners
}
and by using your UIVIEW
View.roundCorners(corners: [.topLeft, .bottomLeft], radius: 20, borderColor: .clear, borderWidth: 0, clipToBonds: true)

Add Colour Gradient to Circular Progress Bar iOS

I am having difficulty adding a colour gradient to my circular progress bar
My setup:
Storyboard: ViewController with an empty view designated as the Class CircularProgressBar
Custom class for the CircularProgressBar
Steps Taken:
I am able to load circular progress bar
In earlier attempts I created a gradient shape layer & set it to be masked by the bounds of the BarLayer, however the gradient shape layer draw a rectangle starting from the centre of my circle & extend to the bottom right so only that portion of the BarLayer would have a gradient
When I moved the origin of the gradient layer rectangle it shifted the Barlayer off the TrackLayer with it
How do I:
Have the gradient layer cover the entire circular progress bar & mask it to the BarLayer
class CircularProgressBar: UIView {
let shapeLayer = CAShapeLayer()
let trackLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
addProgressBar(radius: 5, progress: 0)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
addProgressBar(radius: 5, progress: 0)
}
func addProgressBar(radius: CGFloat, progress: CGFloat) {
let lineWidth = radius*0.080
let circularPath = UIBezierPath(arcCenter: CGPoint(x: bounds.midX, y: bounds.midY), radius: radius, startAngle: 0, endAngle: CGFloat.pi*2, clockwise: true)
//TrackLayer
trackLayer.path = circularPath.cgPath
trackLayer.fillColor = UIColor.lightGray.cgColor
trackLayer.strokeColor = UIColor.clear.cgColor
trackLayer.opacity = 0.5
trackLayer.lineWidth = lineWidth
trackLayer.lineCap = CAShapeLayerLineCap.round
//BarLayer
shapeLayer.path = circularPath.cgPath
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.systemGreen.cgColor
shapeLayer.lineWidth = lineWidth
shapeLayer.strokeEnd = 0
shapeLayer.lineCap = CAShapeLayerLineCap.round
//Rotate Shape Layer
shapeLayer.transform = CATransform3DMakeRotation(-CGFloat.pi/2, 0, 0, 1)
//Shape Shadow
shapeLayer.shadowColor = UIColor.black.cgColor
shapeLayer.shadowOffset = CGSize(width: -7, height: 7)
shapeLayer.shadowRadius = 1
shapeLayer.shadowOpacity = 0.5
//Animation
loadProgress(percentage: progress)
//LoadLayers
layer.addSublayer(trackLayer)
layer.addSublayer(shapeLayer)
}
func loadProgress(percentage: CGFloat) {
let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
basicAnimation.fromValue = 0
basicAnimation.duration = 2
basicAnimation.fillMode = CAMediaTimingFillMode.forwards
basicAnimation.isRemovedOnCompletion = false
shapeLayer.strokeEnd = percentage
shapeLayer.add(basicAnimation, forKey: "basicStroke")
}
ViewController
class HomeViewController: UIViewController {
#IBOutlet weak var containerView: UIView!
var circularProgressBar = CircularProgressBar()
var radius: CGFloat!
var progress: CGFloat!
var answeredCorrect = 0
var totalQuestions = 0
override func viewDidLoad() {
super.viewDidLoad()
answeredCorrect = 50
totalQuestions = 100
//Configure Progress Bar
radius = (containerView.frame.height)/2.60
progress = CGFloat(answeredCorrect) / CGFloat (totalQuestions)
circularProgressBar.addProgressBar(radius: radius, progress: progress)
circularProgressBar.center = containerView.center
//Adding view
containerView.addSubview(circularProgressBar)
}
}
If you are looking for something like this
You need to update your code
import UIKit
#IBDesignable
class CircularProgressBar: UIView {
let shapeLayer = CAShapeLayer()
let trackLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
// addProgressBar(radius: 50, progress: 50)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// addProgressBar(radius: 50, progress: 50)
}
override func layoutSubviews() {
addProgressBar(radius: 50, progress: 50)
}
func addProgressBar(radius: CGFloat, progress: CGFloat) {
let lineWidth = CGFloat(10.0)//radius*0.080
let circularPath = UIBezierPath(arcCenter: CGPoint(x: bounds.midX, y: bounds.midY), radius: radius, startAngle: 0, endAngle: CGFloat.pi*2, clockwise: true)
//TrackLayer
trackLayer.path = circularPath.cgPath
trackLayer.fillColor = UIColor.lightGray.cgColor
trackLayer.strokeColor = UIColor.clear.cgColor
trackLayer.lineWidth = lineWidth
trackLayer.lineCap = CAShapeLayerLineCap.round
//BarLayer
shapeLayer.path = circularPath.cgPath
shapeLayer.fillColor = UIColor.black.cgColor
shapeLayer.strokeColor = UIColor.systemGreen.cgColor
shapeLayer.lineWidth = lineWidth*2
//shapeLayer.strokeEnd = 0
shapeLayer.lineCap = CAShapeLayerLineCap.round
//Rotate Shape Layer
// shapeLayer.transform = CATransform3DMakeRotation(-CGFloat.pi/2, 0, 0, 1)
//Animation
// loadProgress(percentage: progress)
//LoadLayers
layer.addSublayer(trackLayer)
self.addGradient()
// layer.addSublayer(shapeLayer)
}
private func addGradient() {
let gradient = CAGradientLayer()
gradient.colors = [UIColor.red.cgColor,UIColor.purple.cgColor,UIColor.systemPink.cgColor,UIColor.blue.cgColor]
gradient.frame = bounds
gradient.mask = shapeLayer
layer.addSublayer(gradient)
}
}
And for Uniform gradient you can replace addGradient method
private func addGradient() {
let gradient = CAGradientLayer()
gradient.colors = [UIColor.red.cgColor,UIColor.cyan.cgColor,UIColor.brown.cgColor,UIColor.blue.cgColor]
gradient.frame = bounds
gradient.locations = [0.2,0.5,0.75,1]
gradient.startPoint = CGPoint(x: 0.5, y: 0.5)
gradient.endPoint = CGPoint(x: 0.5, y: 0)
gradient.type = .conic
gradient.mask = shapeLayer
layer.addSublayer(gradient)
}

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: rainbow colour circle

Hi i am trying to write colour picker in swift that looks like this.
But so far I managed this.
Draw circle was easy, heres code...
fileprivate func setupScene(){
let circlePath: UIBezierPath = UIBezierPath(arcCenter: CGPoint(x: self.wheelView.frame.width/2, y: self.wheelView.frame.height/2), radius: CGFloat(self.wheelView.frame.height/2), startAngle: CGFloat(0), endAngle:CGFloat(Double.pi * 2), clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
//color inside circle
shapeLayer.fillColor = UIColor.clear.cgColor
//colored border of circle
shapeLayer.strokeColor = UIColor.purple.cgColor
//width size of border
shapeLayer.lineWidth = 10
wheelView.layer.addSublayer(shapeLayer)
}
#IBOutlet var wheelView: UIView!
But now I don't know how to insert rainbow colours ... I tried CAGradientLayer but it was not visible. Any good advice?
Details
Xcode 9.1, swift 4
Xcode 10.2.1 (10E1001), Swift 5
Solution
The code was taken from https://github.com/joncardasis/ChromaColorPicker
import UIKit
class RainbowCircle: UIView {
private var radius: CGFloat {
return frame.width>frame.height ? frame.height/2 : frame.width/2
}
private var stroke: CGFloat = 10
private var padding: CGFloat = 5
//MARK: - Drawing
override func draw(_ rect: CGRect) {
super.draw(rect)
drawRainbowCircle(outerRadius: radius - padding, innerRadius: radius - stroke - padding, resolution: 1)
}
init(frame: CGRect, lineHeight: CGFloat) {
super.init(frame: frame)
stroke = lineHeight
}
required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }
/*
Resolution should be between 0.1 and 1
*/
private func drawRainbowCircle(outerRadius: CGFloat, innerRadius: CGFloat, resolution: Float) {
guard let context = UIGraphicsGetCurrentContext() else { return }
context.saveGState()
context.translateBy(x: self.bounds.midX, y: self.bounds.midY) //Move context to center
let subdivisions:CGFloat = CGFloat(resolution * 512) //Max subdivisions of 512
let innerHeight = (CGFloat.pi*innerRadius)/subdivisions //height of the inner wall for each segment
let outterHeight = (CGFloat.pi*outerRadius)/subdivisions
let segment = UIBezierPath()
segment.move(to: CGPoint(x: innerRadius, y: -innerHeight/2))
segment.addLine(to: CGPoint(x: innerRadius, y: innerHeight/2))
segment.addLine(to: CGPoint(x: outerRadius, y: outterHeight/2))
segment.addLine(to: CGPoint(x: outerRadius, y: -outterHeight/2))
segment.close()
//Draw each segment and rotate around the center
for i in 0 ..< Int(ceil(subdivisions)) {
UIColor(hue: CGFloat(i)/subdivisions, saturation: 1, brightness: 1, alpha: 1).set()
segment.fill()
//let lineTailSpace = CGFloat.pi*2*outerRadius/subdivisions //The amount of space between the tails of each segment
let lineTailSpace = CGFloat.pi*2*outerRadius/subdivisions
segment.lineWidth = lineTailSpace //allows for seemless scaling
segment.stroke()
//Rotate to correct location
let rotate = CGAffineTransform(rotationAngle: -(CGFloat.pi*2/subdivisions)) //rotates each segment
segment.apply(rotate)
}
context.translateBy(x: -self.bounds.midX, y: -self.bounds.midY) //Move context back to original position
context.restoreGState()
}
}
Usage
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let rainbowCircle = RainbowCircle(frame: CGRect(x: 50, y: 50, width: 240, height: 420), lineHeight: 5)
rainbowCircle.backgroundColor = .clear
view.addSubview(rainbowCircle)
}
}
Result

My CoreGraphics code is working really slowly. How can I make this custom UIScrollView perform better?

I made a custom class that creates a sort of timeline that is meant to be scrolled horizontally. Here's the code for my custom UIScrollView :
import UIKit
struct DataPoint {
var fillColor: UIColor = UIColor.grayColor()
init(color: UIColor) {
fillColor = color
}
}
#IBDesignable
class MyView: UIScrollView {
#IBInspectable var lineColor: UIColor = UIColor.grayColor()
#IBInspectable var lineHeight: CGFloat = 65
#IBInspectable var lineWidth: CGFloat = 15
#IBInspectable var lineGap: CGFloat = 25
#IBInspectable var lineCount: Int = 0
var dataPoints = [DataPoint(color: UIColor.greenColor()), DataPoint(color: UIColor.blueColor())]
override init(frame: CGRect) {
super.init(frame: frame)
setupValues()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupValues()
//fatalError("init(coder:) has not been implemented")
}
func setupValues() {
self.contentSize = CGSize(width: self.frame.width * 2, height: self.frame.height)
self.backgroundColor = UIColor.whiteColor()
self.lineCount = Int(self.frame.width / lineGap)
}
override internal func layoutSubviews() {
super.layoutSubviews()
self.setNeedsDisplay()
self.layoutIfNeeded()
}
override func drawRect(rect: CGRect) {
let ctx = UIGraphicsGetCurrentContext()
CGContextSaveGState(ctx)
for i in 0...lineCount {
let start = CGPoint(x: CGFloat(i) * lineGap, y: self.frame.height)
let end = CGPoint(x: CGFloat(i) * lineGap, y: self.frame.height - lineHeight)
drawLine(from: start, to: end, color: UIColor.grayColor())
if i % (lineCount / (dataPoints.count + 2)) == 0 && i != 0 && i != lineCount {
drawPoint(at: end, radius: 5, color: UIColor.orangeColor())
}
}
CGContextRestoreGState(ctx)
}
func drawLine(from start: CGPoint, to end: CGPoint, color: UIColor) {
let path = UIBezierPath()
path.moveToPoint(start)
path.addLineToPoint(end)
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.CGPath
shapeLayer.strokeColor = color.CGColor
shapeLayer.lineWidth = 1
self.layer.addSublayer(shapeLayer)
}
func drawPoint(at center: CGPoint, radius: CGFloat, color: UIColor) {
let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: CGFloat(0), endAngle: CGFloat(M_PI * 2), clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.CGPath
shapeLayer.fillColor = color.CGColor
shapeLayer.strokeColor = UIColor.blackColor().CGColor
shapeLayer.lineWidth = 0.5
self.layer.addSublayer(shapeLayer)
}
}
On my device and on the simulators, this is extremely slow and laggy. What exactly am I doing wrong here ? And what steps can I take to achieve a solid 60fps while scrolling ?
Hello I found your problem, your problem is
override internal func layoutSubviews() {
super.layoutSubviews()
self.setNeedsDisplay()
self.layoutIfNeeded()
}
you are accidentally in a never-ending paint loop
replace this by this
override internal func layoutSubviews() {
super.layoutSubviews()
}
or remove at all
I hope this Helps you, for me works great
It looks to me that you are drawing the same thing every time. Everything is static... So why not draw it all once into a graphics context and make a UIImage out of it to add to the background of the scrollview (or foreground, depending on anything else you are doing)?

Resources