iOS addLayer: how to layout right? - ios

When adding some dash line to the view of a viewController, by add sublayer.
Here is the code:
class ViewController: UIViewController {
#IBOutlet weak var tmpLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
tmpLabel.layer.borderColor = UIColor.red.cgColor;
tmpLabel.layer.borderWidth = 2
tmpLabel.addDashedBorders(padding: 50, y: tmpLabel.bounds.size.height * 0.5)
// ......
}}
extension UIView {
func addDashedBorders( padding x: CGFloat, y : CGFloat) {
//Create a CAShapeLayer
let shapeLayer = CAShapeLayer()
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 2
shapeLayer.lineDashPattern = [2,3]
let path = CGMutablePath()
path.addLines(between: [CGPoint(x: x, y: y),
CGPoint(x: bounds.size.width * 0.5 - 50, y: y)])
path.addLines(between: [CGPoint(x: bounds.size.width * 0.5 + 50, y: y),
CGPoint(x: bounds.size.width - x, y: y)])
shapeLayer.path = path
layer.addSublayer(shapeLayer)
}
}
The following image is in iPhoneX
The code is hard-coded, and not good.
I want to add the sub shape layer to the view , right beneath the label.
there is the code
class ViewController: UIViewController {
#IBOutlet weak var tmpLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
tmpLabel.layer.borderColor = UIColor.red.cgColor;
tmpLabel.layer.borderWidth = 2
view.addDashedBorder(padding: 50, y: tmpLabel.frame.origin.y)
//...
}}
extension UIView {
func addDashedBorder( padding x: CGFloat, y : CGFloat) {
let shapeLayer = CAShapeLayer()
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 2
shapeLayer.lineDashPattern = [2,3]
let path = CGMutablePath()
path.addLines(between: [CGPoint(x: x, y: y),
CGPoint(x: bounds.size.width - x, y: y)])
shapeLayer.path = path
layer.addSublayer(shapeLayer)
}
}
I tested in iPhoneX, there is a distance.
I tried view.safeAreaInsets, it is all zero.
What do I miss?

Related

How to draw same bezier path multiple times after finished the previous one?

I am using bezier path, shape layer and CABasicAnimation to draw view animately. But it draw view only single time. How to draw same bezier path multiple times after finished the previous one ?
This is the attached code for creating this type of waveform. Please check...
class ViewController: UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var graphView: UIView!
// MARK:- PROPERTIES
var movingPoint: CGFloat = 0.0
var startInterval: CGFloat = 10.0
var noOfIntervals: CGFloat = 0
var intervalWidth: CGFloat = 0
var pWaveWidth: CGFloat = 20.0
var qrsWaveWidth: CGFloat = 15
var tWaveWidth: CGFloat = 30
var gapBetweenTwoIntervals: CGFloat = 50
let shapeLayer = CAShapeLayer()
var bezierPath = UIBezierPath()
let animation = CABasicAnimation(keyPath: "strokeEnd")
override func viewDidLoad() {
super.viewDidLoad()
graphView.translatesAutoresizingMaskIntoConstraints = true
graphView.frame.size = CGSize(width: 100000, height: 150)
DispatchQueue.main.async {
self.graphView.layoutIfNeeded()
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
createGraph()
}
#objc func createGraph() {
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 2.0
_ = createBezierPath()
graphView.layer.addSublayer(shapeLayer)
}
#discardableResult
#objc func createBezierPath(isPathAlreadyCreated: Bool = false) -> UIBezierPath {
for _ in 0..<205 {
let path = UIBezierPath()
path.move(to: CGPoint(x: movingPoint, y: graphView.halfHeight))
path.addLine(to: CGPoint(x: startInterval, y: graphView.halfHeight))
// P Point Curve
let pWaveX = startInterval + pWaveWidth
path.addQuadCurve(to: CGPoint(x: pWaveX, y: graphView.halfHeight), controlPoint: CGPoint(x: pWaveX - 6.0, y: graphView.halfHeight - 30.0))
let gapBetweenPAndQRS: CGFloat = 20.0
path.addLine(to: CGPoint(x: (pWaveX + gapBetweenPAndQRS), y: graphView.halfHeight))
// QRS
let qrsStartPoint = pWaveX + gapBetweenPAndQRS
let qrsComplex = qrsStartPoint + qrsWaveWidth
let qrsComplexEndPoint = qrsComplex + (qrsWaveWidth / 2)
path.addLine(to: CGPoint(x: qrsStartPoint + (qrsWaveWidth / 2), y: graphView.halfHeight - 70.0))
path.addLine(to: CGPoint(x: qrsComplex, y: graphView.halfHeight + 40.0))
path.addLine(to: CGPoint(x: qrsComplexEndPoint, y: graphView.halfHeight))
let gapBetweenQRSAndTWave: CGFloat = 20.0
let tWaveStartPoint = qrsComplexEndPoint + gapBetweenQRSAndTWave
path.addLine(to: CGPoint(x: tWaveStartPoint, y: graphView.halfHeight))
// T Point Curve
let tWaveEndX = tWaveStartPoint + tWaveWidth
print(tWaveEndX)
path.addQuadCurve(to: CGPoint(x: tWaveEndX, y: graphView.halfHeight), controlPoint: CGPoint(x: tWaveEndX - 7.0, y: graphView.halfHeight - 30.0))
movingPoint = tWaveEndX
startInterval = tWaveEndX + gapBetweenTwoIntervals
path.addLine(to: CGPoint(x: startInterval, y: graphView.halfHeight))
bezierPath.append(path)
}
shapeLayer.path = bezierPath.cgPath
animation.fromValue = 0.0
animation.duration = 205 * 2
shapeLayer.add(animation, forKey: "PathAnimation")
return bezierPath
}
}
I managed to solve your problem, please check and let me know if it works for you.
NOTE - Instead of server response I have added a button click to add more graph patterns. You can use the server response to do the same by calling the button tap code from server response success method.
import UIKit
class GraphViewController: UIViewController {
#IBOutlet weak var graphView: UIView!
// MARK:- PROPERTIES
var movingPoint: CGFloat = 0.0
var startInterval: CGFloat = 10.0
var noOfIntervals: CGFloat = 0
var intervalWidth: CGFloat = 0
var pWaveWidth: CGFloat = 20.0
var qrsWaveWidth: CGFloat = 15
var tWaveWidth: CGFloat = 30
var gapBetweenTwoIntervals: CGFloat = 50
let animation = CABasicAnimation(keyPath: "strokeEnd")
override func viewDidLoad() {
super.viewDidLoad()
graphView.translatesAutoresizingMaskIntoConstraints = true
graphView.backgroundColor = .blue
graphView.frame.size = CGSize(width: 100000, height: 250)
DispatchQueue.main.async {
self.graphView.layoutIfNeeded()
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
createBezierPath(repeatLoop: 1)
}
#objc func createBezierPath(repeatLoop: Int) {
let pathsArray = UIBezierPath()
let shapeLayer = CAShapeLayer()
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 2.0
for _ in 0..<repeatLoop {
let path = UIBezierPath()
path.move(to: CGPoint(x: movingPoint, y: graphView.halfHeight))
path.addLine(to: CGPoint(x: startInterval, y: graphView.halfHeight))
// P Point Curve
let pWaveX = startInterval + pWaveWidth
path.addQuadCurve(to: CGPoint(x: pWaveX, y: graphView.halfHeight), controlPoint: CGPoint(x: pWaveX - 6.0, y: graphView.halfHeight - 30.0))
let gapBetweenPAndQRS: CGFloat = 20.0
path.addLine(to: CGPoint(x: (pWaveX + gapBetweenPAndQRS), y: graphView.halfHeight))
// QRS
let qrsStartPoint = pWaveX + gapBetweenPAndQRS
let qrsComplex = qrsStartPoint + qrsWaveWidth
let qrsComplexEndPoint = qrsComplex + (qrsWaveWidth / 2)
path.addLine(to: CGPoint(x: qrsStartPoint + (qrsWaveWidth / 2), y: graphView.halfHeight - 70.0))
path.addLine(to: CGPoint(x: qrsComplex, y: graphView.halfHeight + 40.0))
path.addLine(to: CGPoint(x: qrsComplexEndPoint, y: graphView.halfHeight))
let gapBetweenQRSAndTWave: CGFloat = 20.0
let tWaveStartPoint = qrsComplexEndPoint + gapBetweenQRSAndTWave
path.addLine(to: CGPoint(x: tWaveStartPoint, y: graphView.halfHeight))
// T Point Curve
let tWaveEndX = tWaveStartPoint + tWaveWidth
print(tWaveEndX)
path.addQuadCurve(to: CGPoint(x: tWaveEndX, y: graphView.halfHeight), controlPoint: CGPoint(x: tWaveEndX - 7.0, y: graphView.halfHeight - 30.0))
movingPoint = tWaveEndX
startInterval = tWaveEndX + gapBetweenTwoIntervals
path.addLine(to: CGPoint(x: startInterval, y: graphView.halfHeight))
pathsArray.append(path)
shapeLayer.path = pathsArray.cgPath
animation.fromValue = 0.0
animation.duration = Double(repeatLoop) * 2
shapeLayer.add(animation, forKey: "PathAnimation")
}
graphView.layer.addSublayer(shapeLayer)
}
#IBAction func btnTap(_ sender: Any) {
createBezierPath(repeatLoop: 2)
}
}
extension UIView {
var halfHeight: CGFloat {
return self.frame.size.height / 2.0
}
}
Happy to Help :)

Dashed horizontal line using IBDesignable

So i came across this question, and i want to achieve this to draw a horizontal line with the same approach using #IBDesignable.
I have tried to play around inside the class, but no result.
#IBDesignable class DottedVertical: UIView {
#IBInspectable var dotColor: UIColor = UIColor.etc
#IBInspectable var lowerHalfOnly: Bool = false
override func draw(_ rect: CGRect) {
// say you want 8 dots, with perfect fenceposting:
let totalCount = 8 + 8 - 1
let fullHeight = bounds.size.height
let width = bounds.size.width
let itemLength = fullHeight / CGFloat(totalCount)
let path = UIBezierPath()
let beginFromTop = CGFloat(0.0)
let top = CGPoint(x: width/2, y: beginFromTop)
let bottom = CGPoint(x: width/2, y: fullHeight)
path.move(to: top)
path.addLine(to: bottom)
path.lineWidth = width
let dashes: [CGFloat] = [itemLength, itemLength]
path.setLineDash(dashes, count: dashes.count, phase: 0)
// for ROUNDED dots, simply change to....
//let dashes: [CGFloat] = [0.0, itemLength * 2.0]
//path.lineCapStyle = CGLineCap.round
dotColor.setStroke()
path.stroke()
}
}
You can achieve as below,
#IBDesignable class DottedHorizontal: UIView {
#IBInspectable var dotColor: UIColor = UIColor.red
#IBInspectable var lowerHalfOnly: Bool = false
override func draw(_ rect: CGRect) {
let fullHeight = bounds.size.height
let width = bounds.size.width
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: fullHeight/2))
path.addLine(to: CGPoint(x: width, y: fullHeight/2))
path.lineWidth = 5
let dashes: [CGFloat] = [4, 2]
path.setLineDash(dashes, count: dashes.count, phase: 0)
dotColor.setStroke()
path.stroke()
}
}

Corner radius image Swift

I'm trying to make this corner radius image...it's not exactly the same shape of the image..any easy answer instead of trying random numbers of width and height ?
thanks alot
let rectShape = CAShapeLayer()
rectShape.bounds = self.mainImg.frame
rectShape.position = self.mainImg.center
rectShape.path = UIBezierPath(roundedRect: self.mainImg.bounds, byRoundingCorners: [.bottomLeft , .bottomRight ], cornerRadii: CGSize(width: 50, height: 4)).cgPath
You can use QuadCurve to get the design you want.
Here is a Swift #IBDesignable class that lets you specify the image and the "height" of the rounding in Storyboard / Interface Builder:
#IBDesignable
class RoundedBottomImageView: UIView {
var imageView: UIImageView!
#IBInspectable var image: UIImage? {
didSet { self.imageView.image = image }
}
#IBInspectable var roundingValue: CGFloat = 0.0 {
didSet {
self.setNeedsLayout()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
doMyInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
doMyInit()
}
func doMyInit() {
imageView = UIImageView()
imageView.backgroundColor = UIColor.red
imageView.contentMode = UIViewContentMode.scaleAspectFill
addSubview(imageView)
}
override func layoutSubviews() {
super.layoutSubviews()
imageView.frame = self.bounds
let rect = self.bounds
let y:CGFloat = rect.size.height - roundingValue
let curveTo:CGFloat = rect.size.height + roundingValue
let myBezier = UIBezierPath()
myBezier.move(to: CGPoint(x: 0, y: y))
myBezier.addQuadCurve(to: CGPoint(x: rect.width, y: y), controlPoint: CGPoint(x: rect.width / 2, y: curveTo))
myBezier.addLine(to: CGPoint(x: rect.width, y: 0))
myBezier.addLine(to: CGPoint(x: 0, y: 0))
myBezier.close()
let maskForPath = CAShapeLayer()
maskForPath.path = myBezier.cgPath
layer.mask = maskForPath
}
}
Result with 300 x 200 image view, rounding set to 40:
Edit - (3.5 years later)...
To answer #MiteshDobareeya comment, we can switch the rounded edge from Bottom to Top by transforming the bezier path:
let c = CGAffineTransform(scaleX: 1, y: -1).concatenating(CGAffineTransform(translationX: 0, y: bounds.size.height))
myBezier.apply(c)
It's been quite a while since this answer was originally posted, so a few changes:
subclass UIImageView directly - no need to make it a UIView with an embedded UIImageView
add a Bool roundTop var
if set to False (the default), we round the Bottom
if set to True, we round the Top
re-order and "name" our path points for clarity
So, the basic principle:
We create a UIBezierPath and:
move to pt1
add a line to pt2
add a line to pt3
add a quad-curve to pt4 with controlPoint
close the path
use that path for a CAShapeLayer mask
the result:
If we want to round the Top, after closing the path we can apply apply a scale transform using -1 as the y value to vertically mirror it. Because that transform mirror it at "y-zero" we also apply a translate transform to move it back down into place.
That gives us:
Here's the updated class:
#IBDesignable
class RoundedTopBottomImageView: UIImageView {
#IBInspectable var roundingValue: CGFloat = 0.0 {
didSet {
self.setNeedsLayout()
}
}
#IBInspectable var roundTop: Bool = false {
didSet {
self.setNeedsLayout()
}
}
override func layoutSubviews() {
super.layoutSubviews()
let r = bounds
let myBezier = UIBezierPath()
let pt1: CGPoint = CGPoint(x: r.minX, y: r.minY)
let pt2: CGPoint = CGPoint(x: r.maxX, y: r.minY)
let pt3: CGPoint = CGPoint(x: r.maxX, y: r.maxY - roundingValue)
let pt4: CGPoint = CGPoint(x: r.minX, y: r.maxY - roundingValue)
let controlPoint: CGPoint = CGPoint(x: r.midX, y: r.maxY + roundingValue)
myBezier.move(to: pt1)
myBezier.addLine(to: pt2)
myBezier.addLine(to: pt3)
myBezier.addQuadCurve(to: pt4, controlPoint: controlPoint)
myBezier.close()
if roundTop {
// if we want to round the Top instead of the bottom,
// flip the path vertically
let c = CGAffineTransform(scaleX: 1, y: -1) //.concatenating(CGAffineTransform(translationX: 0, y: bounds.size.height))
myBezier.apply(c)
}
let maskForPath = CAShapeLayer()
maskForPath.path = myBezier.cgPath
layer.mask = maskForPath
}
}
You can try with UIView extension. as
extension UIView {
func setBottomCurve(){
let offset = CGFloat(self.frame.size.height + self.frame.size.height/1.8)
let bounds = self.bounds
let rectBounds = CGRect(x: bounds.origin.x,
y: bounds.origin.y ,
width: bounds.size.width,
height: bounds.size.height / 2)
let rectPath = UIBezierPath(rect: rectBounds)
let ovalBounds = CGRect(x: bounds.origin.x - offset / 2,
y: bounds.origin.y ,
width: bounds.size.width + offset,
height: bounds.size.height)
let ovalPath = UIBezierPath(ovalIn: ovalBounds)
rectPath.append(ovalPath)
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = rectPath.cgPath
self.layer.mask = maskLayer
}
}
& use it in viewWillAppear like methods where you can get actual frame of UIImageView.
Usage:
override func viewWillAppear(_ animated: Bool) {
//use it in viewWillAppear like methods where you can get actual frame of UIImageView
myImageView.setBottomCurve()
}

Swift: rainbow colour circle

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

How to make a dashed line in swift?

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];

Resources