I am new to swift. Your help will be really appreciated.
I have two textfields in my application. How would I create same UI as given in the pic below.
I want to create textfields with only one below border as given in the screenshot.
https://www.dropbox.com/s/wlizis5zybsvnfz/File%202017-04-04%2C%201%2052%2024%20PM.jpeg?dl=0
#IBOutlet var textField: UITextField! {
didSet {
let border = CALayer()
let width: CGFloat = 1 // this manipulates the border's width
border.borderColor = UIColor.darkGray.cgColor
border.frame = CGRect(x: 0, y: textField.frame.size.height - width,
width: textField.frame.size.width, height: textField.frame.size.height)
border.borderWidth = width
textField.layer.addSublayer(border)
textField.layer.masksToBounds = true
}
}
Create a subclass of UITextField so you can reuse this component across multiple views without have to re implement the drawing code. Expose various properties via #IBDesignable and #IBInspectable and you can have control over color and thickness in the story board. Also - implement a "redraw" on by overriding layoutSubviews so the border will adjust if you are using auto layout and there is an orientation or perhaps constraint based animation. That all said - effectively your subclass could look like this:
import UIKit
class Field: UITextField {
private let border = CAShapeLayer()
#IBInspectable var color: UIColor = UIColor.blue {
didSet {
border.strokeColor = color.cgColor
}
}
#IBInspectable var thickness: CGFloat = 1.0 {
didSet {
border.lineWidth = thickness
}
}
override func draw(_ rect: CGRect) {
self.borderStyle = .none
let from = CGPoint(x: 0, y: rect.height)
let here = CGPoint(x: rect.width, y: rect.height)
let path = borderPath(start: from, end: here).cgPath
border.path = path
border.strokeColor = color.cgColor
border.lineWidth = thickness
border.fillColor = nil
layer.addSublayer(border)
}
override func layoutSubviews() {
super.layoutSubviews()
let from = CGPoint(x: 0, y: bounds.height)
let here = CGPoint(x: bounds.width, y: bounds.height)
border.path = borderPath(start: from, end: here).cgPath
}
private func borderPath(start: CGPoint, end: CGPoint) -> UIBezierPath {
let path = UIBezierPath()
path.move(to: start)
path.addLine(to: end)
return path
}
}
Then when you add a text field view to your story board - update the class in the Identity Inspector to use this subclass, Field - and then in the attributes inspector, you can set color and thickness.
Add border at Bottom in UITextField call below function:
func setTextFieldBorder(_ dimension: CGRect) -> CALayer {
let border = CALayer()
let width = CGFloat(2.0)
border.borderColor = UIColor.darkGray.cgColor
border.frame = CGRect(x: 0, y: dimension.size.height - width, width: dimension.size.width, height: dimension.size.height)
border.borderWidth = width
return border
}
How to set UITextField border in textField below sample code for that:
txtDemo.layer.addSublayer(setTextFieldBorder(txtDemo.frame))
txtDemo.layer.masksToBounds = true
Where txtDemo is IBOutlet of UITextField.
Related
I've a custom UITabBar. Its bar has a simple but customised shape: its height is bigger then default one, has rounded corners and (important) a shadow layer on the top.
The result is this:
Now I've to add an element that shows the selected section on the top of the bar, to achieve this:
The problem is that no matter the way I choose to add this element (add a subview to the bar or add a new sublayer) but the new element will always be drawn outside the corners. I suppose this is because I can't enable the clipping mask (if I enable the clipping mask I'll kill the shadow and also, more important, the bezierpath)
Do you have any tips for this?
Basically, the goal should be:
have an element that moves horizontally (animated) but cannot be drawn outside the parent (the tabbar)
Actually, the code to draw the custom tabBar is:
class CustomTabBar: UITabBar {
/// The layer that defines the custom shape
private var shapeLayer: CALayer?
/// The radius for the border of the bar
var borderRadius: CGFloat = 0
override func layoutSubviews() {
super.layoutSubviews()
// aspect and shadow
isTranslucent = false
backgroundColor = UIColor.white
tintColor = AZTheme.PaletteColor.primaryColor
shadowImage = nil
layer.masksToBounds = false
layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 0.1
layer.shadowOffset = CGSize(width: 0, height: -1)
layer.shadowRadius = 10
}
override func draw(_ rect: CGRect) {
drawShape()
}
/// Draw and apply the custom shape to the bar
func drawShape() {
let shapeLayer = CAShapeLayer()
shapeLayer.path = createPath()
shapeLayer.fillColor = AZTheme.tabBarControllerBackgroundColor.cgColor
if let oldShapeLayer = self.shapeLayer {
self.layer.replaceSublayer(oldShapeLayer, with: shapeLayer)
} else {
self.layer.insertSublayer(shapeLayer, at: 0)
}
self.shapeLayer = shapeLayer
}
}
// MARK: - Private functions
extension CustomTabBar {
/// Return the custom shape for the bar
internal func createPath() -> CGPath {
let height: CGFloat = self.frame.height
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addArc(withCenter: CGPoint(x: borderRadius, y: 0), radius: borderRadius, startAngle: CGFloat.pi, endAngle: CGFloat.pi * (3/2), clockwise: true)
path.addLine(to: CGPoint(x: frame.width - borderRadius, y: -borderRadius))
path.addArc(withCenter: CGPoint(x: frame.width - borderRadius, y: 0), radius: borderRadius, startAngle: CGFloat.pi * (3/2), endAngle: 0, clockwise: true)
path.addLine(to: CGPoint(x: frame.width, y: height))
path.addLine(to: CGPoint(x: 0, y: height))
path.close()
return path.cgPath
}
}
I solved splitting the owner of custom shape from the owner of the shadow in 2 different views. So I'm using 3 views achieve the goal.
CustomTabBar: has default size and casts shadow with offset.
|
└ SelectorContainer: is a view with custom shape (BezierPath) that is
positioned on the top of the TabBar to graphically "extend" the view
and have the feeling of a bigger TabBar. It has rounded corners on
the top-right, top-left margin. MaskToBounds enabled.
|
└ Selector: simple view that change the its origin through animation.
See the result here
The code:
class CustomTabBar: UITabBar {
/// The corner radius value for the top-left, top-right corners of the TabBar
var borderRadius: CGFloat = 0
/// Who is containing the selector. Is a subview of the TabBar.
private var selectorParent: UIView?
/// Who moves itself following the current section. Is a subview of ```selectorParent```.
private var selector: UIView?
/// The height of the ```selector```
private var selectorHeight: CGFloat = 5
/// The number of sections handled by the TabBarController.
private var numberOfSections: Int = 0
override func layoutSubviews() {
super.layoutSubviews()
isTranslucent = false
backgroundColor = UIColor.white
tintColor = AZTheme.PaletteColor.primaryColor
shadowImage = nil
layer.masksToBounds = false
layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 0.1
layer.shadowOffset = CGSize(width: 0, height: -1)
layer.shadowRadius = 10
}
}
// MARK: - Private functions
extension CustomTabBar {
/// Create the selector element on the top of the TabBar
func setupSelectorView(numberOfSections: Int) {
self.numberOfSections = numberOfSections
// delete previous subviews (if exist)
if let selectorContainer = self.selectorParent {
selectorContainer.removeFromSuperview()
self.selector?.removeFromSuperview()
self.selectorParent = nil
self.selector = nil
}
// SELECTOR CONTAINER
let selectorContainerRect: CGRect = CGRect(x: 0,
y: -borderRadius,
width: frame.width,
height: borderRadius)
let selectorContainer = UIView(frame: selectorContainerRect)
selectorContainer.backgroundColor = UIColor.white
selectorContainer.AZ_roundCorners([.topLeft, .topRight], radius: borderRadius)
selectorContainer.layer.masksToBounds = true
self.addSubview(selectorContainer)
// SELECTOR
let selectorRect: CGRect = CGRect(x: 0,
y: 0,
width: selectorContainer.frame.width / CGFloat(numberOfSections),
height: selectorHeight)
let selector = UIView(frame: selectorRect)
selector.backgroundColor = AZTheme.PaletteColor.primaryColor
selectorContainer.addSubview(selector)
// add views to hierarchy
self.selectorParent = selectorContainer
self.selector = selector
}
/// Animate the position of the selector passing the index of the new section
func animateSelectorTo(sectionIndex: Int) {
guard let selectorContainer = self.selectorParent, let selector = self.selector else { return }
selector.layer.removeAllAnimations()
let sectionWidth: CGFloat = selectorContainer.frame.width / CGFloat(numberOfSections)
let newCoord = CGPoint(x: sectionWidth * CGFloat(sectionIndex), y: selector.frame.origin.y)
UIView.animate(withDuration: 0.25, delay: 0, options: UIView.AnimationOptions.curveEaseOut, animations: { [weak self] in
self?.selector?.frame.origin = newCoord
}, completion: nil)
}
}
How to draw View like this.
After research I got context.fillRects method can be used. But how to find the exact rects for this.
let context = UIGraphicsGetCurrentContext()
context?.setFillColor(UIColor.red.cgColor)
context?.setAlpha(0.5)
context?.fill([<#T##rects: [CGRect]##[CGRect]#>])
How to achieve this result?
Background: Blue.
Overlay(Purple): 50% opacity that contains square hole in the center
First create your view and then draw everything with two UIBezierPaths: one is describing the inside rect (the hole) and the other one runs along the borders on your screen (externalPath). This way of drawing ensures that the blue rect in the middle is a true hole and not drawn on top of the purple view.
let holeWidth: CGFloat = 200
let hollowedView = UIView(frame: view.frame)
hollowedView.backgroundColor = UIColor.clear
//Initialise the layer
let hollowedLayer = CAShapeLayer()
//Draw your two paths and append one to the other
let holePath = UIBezierPath(rect: CGRect(origin: CGPoint(x: (view.frame.width - holeWidth) / 2, y: (view.frame.height - holeWidth) / 2), size: CGSize(width: holeWidth, height: holeWidth)))
let externalPath = UIBezierPath(rect: hollowedView.frame).reversing()
holePath.append(externalPath)
holePath.usesEvenOddFillRule = true
//Assign your path to the path property of your layer
hollowedLayer.path = holePath.cgPath
hollowedLayer.fillColor = UIColor.purple.cgColor
hollowedLayer.opacity = 0.5
//Add your hollowedLayer to the layer of your hollowedView
hollowedView.layer.addSublayer(hollowedLayer)
view.addSubview(hollowedView)
The result looks like this :
Create a custom UIView with background color blue.
class CustomView: UIView {
// Try adding a rect and fill color.
override func draw(_ rect: CGRect) {
let ctx = UIGraphicsGetCurrentContext()
ctx!.beginPath()
//Choose the size based on the size required.
ctx?.addRect(CGRect(x: 20, y: 20, width: rect.maxX - 40, height: rect.maxY - 40))
ctx!.closePath()
ctx?.setFillColor(UIColor.red.cgColor)
ctx!.fillPath()
}
}
I just ended up with this.
Code:
createHoleOnView()
let blurView = createBlurEffect(style: style)
self.addSubview(blurView)
Method Create Hole:
private func createHoleOnView() {
let maskView = UIView(frame: self.frame)
maskView.clipsToBounds = true;
maskView.backgroundColor = UIColor.clear
func holeRect() -> CGRect {
var holeRect = CGRect(x: 0, y: 0, width: scanViewSize.rawValue.width, height: scanViewSize.rawValue.height)
let midX = holeRect.midX
let midY = holeRect.midY
holeRect.origin.x = maskView.frame.midX - midX
holeRect.origin.y = maskView.frame.midY - midY
self.holeRect = holeRect
return holeRect
}
let outerbezierPath = UIBezierPath.init(roundedRect: self.bounds, cornerRadius: 0)
let holePath = UIBezierPath(roundedRect: holeRect(), cornerRadius: holeCornerRadius)
outerbezierPath.append(holePath)
outerbezierPath.usesEvenOddFillRule = true
let hollowedLayer = CAShapeLayer()
hollowedLayer.fillRule = kCAFillRuleEvenOdd
hollowedLayer.fillColor = outerColor.cgColor
hollowedLayer.path = outerbezierPath.cgPath
if self.holeStyle == .none {
hollowedLayer.opacity = 0.8
}
maskView.layer.addSublayer(hollowedLayer)
switch self.holeStyle {
case .none:
self.addSubview(maskView)
break
case .blur(_):
self.mask = maskView;
break
}
}
UIView's Extension function for Create Blur:
internal func createBlurEffect(style: UIBlurEffectStyle = .extraLight) -> UIView {
let blurEffect = UIBlurEffect(style: style)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
blurEffectView.frame = self.bounds
return blurEffectView
}
i have applied below code in viewdidload() but using this code border is arrive but it scrolling with table content.i want fix border at top and bottom side.
here my code is ->
let topBorder = CAShapeLayer()
let topPath = UIBezierPath()
topPath.move(to: CGPoint(x: 0, y: 0))
topPath.addLine(to: CGPoint(x: tblFilter.frame.width, y: 0))
topBorder.path = topPath.cgPath
topBorder.strokeColor = UIColor.red.cgColor
topBorder.lineWidth = 1.0
topBorder.fillColor = UIColor.red.cgColor
tblFilter.layer.addSublayer(topBorder)
let bottomBorder = CAShapeLayer()
let bottomPath = UIBezierPath()
bottomPath.move(to: CGPoint(x: 0, y: tblFilter.frame.height))
bottomPath.addLine(to: CGPoint(x: tblFilter.frame.width, y: tblFilter.frame.height))
bottomBorder.path = bottomPath.cgPath
bottomBorder.strokeColor = UIColor.red.cgColor
bottomBorder.lineWidth = 1.0
bottomBorder.fillColor = UIColor.red.cgColor
tblFilter.layer.addSublayer(bottomBorder)
give me suggetion and thanks
I am using below methods to add borders to any view. Please take a look if it helps you.
//MARK: - Add Border to View -
func addTopBorderWithColor(_ objView : UIView, color: UIColor, width: CGFloat) {
let border = CALayer()
border.backgroundColor = color.cgColor
border.frame = CGRect(x: 0, y: 0, width: objView.frame.size.width, height: width)
objView.layer.addSublayer(border)
}
func addBottomBorderWithColor(_ objView : UIView, color: UIColor, width: CGFloat) {
let border = CALayer()
border.backgroundColor = color.cgColor
border.frame = CGRect(x: 0, y: objView.frame.size.height - width, width: objView.frame.size.width, height: width)
objView.layer.addSublayer(border)
}
I have a method which uses few enums to achieve what you want to do. Please see objective c code below
-(void) addUIViewAt:(postionOfView) position OfView:(UIView *) view ofHeight:(int) height ofBackgroundColor:(UIColor *)backgroundColor{
UIView *viewForBorder = [[UIView alloc] init] ;
if (position == positionTop){
[viewForBorder setFrame:CGRectMake(view.bounds.origin.x
, view.bounds.origin.y, view.bounds.size.width, height)];
}
else if (position == positionBottom){
[viewForBorder setFrame:CGRectMake(view.bounds.origin.x
, view.bounds.size.height - height, view.bounds.size.width, height)];
}
else if (position == positionLeft){
[viewForBorder setFrame:CGRectMake(view.bounds.origin.x
, view.bounds.origin.y, height, view.bounds.size.height)];
}
else{
[viewForBorder setFrame:CGRectMake(view.bounds.size.width - height
, view.bounds.origin.y, height, view.bounds.size.height)];
}
[viewForBorder setBackgroundColor:backgroundColor];
[view addSubview:viewForBorder];
// viewForBorder.layer.zPosition = MAXFLOAT;
}
Also use these enums for the method above
/**
this used to detect the position of border overlay on a view
*/
typedef enum : NSUInteger {
positionTop,
positionRight,
positionLeft,
positionBottom,
} postionOfView;
Just feed this method with appropriate values
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)
}
}
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))
}
}