First time using BezierPaths, wondering how this function is actually supposed to be implemented. Currently the bezier path moves within the frame of the image, as opposed to drawing on screen.
Is there a better way to do it?
func drawLineFromPoint(start : CGPoint, toPoint end:CGPoint, ofColor lineColor: UIColor, inView view:UIView) {
var maxWidth = abs(start.x - end.x)
var maxHeight = abs(start.y - end.y)
var contextSize : CGSize!
if maxWidth == 0 {
contextSize = CGSize(width: 1, height: maxHeight)
}else {
contextSize = CGSize(width: maxWidth, height: 1)
}
//design the path
UIGraphicsBeginImageContextWithOptions(contextSize, false, 0)
var path = UIBezierPath()
path.lineWidth = 1.0
lineColor.set()
//draw the path and make visible
path.moveToPoint(start)
path.addLineToPoint(end)
path.stroke()
//create image from path and add to subview
var image = UIGraphicsGetImageFromCurrentImageContext()
var imageView = UIImageView(image: image)
view.addSubview(imageView)
UIGraphicsEndImageContext()
}
Ended up doing it this way:
func drawLineFromPoint(start : CGPoint, toPoint end:CGPoint, ofColor lineColor: UIColor, inView view:UIView) {
//design the path
let path = UIBezierPath()
path.move(to: start)
path.addLine(to: end)
//design path in layer
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = lineColor.CGColor
shapeLayer.lineWidth = 1.0
view.layer.addSublayer(shapeLayer)
}
Swift 3.1 Version of William Falcon's Answer + Improved
This is just an updated version of already accepted answer, and I just added a little bit more to it.
func drawLineFromPointToPoint(startX: Int, toEndingX endX: Int, startingY startY: Int, toEndingY endY: Int, ofColor lineColor: UIColor, widthOfLine lineWidth: CGFloat, inView view: UIView) {
let path = UIBezierPath()
path.move(to: CGPoint(x: startX, y: startY))
path.addLine(to: CGPoint(x: endX, y: endY))
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = lineColor.cgColor
shapeLayer.lineWidth = lineWidth
view.layer.addSublayer(shapeLayer)
}
And of course the implementation would be
drawLineFromPointToPoint(startX: Int, toEndingX: Int, startingY: Int, toEndingY: Int, ofColor: UIColor, widthOfLine: CGFloat, inView: UIView)
What I changed from accepted answer
I changed the vars to lets, and made it easier to input the start and end of both the x and the y. I also allow the user to change the width of the line.
I chose for the values to be of type Int, but you can change those to be the other allowed options.
Swift 4
func drawLineFromPoint(start : CGPoint, toPoint end:CGPoint, ofColor lineColor: UIColor, inView view:UIView) {
//design the path
let path = UIBezierPath()
path.move(to: start)
path.addLine(to: end)
//design path in layer
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = lineColor.cgColor
shapeLayer.lineWidth = 1.0
view.layer.addSublayer(shapeLayer)
}
To draw horizontal line on top:
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: 0, y: 0))
path.addLineToPoint(CGPoint(x: yourView.frame.width, y: 0))
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.CGPath
shapeLayer.strokeColor = UIColor.lightGrayColor().CGColor
shapeLayer.lineWidth = 0.5
yourView.layer.addSublayer(shapeLayer)
To draw horizontal line on bottom:
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: 0, y: yourView.frame.height))
path.addLineToPoint(CGPoint(x: yourView.frame.width, y: yourView.frame.height))
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.CGPath
shapeLayer.strokeColor = UIColor.lightGrayColor().CGColor
shapeLayer.lineWidth = 0.5
yourView.layer.addSublayer(shapeLayer)
Related
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)
I am drawing a line using UIBezierPath and CAShapeLayer. I need to add gradient to the line as top colour to white and bottom colour to red. I followed few solutions from StackOverlow, no luck. Below is my code.
private func drawLine(start: CGPoint, toPoint end: CGPoint) {
//design the path
let path = UIBezierPath()
path.move(to: start)
path.addLine(to: end)
//design path in layer
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 2.0
self.layer.addSublayer(shapeLayer)
let gradient = CAGradientLayer()
gradient.frame = path.bounds
gradient.colors = [UIColor.white.cgColor, UIColor.red.cgColor]
let shapeMask = CAShapeLayer()
shapeMask.path = path.cgPath
gradient.mask = shapeMask
self.layer.addSublayer(gradient)
}
Another try from referring StackOverflow was
private func drawLine(start: CGPoint, toPoint end: CGPoint) {
//design the path
let path = UIBezierPath()
path.move(to: start)
path.addLine(to: end)
//design path in layer
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 2.0
self.layer.addSublayer(shapeLayer)
addGradientLayer(to: shapeLayer, path: path)
}
private func addGradientLayer(to layer: CALayer, path: UIBezierPath) {
let gradientMask = CAShapeLayer()
gradientMask.contentsScale = UIScreen.main.scale
gradientMask.strokeColor = UIColor.white.cgColor
gradientMask.path = path.cgPath
let gradientLayer = CAGradientLayer()
gradientLayer.mask = gradientMask
gradientLayer.frame = layer.frame
gradientLayer.contentsScale = UIScreen.main.scale
gradientLayer.colors = [UIColor.white.cgColor, UIColor.red.cgColor]
layer.addSublayer(gradientLayer)
}
Both just draws the red line (stroke).
The short answer to your problem is most likely that you need to call cgPath.copy(strokingWithWidth: 3.0, lineCap: .butt, lineJoin: .bevel, miterLimit: 1.0) to convert your stroking path on mask to fill path.
But to break this down step by step...
First, you are drawing a gradient, which is masked by a line. Not drawing a line that has applied gradient. So only one layer needs to be added and it needs to be a gradient layer. So a first step should draw a rectangle with a gradient:
func drawLine(start: CGPoint, toPoint end: CGPoint) {
let path = UIBezierPath()
path.move(to: start)
path.addLine(to: end)
let gradientLayer = CAGradientLayer()
gradientLayer.frame = path.bounds
gradientLayer.colors = [UIColor.white.cgColor, UIColor.red.cgColor]
self.layer.addSublayer(gradientLayer)
}
Now that gradient is visible lets apply some mask to it. A circle should be an easy task:
func drawLine(start: CGPoint, toPoint end: CGPoint) {
let path = UIBezierPath()
path.move(to: start)
path.addLine(to: end)
let gradientLayer = CAGradientLayer()
gradientLayer.frame = path.bounds
gradientLayer.colors = [UIColor.white.cgColor, UIColor.red.cgColor]
gradientLayer.mask = {
let layer = CAShapeLayer()
layer.path = UIBezierPath(ovalIn: path.bounds).cgPath
return layer
}()
self.layer.addSublayer(gradientLayer)
}
if you do this and you used some non-zero starting coordinates of the path then you will see that the circle is cut off. The path that is being masked must be in gradient layer coordinates so path.bounds needs to be corrected to
gradientLayer.mask = {
let layer = CAShapeLayer()
layer.path = UIBezierPath(ovalIn: CGRect(x: 0.0, y: 0.0, width: path.bounds.width, height: path.bounds.height)).cgPath
return layer
}()
now that coordinates are OK let's convert this to a line. It gets a little bit more complicated because we need to subtract the origin of original path:
func drawLine(start: CGPoint, toPoint end: CGPoint) {
let path = UIBezierPath()
path.move(to: start)
path.addLine(to: end)
let gradientLayer = CAGradientLayer()
gradientLayer.frame = path.bounds
gradientLayer.colors = [UIColor.white.cgColor, UIColor.red.cgColor]
gradientLayer.mask = {
let layer = CAShapeLayer()
let lineStart = CGPoint(x: start.x - path.bounds.minX,
y: start.y - path.bounds.minY)
let lineEnd = CGPoint(x: end.x - path.bounds.minX,
y: end.y - path.bounds.minY)
let path = UIBezierPath()
path.move(to: lineStart)
path.addLine(to: lineEnd)
layer.path = path.cgPath
return layer
}()
self.layer.addSublayer(gradientLayer)
}
there are other ways to move the path. A transform could be used just as well. So if this approach is not nice enough for you please choose whichever you wish.
However, this now draws nothing at all. And the reason is that when using mask a fill approach is always used. And filling a line does nothing as it has no area. You could use multiple lines to draw a custom shape for instance. And in your case you could use 4 lines which would represent a rectangle which is the line you expect to draw.
However, drawing lines is a pain so thankfully there is a not-very-intuitively designed API which converts your stroked path to a fill path. So in your case it converts a line into a rectangle with a defined width (and other properties which are only needed for more complex strokes). You can use
public func copy(strokingWithWidth lineWidth: CGFloat, lineCap: CGLineCap, lineJoin: CGLineJoin, miterLimit: CGFloat, transform: CGAffineTransform = .identity) -> CGPath
The code is not documented but in online documentation you may find
Discussion The new path is created so that filling the new path draws
the same pixels as stroking the original path with the specified line
style.
Now the end result should look something like this:
func drawLine(start: CGPoint, toPoint end: CGPoint) {
let path = UIBezierPath()
path.move(to: start)
path.addLine(to: end)
let gradientLayer = CAGradientLayer()
gradientLayer.frame = path.bounds
gradientLayer.colors = [UIColor.white.cgColor, UIColor.red.cgColor]
gradientLayer.mask = {
let layer = CAShapeLayer()
let lineStart = CGPoint(x: start.x - path.bounds.minX,
y: start.y - path.bounds.minY)
let lineEnd = CGPoint(x: end.x - path.bounds.minX,
y: end.y - path.bounds.minY)
let path = UIBezierPath()
path.move(to: lineStart)
path.addLine(to: lineEnd)
layer.path = path.cgPath.copy(strokingWithWidth: 3.0, lineCap: .butt, lineJoin: .bevel, miterLimit: 1.0)
return layer
}()
self.layer.addSublayer(gradientLayer)
}
I hardcoded line width to 3.0 but you are probably best adding this as a method parameter or as view property.
To play around with different setup and see results quickly from this answer you can create a new project and replace your view controller code to the following:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onTap)))
view.addSubview({
let label = UILabel(frame: CGRect(x: 20.0, y: 70.0, width: 300.0, height: 20.0))
label.text = "Tap to toggle through drawing types"
return label
}())
}
private var instance: Int = 0
private var currentView: UIView?
#objc private func onTap() {
currentView?.removeFromSuperview()
let view = MyView(frame: CGRect(x: 30.0, y: 100.0, width: 100.0, height: 200.0))
currentView = view
view.backgroundColor = .lightGray
self.view.addSubview(view)
view.drawLine(type: instance, start: CGPoint(x: 90.0, y: 10.0), toPoint: .init(x: 10.0, y: 150.0))
instance += 1
}
}
private extension ViewController {
class MyView: UIView {
func drawLine(type: Int, start: CGPoint, toPoint end: CGPoint) {
let methods = [drawLine1, drawLine2, drawLine3, drawLine4, drawLine5]
methods[type%methods.count](start, end)
}
private func drawLine1(start: CGPoint, toPoint end: CGPoint) {
let path = UIBezierPath()
path.move(to: start)
path.addLine(to: end)
let gradientLayer = CAGradientLayer()
gradientLayer.frame = path.bounds
gradientLayer.colors = [UIColor.white.cgColor, UIColor.red.cgColor]
self.layer.addSublayer(gradientLayer)
}
private func drawLine2(start: CGPoint, toPoint end: CGPoint) {
let path = UIBezierPath()
path.move(to: start)
path.addLine(to: end)
let gradientLayer = CAGradientLayer()
gradientLayer.frame = path.bounds
gradientLayer.colors = [UIColor.white.cgColor, UIColor.red.cgColor]
gradientLayer.mask = {
let layer = CAShapeLayer()
layer.path = UIBezierPath(ovalIn: path.bounds).cgPath
return layer
}()
self.layer.addSublayer(gradientLayer)
}
private func drawLine3(start: CGPoint, toPoint end: CGPoint) {
let path = UIBezierPath()
path.move(to: start)
path.addLine(to: end)
let gradientLayer = CAGradientLayer()
gradientLayer.frame = path.bounds
gradientLayer.colors = [UIColor.white.cgColor, UIColor.red.cgColor]
gradientLayer.mask = {
let layer = CAShapeLayer()
layer.path = UIBezierPath(ovalIn: CGRect(x: 0.0, y: 0.0, width: path.bounds.width, height: path.bounds.height)).cgPath
return layer
}()
self.layer.addSublayer(gradientLayer)
}
private func drawLine4(start: CGPoint, toPoint end: CGPoint) {
let path = UIBezierPath()
path.move(to: start)
path.addLine(to: end)
let gradientLayer = CAGradientLayer()
gradientLayer.frame = path.bounds
gradientLayer.colors = [UIColor.white.cgColor, UIColor.red.cgColor]
gradientLayer.mask = {
let layer = CAShapeLayer()
let lineStart = CGPoint(x: start.x - path.bounds.minX,
y: start.y - path.bounds.minY)
let lineEnd = CGPoint(x: end.x - path.bounds.minX,
y: end.y - path.bounds.minY)
let path = UIBezierPath()
path.move(to: lineStart)
path.addLine(to: lineEnd)
layer.path = path.cgPath
return layer
}()
self.layer.addSublayer(gradientLayer)
}
private func drawLine5(start: CGPoint, toPoint end: CGPoint) {
let path = UIBezierPath()
path.move(to: start)
path.addLine(to: end)
let gradientLayer = CAGradientLayer()
gradientLayer.frame = path.bounds
gradientLayer.colors = [UIColor.white.cgColor, UIColor.red.cgColor]
gradientLayer.mask = {
let layer = CAShapeLayer()
let lineStart = CGPoint(x: start.x - path.bounds.minX,
y: start.y - path.bounds.minY)
let lineEnd = CGPoint(x: end.x - path.bounds.minX,
y: end.y - path.bounds.minY)
let path = UIBezierPath()
path.move(to: lineStart)
path.addLine(to: lineEnd)
layer.path = path.cgPath.copy(strokingWithWidth: 3.0, lineCap: .butt, lineJoin: .bevel, miterLimit: 1.0)
return layer
}()
self.layer.addSublayer(gradientLayer)
}
}
}
I've created an extension for UIView that allows me to make a concave shape.
extension UIView {
func createConcave(depth: CGFloat) {
let width = self.bounds.width
let height = self.bounds.height
let path = UIBezierPath()
let p0 = CGPoint(x: 0, y: 0)
let p2 = CGPoint(x: width, y: 0)
let p1 = CGPoint(x: width / 2, y: depth)
path.move(to: p0)
path.addQuadCurve(to: p2, controlPoint: p1)
path.addLine(to: CGPoint(x: width, y: height))
path.addLine(to: CGPoint(x: 0, y: height))
path.addLine(to: p0)
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
self.layer.masksToBounds = false
}
}
What would be a good solution to add a shadow to the view that matches the shape? Would I have to specify the shadow path to be the same path as the concave shape?
You are masking the layer to the path. Thus anything, including the shadow, will be clipped by that mask.
Instead of masking, add sublayer.
E.g.
#IBDesignable
class ConcaveView: UIView {
#IBInspectable var depth: CGFloat = 10 { didSet { updatePath() } }
#IBInspectable var fillColor: UIColor = .red { didSet { shapeLayer.fillColor = fillColor.cgColor } }
private lazy var shapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.fillColor = fillColor.cgColor
shapeLayer.shadowColor = UIColor.black.cgColor
shapeLayer.shadowRadius = 5
shapeLayer.shadowOpacity = 1
shapeLayer.shadowOffset = .zero
return shapeLayer
}()
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
func configure() {
layer.addSublayer(shapeLayer)
clipsToBounds = false
}
override func layoutSubviews() {
super.layoutSubviews()
updatePath()
}
func updatePath() {
let path = UIBezierPath()
let point0 = CGPoint(x: bounds.minX, y: bounds.minY)
let point2 = CGPoint(x: bounds.maxX, y: bounds.minY)
let point1 = CGPoint(x: bounds.width / 2, y: bounds.minY + depth)
path.move(to: point0)
path.addQuadCurve(to: point2, controlPoint: point1)
path.addLine(to: CGPoint(x: bounds.maxX, y: bounds.maxY))
path.addLine(to: CGPoint(x: bounds.minX, y: bounds.maxY))
path.addLine(to: point0)
shapeLayer.path = path.cgPath
}
}
That yields:
I am trying to draw a dotted line at bottom of UITextField, but not got success. Below is what i tried so far. Please guide.
func addDashedBorder() {
let color = UIColor.white.cgColor
let width = CGFloat(2.0)
let shapeLayer:CAShapeLayer = CAShapeLayer()
let frameSize = self.frame.size
let shapeRect = CGRect(x: 0, y: frameSize.height, width: frameSize.width, height: 2.0)
shapeLayer.bounds = shapeRect
shapeLayer.position = CGPoint(x: frameSize.width/2, y: frameSize.height - width)
shapeLayer.fillColor = UIColor.darkGray.cgColor
shapeLayer.strokeColor = color
shapeLayer.lineWidth = 2.0
// shapeLayer.lineJoin = kCALineJoinRound
shapeLayer.lineDashPattern = [6,3]
shapeLayer.path = UIBezierPath(rect: shapeRect).cgPath//UIBezierPath(roundedRect: shapeRect, cornerRadius: 0).cgPath
self.layer.addSublayer(shapeLayer)
}
}
Output getting is:
Expected is:
Finally fixed:
extension UIView {
func addDashedLine(strokeColor: UIColor, lineWidth: CGFloat) {
backgroundColor = .clear
let shapeLayer = CAShapeLayer()
shapeLayer.name = "DashedTopLine"
shapeLayer.bounds = bounds
shapeLayer.position = CGPoint(x: frame.width / 2, y: frame.height * 1.2)
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = strokeColor.cgColor
shapeLayer.lineWidth = lineWidth
shapeLayer.lineJoin = kCALineJoinRound
shapeLayer.lineDashPattern = [6, 4]
let path = CGMutablePath()
path.move(to: CGPoint.zero)
path.addLine(to: CGPoint(x: frame.width, y: 0))
shapeLayer.path = path
layer.addSublayer(shapeLayer)
}
}
Got help from here: [drawing dashed line using CALayer
]1
Try this
Use dot image :
self.textField.layer.borderWidth = 3
self.textField.layer.borderColor = (UIColor(patternImage: UIImage(named: "dot")!)).CGColor
Updated:
Use extension below
mytextfield.addDashedLine(strokeColor:.red,lineWidth:1)
extension UIView {
func addDashedLine(strokeColor: UIColor, lineWidth: CGFloat) {
backgroundColor = .clear
let shapeLayer = CAShapeLayer()
shapeLayer.name = "DashedTopLine"
shapeLayer.bounds = bounds
shapeLayer.position = CGPoint(x:30, y: 40)
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = strokeColor.cgColor
shapeLayer.lineWidth = lineWidth
shapeLayer.lineJoin = kCALineJoinRound
shapeLayer.lineDashPattern = [4, 4]
let path = CGMutablePath()
path.move(to: CGPoint.zero)
path.addLine(to: CGPoint(x:500, y: 0))
shapeLayer.path = path
layer.addSublayer(shapeLayer)
}
}
I want to know how to make a dashed line in swift like this: - - - - - - - - instead of a regular straight line like this: ----------------, I know that i can make multiple lines but that will require so much unnecessary code if i can just write it in 1 line. Btw it has to be in CoreGraphics.
Swift 4
#IBOutlet var dashedView: UIView!
func drawDottedLine(start p0: CGPoint, end p1: CGPoint, view: UIView) {
let shapeLayer = CAShapeLayer()
shapeLayer.strokeColor = UIColor.lightGray.cgColor
shapeLayer.lineWidth = 1
shapeLayer.lineDashPattern = [7, 3] // 7 is the length of dash, 3 is length of the gap.
let path = CGMutablePath()
path.addLines(between: [p0, p1])
shapeLayer.path = path
view.layer.addSublayer(shapeLayer)
}
Call function
drawDottedLine(start: CGPoint(x: dashedView.bounds.minX, y: dashedView.bounds.minY), end: CGPoint(x: dashedView.bounds.maxX, y: dashedView.bounds.minY), view: dashedView)
With the above you will have a straight line, you can also change points as you wish, for example if you change the end point's y from dashedView.bounds.minY to dashedView.bounds.maxY you will have diagonal.
If you will use it in a subclass of UIView you won't have the outlet so you will use it with self instead.
You create Dashed Lines the same way as Objective-C, except that you'll use Swift.
Here is how you do it using UIBezierPath:
let path = UIBezierPath()
let p0 = CGPoint(x: self.bounds.minX, y: self.bounds.midY)
path.move(to: p0)
let p1 = CGPoint(x: self.bounds.maxX, y: self.bounds.midY)
path.addLine(to: p1)
let dashes: [ CGFloat ] = [ 16.0, 32.0 ]
path.setLineDash(dashes, count: dashes.count, phase: 0.0)
path.lineWidth = 8.0
path.lineCapStyle = .butt
UIColor.magenta.set()
path.stroke()
Here is how to draw Dotted Lines using UIBezierPath:
let path = UIBezierPath()
let p0 = CGPointMake(CGRectGetMinX(self.bounds), CGRectGetMidY(self.bounds))
path.moveToPoint(p0)
let p1 = CGPointMake(CGRectGetMaxX(self.bounds), CGRectGetMidY(self.bounds))
path.addLineToPoint(p1)
let dashes: [ CGFloat ] = [ 0.0, 16.0 ]
path.setLineDash(dashes, count: dashes.count, phase: 0.0)
path.lineWidth = 8.0
path.lineCapStyle = .Round
UIColor.magentaColor().set()
path.stroke()
Here is how to draw Dashed Lines Using CGContext:
let context: CGContext = UIGraphicsGetCurrentContext()!
let p0 = CGPointMake(CGRectGetMinX(self.bounds), CGRectGetMidY(self.bounds))
CGContextMoveToPoint(context, p0.x, p0.y)
let p1 = CGPointMake(CGRectGetMaxX(self.bounds), CGRectGetMidY(self.bounds))
CGContextAddLineToPoint(context, p1.x, p1.y)
let dashes: [ CGFloat ] = [ 16.0, 32.0 ]
CGContextSetLineDash(context, 0.0, dashes, dashes.count)
CGContextSetLineWidth(context, 8.0)
CGContextSetLineCap(context, .Butt)
UIColor.blueColor().set()
CGContextStrokePath(context)
By Using Custom Class inherited from UIView also supports Storyboard.
All you need to do is make a view in storyboard assign class to that view and see the magic in storyboard.
#IBDesignable
class DashedLineView : UIView {
#IBInspectable var perDashLength: CGFloat = 2.0
#IBInspectable var spaceBetweenDash: CGFloat = 2.0
#IBInspectable var dashColor: UIColor = UIColor.lightGray
override func draw(_ rect: CGRect) {
super.draw(rect)
let path = UIBezierPath()
if height > width {
let p0 = CGPoint(x: self.bounds.midX, y: self.bounds.minY)
path.move(to: p0)
let p1 = CGPoint(x: self.bounds.midX, y: self.bounds.maxY)
path.addLine(to: p1)
path.lineWidth = width
} else {
let p0 = CGPoint(x: self.bounds.minX, y: self.bounds.midY)
path.move(to: p0)
let p1 = CGPoint(x: self.bounds.maxX, y: self.bounds.midY)
path.addLine(to: p1)
path.lineWidth = height
}
let dashes: [ CGFloat ] = [ perDashLength, spaceBetweenDash ]
path.setLineDash(dashes, count: dashes.count, phase: 0.0)
path.lineCapStyle = .butt
dashColor.set()
path.stroke()
}
private var width : CGFloat {
return self.bounds.width
}
private var height : CGFloat {
return self.bounds.height
}
}
Here's an easy to use UIView that draws a dashed line.
I took #Fan Jin's answer and made an UIView subclass that should work just fine with Auto Layout.
Swift 5.3, Xcode 12
import UIKit
public class DashedView: UIView {
public struct Configuration {
public var color: UIColor
public var dashLength: CGFloat
public var dashGap: CGFloat
public init(
color: UIColor,
dashLength: CGFloat,
dashGap: CGFloat) {
self.color = color
self.dashLength = dashLength
self.dashGap = dashGap
}
static let `default`: Self = .init(
color: .lightGray,
dashLength: 7,
dashGap: 3)
}
// MARK: - Properties
/// Override to customize height
public class var lineHeight: CGFloat { 1.0 }
override public var intrinsicContentSize: CGSize {
CGSize(width: UIView.noIntrinsicMetric, height: Self.lineHeight)
}
public final var config: Configuration = .default {
didSet {
drawDottedLine()
}
}
private var dashedLayer: CAShapeLayer?
// MARK: - Life Cycle
override public func layoutSubviews() {
super.layoutSubviews()
// We only redraw the dashes if the width has changed.
guard bounds.width != dashedLayer?.frame.width else { return }
drawDottedLine()
}
// MARK: - Drawing
private func drawDottedLine() {
if dashedLayer != nil {
dashedLayer?.removeFromSuperlayer()
}
dashedLayer = drawDottedLine(
start: bounds.origin,
end: CGPoint(x: bounds.width, y: bounds.origin.y),
config: config)
}
}
// Thanks to: https://stackoverflow.com/a/49305154/4802021
private extension DashedView {
func drawDottedLine(
start: CGPoint,
end: CGPoint,
config: Configuration) -> CAShapeLayer {
let shapeLayer = CAShapeLayer()
shapeLayer.strokeColor = config.color.cgColor
shapeLayer.lineWidth = Self.lineHeight
shapeLayer.lineDashPattern = [config.dashLength as NSNumber, config.dashGap as NSNumber]
let path = CGMutablePath()
path.addLines(between: [start, end])
shapeLayer.path = path
layer.addSublayer(shapeLayer)
return shapeLayer
}
}
My extension method built from #FanJins answer
extension UIView {
func createDashedLine(from point1: CGPoint, to point2: CGPoint, color: UIColor, strokeLength: NSNumber, gapLength: NSNumber, width: CGFloat) {
let shapeLayer = CAShapeLayer()
shapeLayer.strokeColor = color.cgColor
shapeLayer.lineWidth = width
shapeLayer.lineDashPattern = [strokeLength, gapLength]
let path = CGMutablePath()
path.addLines(between: [point1, point2])
shapeLayer.path = path
layer.addSublayer(shapeLayer)
}
}
Then calling method looks something like this:
let topPoint = CGPoint(x: view.frame.midX, y: view.bounds.minY)
let bottomPoint = CGPoint(x: view.frame.midX, y: view.bounds.maxY)
view.createDashedLine(from: topPoint, to: bottomPoint, color: .black, strokeLength: 4, gapLength: 6, width: 2)
Pretty easy UIView Extension for SWIFT 4.2:
extension UIView {
private static let lineDashPattern: [NSNumber] = [2, 2]
private static let lineDashWidth: CGFloat = 1.0
func makeDashedBorderLine() {
let path = CGMutablePath()
let shapeLayer = CAShapeLayer()
shapeLayer.lineWidth = UIView.lineDashWidth
shapeLayer.strokeColor = UIColor.lightGray.cgColor
shapeLayer.lineDashPattern = UIView.lineDashPattern
path.addLines(between: [CGPoint(x: bounds.minX, y: bounds.height/2),
CGPoint(x: bounds.maxX, y: bounds.height/2)])
shapeLayer.path = path
layer.addSublayer(shapeLayer)
}
}
Objective C
#user3230875 answer helped me to understand what's needed to draw a dotted line.
so I hope this answer might help an Obj-C seeker
//dashed line
path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(dashedLineStartX, dashedLineStartY)];
[path addLineToPoint:CGPointMake(dashedLineEndX, dashedLineEndY)];
path.lineWidth = 5;
[color setStroke];
CGFloat dashes[] = {4.0,8.0};
[path setLineDash:dashes count:2 phase:0.0];
path.lineCapStyle = kCGLineCapButt;
[path stroke];