I have added a imageview which is displayed as a circle by using the code below.
ImageView.layer.cornerRadius = ImageView.frame.size.width/2
ImageView.clipsToBounds = true
how can I implement a label within this which displays a number. I am not sure how to go about doing this. I am quite new to swift therefore I am not if this can be done or if there are better ways about doing this. My goal is to create a custom circle to display within the imageview which displays a number.
Or is it better to embed a view and then use uibeziapath to construct a circle.
You can create a custom view using CAShapeLayer and UIBezierPath if you want a hollow circle and label inside it. This class does that.
class CircleView: UIView {
let shapeLayer = CAShapeLayer()
var circularPath: UIBezierPath?
// This is your label, you can its property here like textColor and Font
let breathingStatusLabel : UILabel = {
let label = UILabel()
label.textColor = UIColor.white
label.text = “1”
label.textAlignment = .center
label.font = UIFont(name: "AvenirNext-UltraLight", size: 31)
label.alpha = 1.0
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.frame = frame
self.setupPaths()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func setupPaths() {
// Add label to your view and set its frame
self.addSubview(breathingStatusLabel)
breathingStatusLabel.frame = CGRect.init(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height)
breathingStatusLabel.center = CGPoint.init(x: self.bounds.width/2, y: self.bounds.height/2)
circularPath = UIBezierPath(arcCenter: .zero, radius: self.bounds.height / 2, startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
shapeLayer.path = circularPath?.cgPath
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 6.0
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.position = CGPoint(x: self.center.x, y: self.bounds.height / 2)
shapeLayer.lineCap = kCALineCapRound
shapeLayer.strokeEnd = 1
self.layer.addSublayer(shapeLayer)
self.shapeLayer.transform = CATransform3DMakeRotation(CGFloat.pi / 2, 0, 0, 1)
}
}
To use it :
let circle = CircleView(frame: someView.frame) // Some frame is where you want to show this.
view.addSubview(circle)
My goal is to create a custom circle to display within the imageview which displays a number.
If you are not using any image in imageView but you just make a circle then its better if you use
mylabel.layer.cornerRadius = mylabel.frame.size.width/2
and set data as mylabel.text = "2" and make sure it is center aligned.
Related
I created a standard border around my UIView like so:
vwGroup.layer.borderColor = UIColor.yellow.cgColor
vwGroup.layer.borderWidth = 2.0;
but I would like to have 5px of padding/margin/spacing between the UIView, and the border that surrounds it.
Right now it just draws the border immediately around it. I'd like to push it out so there is clear space between.
Any suggestions? I suspect insets are the way to go but can't figure it out.
Thank you!
Insets isn't the way to go. You use inset for padding a view's internal content from its margins. For what you need your best option is to wrap your vwGroup inside another UIView and set the border in the wrapping view. Something like:
let wrappingView = UIView(frame: someFrame)
wrappingView.backgroundColor = .clear
wrappingView.layer.borderColor = UIColor.yellow.cgColor
wrappingView.layer.borderWidth = 2.0;
wrappingView.addSubview(vwGroup)
Of course this is just for you to get the big picture. You might want to set proper frames/constraints.
Try this, it is helpful for you.
First, Add this extension
extension CALayer {
func addGradientBorder(colors:[UIColor],width:CGFloat = 1) {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = CGRect(origin: CGPoint.zero, size: self.bounds.size)
gradientLayer.startPoint = CGPoint(x:0.0, y:0.0)
gradientLayer.endPoint = CGPoint(x:1.0,y:1.0)
gradientLayer.colors = colors.map({$0.cgColor})
let shapeLayer = CAShapeLayer()
shapeLayer.lineWidth = width
shapeLayer.path = UIBezierPath(rect: self.bounds).cgPath
shapeLayer.fillColor = nil
shapeLayer.strokeColor = UIColor.red.cgColor
gradientLayer.mask = shapeLayer
self.addSublayer(gradientLayer)
}
}
Then add the UIView with border
let vwGroup = UIView(frame: CGRect(x: 50, y: 150, width: 200, height: 200))
vwGroup.backgroundColor = .red
//-- This is for padding between boarder and view -- you can set padding color ---
vwGroup.layer.addGradientBorder(colors:[UIColor.black,UIColor.black] , width: 40)
//-- This is for outer boarder -- you can also change the color of outer boarder --
vwGroup.layer.addGradientBorder(colors:[UIColor.white,UIColor.white] , width: 10)
self.view.addSubview(vwGroup)
Output is:
swift 5.4
call like this:
customView.layer.innerBorder()
did it, by add a sublayer
extension CALayer {
func innerBorder(borderOffset: CGFloat = 24.0, borderColor: UIColor = UIColor.blue, borderWidth: CGFloat = 2) {
let innerBorder = CALayer()
innerBorder.frame = CGRect(x: borderOffset, y: borderOffset, width: frame.size.width - 2 * borderOffset, height: frame.size.height - 2 * borderOffset)
innerBorder.borderColor = borderColor.cgColor
innerBorder.borderWidth = borderWidth
innerBorder.name = "innerBorder"
insertSublayer(innerBorder, at: 0)
}
}
I have a range of defined Bezier Paths within one View Controller (the default). However specifically, I want to make one of them a UIButton (It doesn't have to lead anywhere yet but it would be great if it could print something on touch).
By looking at some similar questions, I have been able to define the Bezier Path I want and the UIButton separately on the simulator, but haven't been able to splice them together.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let circlePath = UIBezierPath(arcCenter: CGPoint(x: 200, y: 350), radius: CGFloat(150), 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.gray.cgColor
//you can change the line width
shapeLayer.lineWidth = 7.5
view.layer.addSublayer(shapeLayer)
let button = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
button.backgroundColor = .green
button.setTitle("Test Button", for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
self.view.addSubview(button)
}
#objc func buttonAction(sender: UIButton!) {
print("Button tapped")
}
}
How can I pass circlePath as a UIButton?.
A UIButton is a UIView so you can add the layer you created to the button in just the same way as you did the view:
button.layer.addSublayer(shapeLayer)
Note that this layer will then cover the label of the UIButton, but an easy way to solve this is to change the z-position of the layer:
shapeLayer.zPosition = -1
E.g. if you want to add a circular layer to a button:
let circlePath = UIBezierPath(ovalIn: button.bounds)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
shapeLayer.zPosition = -1
button.layer.addSublayer(shapeLayer)
Or, matching the circle in your example but creating the button before the shape:
// define circle parameters
let radius: CGFloat = 150
let center = CGPoint(x: 200, y: 350)
// create the button
let button = UIButton(frame: CGRect(origin: center.applying(CGAffineTransform(translationX: -radius, y: -radius)),
size: CGSize(width: 2 * radius, height: 2 * radius)))
// create the circle layer
let circlePath = UIBezierPath(ovalIn: button.bounds)
let shapeLayer = CAShapeLayer()
shapeLayer.path = circlePath.cgPath
shapeLayer.zPosition = -1
// add the circle layer to the button
button.layer.addSublayer(shapeLayer)
view.addSubview(button)
I would like to move the code to customize UIButtons out of my viewcontroller classes as a best practice. The code I have below is to add a white border to UIButtons and I would like to easily call it on buttons throughout my project.
//White Border
let passwordBorder = CALayer()
let width = CGFloat(5.0)
passwordBorder.borderColor = UIColor.whiteColor().CGColor
passwordBorder.frame = CGRect(x: 0, y: 0, width: passwordField.frame.size.width, height: passwordField.frame.size.height)
passwordBorder.borderWidth = width
passwordField.layer.addSublayer(passwordBorder)
passwordField.layer.masksToBounds = true
How would I put this code into a helper function so I could call it easily?
I am new to coding and am having trouble with helper functions on anything UI. Thanks!
Take a look at Swift's Extensions. You could pretty easily do something like
extension UIButton {
func setPasswordBorderColor(borderColor: UIColor) {
//White Border
let passwordBorder = CALayer()
let width = CGFloat(5.0)
passwordBorder.borderColor = borderColor.CGColor
passwordBorder.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height)
passwordBorder.borderWidth = width
self.layer.addSublayer(passwordBorder)
self.layer.masksToBounds = true
}
}
import UIKit
//
#IBDesignable
class CustomBorderButton: UIButton {
#IBInspectable var cornerRadius: CGFloat = 0 {
didSet {
layer.cornerRadius = cornerRadius
layer.masksToBounds = cornerRadius > 0
}
}
#IBInspectable var borderWidth: CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
#IBInspectable var borderColor: UIColor? {
didSet {
layer.borderColor = borderColor?.CGColor
}
}
}
Without Code you can configure your button
Select Your button then Select Identity Inspector then add
"User Defined Runtime Attribute".See screen Shot for more details
Add CALayer+XibConfiguration.h & CALayer+XibConfiguration.m in your Project
for CALayer+XibConfiguration open this link & download CALayer+XibConfiguration
There are several ways by which you can achieve this like using category, subclassing UIButton, or create a function in a class (may be base class inherited by all other classes.), etc.
By using function you ca do
func cutomizeButton(frame : CGRect, title : String) -> UIButton {
let button : UIButton = UIButton(type: .Custom)
button.frame = frame
button.layer.borderWidth = 5.0
button.layer.borderColor = UIColor.whiteColor().CGColor;
button.layer.masksToBounds = true
button.titleLabel?.text = title;
//do other stuff
return button;
}
In helper class make method like
func customiseButton(button:UIButton){
//White Border
let passwordBorder = CALayer()
let width = CGFloat(5.0)
passwordBorder.borderColor = UIColor.whiteColor().CGColor
passwordBorder.frame = CGRect(x: 0, y: 0, width: button.frame.size.width, height: button.frame.size.height)
passwordBorder.borderWidth = width
button.layer.addSublayer(passwordBorder)
button.layer.masksToBounds = true
}
and in ViewController call this method as
HelperClass().customiseButton(passwordField)
You can try with this Extension for round button:
extension UIButton{
func roundCorners(corners:UIRectCorner, radius: CGFloat){
let borderLayer = CAShapeLayer()
borderLayer.frame = self.layer.bounds
borderLayer.strokeColor = UIColor.green.cgColor
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.lineWidth = 10.5
let path = UIBezierPath(roundedRect: self.bounds,
byRoundingCorners: corners,
cornerRadii: CGSize(width: radius, height: radius))
borderLayer.path = path.cgPath
self.layer.addSublayer(borderLayer)
}
}
I need to draw a shape like that one:
And animate the color path when you are scrolling. I have been for 2 hours trying several things but don't reach the final way to overcome that. I have tried creating a custom UIView, create a CShapeLayer and then a UIBezierPath to that layer, and finally adding the subview to a view in the viewdidload, but this does not work. What else can I do to approach that? I will start with the shapes that are the most complex things, as the label will be just aligned to the shape.
----FIRST PART SOLVED, DRAWRECT NOW APPEARS, BUT HOW DO I ANIMATE?----
That's my updated code in my drawRect method.
class CustomOval: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
override func drawRect(rect: CGRect) {
//SHAPE 2
let rectanglePath2 = UIBezierPath(roundedRect: CGRectMake(135, 177, 20, 70), cornerRadius: 0)
let shapeLayer2 = CAShapeLayer()
shapeLayer2.path = rectanglePath2.CGPath
shapeLayer2.bounds = CGRect(x: 135, y: 177, width: 20, height: 70)
shapeLayer2.lineWidth = 5.0
let label2 = UILabel()
label2.frame = CGRectMake(150 + rectanglePath2.bounds.width, rectanglePath2.bounds.height + 120, 100, 50)
label2.text = "Label 2"
self.addSubview(label2)
//// Rectangle Drawing
UIColor.blueColor().setFill()
rectanglePath2.fill()
//SHAPE 3
let rectanglePath3 = UIBezierPath(roundedRect: CGRectMake(135, 237, 20, 70), cornerRadius: 0)
let shapeLayer3 = CAShapeLayer()
shapeLayer3.path = rectanglePath3.CGPath
shapeLayer3.bounds = CGRect(x: 135, y: rectanglePath2.bounds.maxY, width: 20, height: 70)
shapeLayer3.lineWidth = 5.0
//// Rectangle Drawing
UIColor.redColor().setFill()
rectanglePath3.fill()
let label3 = UILabel()
label3.frame = CGRectMake(rectanglePath3.bounds.width + 150, rectanglePath3.bounds.height + 190, 100, 50)
label3.text = "Label 3"
self.addSubview(label3)
//SHAPE 1
let rectanglePath = UIBezierPath(roundedRect: CGRectMake(104, 24, 80, 155), cornerRadius: 40)
let shapeLayer = CAShapeLayer()
shapeLayer.path = rectanglePath.CGPath
shapeLayer.bounds = CGRect(x: 104, y: 24, width: 80, height: 155)
shapeLayer.lineWidth = 5.0
//// Rectangle Drawing
UIColor.grayColor().setFill()
rectanglePath.fill()
}
}
-UPDATE ANIMATION-
var shapeLayer = CAShapeLayer()
override init(frame: CGRect) {
shapeLayer = CAShapeLayer()
super.init(frame: frame)
animateShape1()
}
required init(coder aDecoder: NSCoder) {
shapeLayer = CAShapeLayer()
super.init(coder: aDecoder)!
animateShape1()
}
func animateShape1(){
let animation = CABasicAnimation(keyPath: "fillColor")
animation.fromValue = UIColor.whiteColor().CGColor
animation.toValue = UIColor.redColor().CGColor
animation.duration = 5 //2 sec
shapeLayer.fillColor = UIColor.redColor().CGColor //color end value
layer.addAnimation(animation, forKey: "somekey")
}
My main questions now are:
How do I set an image inside the CAShapeLayer? I have tried with: FIXED
Calling it from the init
func addImage(){
let imageSubLayer = CALayer()
let image = UIImage(named: "paint.png")
imageSubLayer.contents = image?.CGImage
imageSubLayer.bounds = (frame: CGRect(x: shapeLayer.bounds.width/2, y: shapeLayer.bounds.height/2, width: 50, height: 50))
shapeLayer.addSublayer(imageSubLayer)
}
I have also tried, and the one that has worked is: But i dont want a tiled image. I have also tried without tiled and this does not work
CGContextSaveGState(context)
rectanglePath.addClip()
CGContextScaleCTM(context, 0, (image?.size.height)!)
CGContextDrawTiledImage(context, CGRectMake(20, 20, 50, 50), image!.CGImage)
CGContextRestoreGState(context)
I also need to animate the process, and I have tried with the reply
of #s0urce but unfortunately, it is not drawing. Do I need to do
something else?
You should never add sublayers inside drawRect method. It would be much better to do this inside init or initWithFrame: method of a view or at least inside viewDidLoad method of view controller.
You need to set bounds of CAShapeLayer otherwise it would be CGRectZero. Don't forget that points of UIBezierPath should be in the coordinate system of CAShapeLayer.
Color animation sample code:
let animation = CABasicAnimation(keyPath: "fillColor")
animation.fromValue = UIColor.whiteColor().CGColor
animation.toValue = UIColor.redColor().CGColor
animation.duration = 2 //2 sec
layer.fillColor = UIColor.redColor().CGColor //color end value
layer.addAnimation(animation, forKey: "somekey")
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))
}
}