How to create dotted step indicator in swift 5(iOS) - ios

I have a requirement like to draw a dotted step indicator in swift 5. I have searched for lot of tutorials on the same but I couldn't get the proper result. I need the step indicator which should be in vertical position(Top to Bottom). I have used the following code but it is coming wrongly when I run the app in iPad.
private func drawLinePath() {
//let linePath = UIBezierPath()
let path = UIBezierPath()
let centerX = self.frame.width / 2.0
let lineHeight = self.frame.height / 10
path.move(to: CGPoint(x: centerX, y: 0))
path.addLine(to: CGPoint(x: centerX, y: lineHeight))
path.move(to: CGPoint(x: centerX, y:lineHeight + 3))
path.addLine(to: CGPoint(x: centerX, y: lineHeight + 5))
path.move(to: CGPoint(x: centerX, y:lineHeight + 8))
path.addLine(to: CGPoint(x: centerX, y: lineHeight + 10))
path.move(to: CGPoint(x: centerX, y:lineHeight + 12))
path.addLine(to: CGPoint(x: centerX, y: lineHeight + 15))
path.move(to: CGPoint(x: centerX, y:lineHeight + 18))
path.addLine(to: CGPoint(x: centerX, y: lineHeight + 21 ))
path.move(to: CGPoint(x: centerX, y:lineHeight + 23))
path.addLine(to: CGPoint(x: centerX, y: lineHeight + 26))
path.move(to: CGPoint(x: centerX, y:lineHeight + 28))
path.addLine(to: CGPoint(x: centerX, y: lineHeight + 31))
path.move(to: CGPoint(x: centerX, y:lineHeight + 33))
path.addLine(to: CGPoint(x: centerX, y: lineHeight + 36))
path.move(to: CGPoint(x: centerX, y:lineHeight + 38))
path.addLine(to: CGPoint(x: centerX, y: lineHeight + 41))
self.path = path.cgPath
}

here is code working perfectly.
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.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = lineColor.cgColor
shapeLayer.lineWidth = 4.0
shapeLayer.lineCap = .round
shapeLayer.lineDashPattern = [0.001,16]
shapeLayer.lineDashPhase = 4
view.layer.addSublayer(shapeLayer)
}
func drawLine() {
let frm1: CGRect = img1.frame
let frm2 : CGRect = img2.frame
drawLineFromPoint(start: CGPoint(x: frm1.origin.x + 12, y: frm1.origin.y + 25), toPoint: CGPoint(x: frm1.origin.x + 12, y: frm2.origin.y), ofColor: UIColor.black, inView: self.view)
}
override func viewDidLoad() {
super.viewDidLoad()
drawLine()
}
here is image

For that dotted path you can take UILabel and set text as '......' with your required font size and color. No need to draw any Bezier path. Just use UILabel.

take UIView for dashed line and use below code for line.
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)
}}
Now use it like below..
let topPoint = CGPoint(x: vw.frame.midX, y: vw.bounds.minY)
let bottomPoint = CGPoint(x: vw.frame.midX, y: vw.bounds.maxY)
vw.createDashedLine(from: topPoint, to: bottomPoint, color: .lightGray, strokeLength: 4, gapLength: 6, width: 2)

You can use StepIndicator pod for that, you can find example from this link
https://github.com/chenyun122/StepIndicator

let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize.init(width: 20, height: 40))
let layer = CAShapeLayer()
let path = UIBezierPath(roundedRect: rect, cornerRadius: 4)
layer.path = path.cgPath
layer.strokeColor = UIColor.gray.cgColor
layer.lineDashPattern = [3,3];
layer.backgroundColor = _CLEAR_COLOR.cgColor;
layer.fillColor = _CLEAR_COLOR.cgColor;
viewDottedLine.layer.addSublayer(layer);
Change rect values as per your view and lineDashPattern.
Hope that will work for you.

Related

Insert CATextLayer inside UIBezierPath and rotate

In the following example I'm trying to insert a CATextLayer inside a UIBezierPath and rotate it -45 degrees.
class DiagonalView: UIView {
override func draw(_ rect: CGRect) {
let path = UIBezierPath()
path.move(to: .init(x: rect.maxX, y: rect.midY + (rect.midY / 2.5)))
path.addLine(to: .init(x: rect.midX + (rect.midX / 2.5), y: rect.maxY))
path.addLine(to: .init(x: rect.midX - (rect.midX / 10.5), y: rect.maxY))
path.addLine(to: .init(x: rect.maxX, y: rect.midY - (rect.midY / 10.5)))
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.fillColor = UIColor.black.cgColor
layer.addSublayer(shapeLayer)
let textlayer = CATextLayer.init()
textlayer.string = "iPhone X"
textlayer.fontSize = 12
textlayer.isWrapped = true
textlayer.foregroundColor = UIColor.white.cgColor
textlayer.frame = path.bounds
let degrees = -45.0
let radians = CGFloat(degrees * Double.pi / 180)
textlayer.transform = CATransform3DMakeTranslation(10, 50, 0)
textlayer.transform = CATransform3DMakeRotation(radians, 0.0, 0.5, 1.0)
layer.addSublayer(textlayer)
}
}
let containerView = DiagonalView(frame: CGRect(x: 0.0, y: 0.0, width: 140.0, height: 140.0))
containerView.backgroundColor = .red
PlaygroundPage.current.liveView = containerView
Although its not actually the result that I expected
Result
Expected
Any idea of what mistake I might have done with the code?
Thanks a lot for your time!

CustomView with squiggle(wavy) top.(Swift)

I am trying to create a custom view a squiggle top and add an image view in the middle.
Something like this:
But I am not so used to UIBezierPath, so I am pretty confused.
This is what I have done so far.
class DemoView: UIView {
var path: UIBezierPath!
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.darkGray
complexShape()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
complexShape()
}
func complexShape() {
path = UIBezierPath()
path.move(to: CGPoint(x: 0.0, y: 0.0))
path.addLine(to: CGPoint(x: self.frame.size.width/2 - 50.0, y: 0.0))
path.addLine(to: CGPoint(x: self.frame.size.width/2, y: 0.0))
path.addCurve(to: CGPoint(x: self.frame.size.width, y: 50.0),
controlPoint1: CGPoint(x: self.frame.size.width + 50.0, y: 25.0),
controlPoint2: CGPoint(x: self.frame.size.width - 150.0, y: 50.0))
path.addLine(to: CGPoint(x: self.frame.size.width, y: self.frame.size.height))
path.addLine(to: CGPoint(x: 0.0, y: self.frame.size.height))
path.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
self.layer.mask = shapeLayer
}
}
extension CGFloat {
func toRadians() -> CGFloat {
return self * .pi / 180.0
}
}
The method below will let you add the background wave effect to another view. All you then need to do for the foreground square is add another view. Play with the constants to change the wave shape/height.
func addWaveBackground(to view: UIView){
let leftDrop:CGFloat = 0.4
let rightDrop: CGFloat = 0.3
let leftInflexionX: CGFloat = 0.4
let leftInflexionY: CGFloat = 0.47
let rightInflexionX: CGFloat = 0.6
let rightInflexionY: CGFloat = 0.22
let backView = UIView(frame: view.frame)
backView.backgroundColor = .gray
view.addSubview(backView)
let backLayer = CAShapeLayer()
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x:0, y: view.frame.height * leftDrop))
path.addCurve(to: CGPoint(x:view.frame.width, y: view.frame.height * rightDrop),
controlPoint1: CGPoint(x: view.frame.width * leftInflexionX, y: view.frame.height * leftInflexionY),
controlPoint2: CGPoint(x: view.frame.width * rightInflexionX, y: view.frame.height * rightInflexionY))
path.addLine(to: CGPoint(x:view.frame.width, y: 0))
path.close()
backLayer.fillColor = UIColor.blue.cgColor
backLayer.path = path.cgPath
backView.layer.addSublayer(backLayer)
}
Pass in the view you want to add the wave effect to (this will usually be the VC's main view).

How to clip a UIView with a bezier path?

I'm not totally sure to understand how UIBezierPath is supposed to work.
I have added a simple UIView in the middle of the screen, and I wanted to clip it by adding a mask to its layer. I tried this, thinking I'd get something like a losange in the middle of the view:
override func viewDidLoad() {
super.viewDidLoad()
viewToClip.backgroundColor = .white
let bezierPath = UIBezierPath()
bezierPath.move(to: viewToClip.center)
bezierPath.addLine(to: CGPoint(x: viewToClip.center.x - 5, y: viewToClip.center.y))
bezierPath.addLine(to: CGPoint(x: viewToClip.center.x, y: viewToClip.center.y - 5))
bezierPath.addLine(to: CGPoint(x: viewToClip.center.x + 5, y: viewToClip.center.y))
bezierPath.addLine(to: CGPoint(x: viewToClip.center.x, y: viewToClip.center.y + 5))
bezierPath.close()
let testLayer = CAShapeLayer()
testLayer.path = bezierPath.cgPath
viewToClip.layer.mask = testLayer
}
But instead of that, the view simply disappears from the screen. What am I doing wrong?
Thanks for your help.
Can you try
import UIKit
class bb: UIView {
var once:Bool = true
override func draw(_ rect: CGRect) {
if(once)
{
once = false
self.backgroundColor = .white
let bezierPath = UIBezierPath()
let cen = CGPoint.init(x: self.bounds.size.width/2, y: self.bounds.size.height/2)
bezierPath.move(to: cen)
bezierPath.addLine(to: CGPoint(x: cen.x - 5, y: cen.y))
bezierPath.addLine(to: CGPoint(x: cen.x, y: cen.y - 5))
bezierPath.addLine(to: CGPoint(x: cen.x + 5, y: cen.y))
bezierPath.addLine(to: CGPoint(x: cen.x, y: cen.y + 5))
bezierPath.close()
let testLayer = CAShapeLayer()
testLayer.path = bezierPath.cgPath
testLayer.lineWidth = 1.0
testLayer.strokeColor = UIColor.blue.cgColor
testLayer.fillColor = UIColor.green.cgColor
self.layer.addSublayer(testLayer)
}
}

UIView with UIBezierPath with reversing path

I am trying to give some mask at bottom view using UIBezierPath with addCurve() method.
Expected Result
Current Output
What I have tried is:
viewHeader.backgroundColor = headerColor
let path = UIBezierPath()
let cpoint1 = CGPoint(x: 80, y: 160)
let cpoint2 = CGPoint(x: 280, y: viewHeader.Height + 50)
path.move(to:.init(x: 0, y: viewHeader.Height + 10))
path.addCurve(to: .init(x: viewHeader.Width, y: 205), controlPoint1: cpoint1, controlPoint2: cpoint2)
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.reversing().cgPath
shapeLayer.fillColor = headerColor?.cgColor
shapeLayer.strokeColor = UIColor.black.cgColor
shapeLayer.lineWidth = 1.0
viewHeader.layer.addSublayer(shapeLayer)
I hope it ll work for you, enjoy.
func drawLine() {
let headerColor = UIColor.red
let path = UIBezierPath()
let cpoint1 = CGPoint(x: viewHeader.frame.width/4, y: 160)
let cpoint2 = CGPoint(x: viewHeader.frame.width*3/4, y: viewHeader.frame.height + 50)
path.move(to:.init(x: 0, y: 0))
path.addLine(to:.init(x: 0, y: viewHeader.frame.height - 50))
path.addCurve(to: .init(x: viewHeader.frame.width, y: viewHeader.frame.height - 50), controlPoint1: cpoint1, controlPoint2: cpoint2)
path.addLine(to: CGPoint(x: viewHeader.frame.width, y: 0))
path.addLine(to: CGPoint(x: 0, y: 0))
path.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.reversing().cgPath
shapeLayer.fillColor = headerColor.cgColor
shapeLayer.strokeColor = headerColor.cgColor
shapeLayer.lineWidth = 1.0
viewHeader.layer.addSublayer(shapeLayer)
}

Different cornerRadius for each corner Swift 3 - iOS

I want to set different corner radius for a view in Swift -3 , I am able to set the radius for the each corner to the same value like the one mentioned in the following post ,how to set cornerRadius for only top-left and top-right corner of a UIView?
Is there a way I can set the corner radius in the following format ?
Radius top-left: 18
Radius top-right: 18
Radius bottom-right: 3
Radius bottom-left: 18
Do you want to add unique corner value for each corner?
Do you want to add a border after that?
I've got a solution will look like this:
First, add a UIBezierPath extension I made:
extension UIBezierPath {
convenience init(shouldRoundRect rect: CGRect, topLeftRadius: CGSize = .zero, topRightRadius: CGSize = .zero, bottomLeftRadius: CGSize = .zero, bottomRightRadius: CGSize = .zero){
self.init()
let path = CGMutablePath()
let topLeft = rect.origin
let topRight = CGPoint(x: rect.maxX, y: rect.minY)
let bottomRight = CGPoint(x: rect.maxX, y: rect.maxY)
let bottomLeft = CGPoint(x: rect.minX, y: rect.maxY)
if topLeftRadius != .zero{
path.move(to: CGPoint(x: topLeft.x+topLeftRadius.width, y: topLeft.y))
} else {
path.move(to: CGPoint(x: topLeft.x, y: topLeft.y))
}
if topRightRadius != .zero{
path.addLine(to: CGPoint(x: topRight.x-topRightRadius.width, y: topRight.y))
path.addCurve(to: CGPoint(x: topRight.x, y: topRight.y+topRightRadius.height), control1: CGPoint(x: topRight.x, y: topRight.y), control2:CGPoint(x: topRight.x, y: topRight.y+topRightRadius.height))
} else {
path.addLine(to: CGPoint(x: topRight.x, y: topRight.y))
}
if bottomRightRadius != .zero{
path.addLine(to: CGPoint(x: bottomRight.x, y: bottomRight.y-bottomRightRadius.height))
path.addCurve(to: CGPoint(x: bottomRight.x-bottomRightRadius.width, y: bottomRight.y), control1: CGPoint(x: bottomRight.x, y: bottomRight.y), control2: CGPoint(x: bottomRight.x-bottomRightRadius.width, y: bottomRight.y))
} else {
path.addLine(to: CGPoint(x: bottomRight.x, y: bottomRight.y))
}
if bottomLeftRadius != .zero{
path.addLine(to: CGPoint(x: bottomLeft.x+bottomLeftRadius.width, y: bottomLeft.y))
path.addCurve(to: CGPoint(x: bottomLeft.x, y: bottomLeft.y-bottomLeftRadius.height), control1: CGPoint(x: bottomLeft.x, y: bottomLeft.y), control2: CGPoint(x: bottomLeft.x, y: bottomLeft.y-bottomLeftRadius.height))
} else {
path.addLine(to: CGPoint(x: bottomLeft.x, y: bottomLeft.y))
}
if topLeftRadius != .zero{
path.addLine(to: CGPoint(x: topLeft.x, y: topLeft.y+topLeftRadius.height))
path.addCurve(to: CGPoint(x: topLeft.x+topLeftRadius.width, y: topLeft.y) , control1: CGPoint(x: topLeft.x, y: topLeft.y) , control2: CGPoint(x: topLeft.x+topLeftRadius.width, y: topLeft.y))
} else {
path.addLine(to: CGPoint(x: topLeft.x, y: topLeft.y))
}
path.closeSubpath()
cgPath = path
}
}
Then, add this UIView extension:
extension UIView{
func roundCorners(topLeft: CGFloat = 0, topRight: CGFloat = 0, bottomLeft: CGFloat = 0, bottomRight: CGFloat = 0) {//(topLeft: CGFloat, topRight: CGFloat, bottomLeft: CGFloat, bottomRight: CGFloat) {
let topLeftRadius = CGSize(width: topLeft, height: topLeft)
let topRightRadius = CGSize(width: topRight, height: topRight)
let bottomLeftRadius = CGSize(width: bottomLeft, height: bottomLeft)
let bottomRightRadius = CGSize(width: bottomRight, height: bottomRight)
let maskPath = UIBezierPath(shouldRoundRect: bounds, topLeftRadius: topLeftRadius, topRightRadius: topRightRadius, bottomLeftRadius: bottomLeftRadius, bottomRightRadius: bottomRightRadius)
let shape = CAShapeLayer()
shape.path = maskPath.cgPath
layer.mask = shape
}
}
Finally, call method
myView.roundCorners(topLeft: 10, topRight: 20, bottomLeft: 30, bottomRight: 40)
And add border. Apparently, layer.borderRadius won't work properly, so create a border using CAShapeLayer and previously created path.
let borderLayer = CAShapeLayer()
borderLayer.path = (myView.layer.mask! as! CAShapeLayer).path! // Reuse the Bezier path
borderLayer.strokeColor = UIColor.red.cgColor
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.lineWidth = 5
borderLayer.frame = myView.bounds
myView.layer.addSublayer(borderLayer)
Voila!
You could set the default layer.cornerRadius to the smallest value and then set the layer mask's border to the bigger value.
let demoView = UIView(frame: CGRect(x: 100, y: 200, width: 100, height: 100))
demoView.backgroundColor = UIColor.red
demoView.layer.cornerRadius = 3.0
let maskPath = UIBezierPath(roundedRect: demoView.bounds,
byRoundingCorners: [.topLeft, .topRight, .bottomLeft],
cornerRadii: CGSize(width: 18.0, height: 0.0))
let maskLayer = CAShapeLayer()
maskLayer.path = maskPath.cgPath
demoView.layer.mask = maskLayer
view.addSubview(demoView)
A slightly improved and simplified answer based on #Kirill Dobryakov's. Curves can leave very small but noticeable irregularities, when you look at it and you know it's not ideally round (try e.g. view side 40 and radius 20). I have no idea how it's even possible, but anyway, the most reliable way is to use arcs which make ideal round corners, and also an #IBDesigneable component for you:
extension UIBezierPath {
convenience init(shouldRoundRect rect: CGRect, topLeftRadius: CGFloat, topRightRadius: CGFloat, bottomLeftRadius: CGFloat, bottomRightRadius: CGFloat){
self.init()
let path = CGMutablePath()
let topLeft = rect.origin
let topRight = CGPoint(x: rect.maxX, y: rect.minY)
let bottomRight = CGPoint(x: rect.maxX, y: rect.maxY)
let bottomLeft = CGPoint(x: rect.minX, y: rect.maxY)
if topLeftRadius != 0 {
path.move(to: CGPoint(x: topLeft.x + topLeftRadius, y: topLeft.y))
} else {
path.move(to: topLeft)
}
if topRightRadius != 0 {
path.addLine(to: CGPoint(x: topRight.x - topRightRadius, y: topRight.y))
path.addArc(tangent1End: topRight, tangent2End: CGPoint(x: topRight.x, y: topRight.y + topRightRadius), radius: topRightRadius)
}
else {
path.addLine(to: topRight)
}
if bottomRightRadius != 0 {
path.addLine(to: CGPoint(x: bottomRight.x, y: bottomRight.y - bottomRightRadius))
path.addArc(tangent1End: bottomRight, tangent2End: CGPoint(x: bottomRight.x - bottomRightRadius, y: bottomRight.y), radius: bottomRightRadius)
}
else {
path.addLine(to: bottomRight)
}
if bottomLeftRadius != 0 {
path.addLine(to: CGPoint(x: bottomLeft.x + bottomLeftRadius, y: bottomLeft.y))
path.addArc(tangent1End: bottomLeft, tangent2End: CGPoint(x: bottomLeft.x, y: bottomLeft.y - bottomLeftRadius), radius: bottomLeftRadius)
}
else {
path.addLine(to: bottomLeft)
}
if topLeftRadius != 0 {
path.addLine(to: CGPoint(x: topLeft.x, y: topLeft.y + topLeftRadius))
path.addArc(tangent1End: topLeft, tangent2End: CGPoint(x: topLeft.x + topLeftRadius, y: topLeft.y), radius: topLeftRadius)
}
else {
path.addLine(to: topLeft)
}
path.closeSubpath()
cgPath = path
}
}
#IBDesignable
open class VariableCornerRadiusView: UIView {
private func applyRadiusMaskFor() {
let path = UIBezierPath(shouldRoundRect: bounds, topLeftRadius: topLeftRadius, topRightRadius: topRightRadius, bottomLeftRadius: bottomLeftRadius, bottomRightRadius: bottomRightRadius)
let shape = CAShapeLayer()
shape.path = path.cgPath
layer.mask = shape
}
#IBInspectable
open var topLeftRadius: CGFloat = 0 {
didSet { setNeedsLayout() }
}
#IBInspectable
open var topRightRadius: CGFloat = 0 {
didSet { setNeedsLayout() }
}
#IBInspectable
open var bottomLeftRadius: CGFloat = 0 {
didSet { setNeedsLayout() }
}
#IBInspectable
open var bottomRightRadius: CGFloat = 0 {
didSet { setNeedsLayout() }
}
override open func layoutSubviews() {
super.layoutSubviews()
applyRadiusMaskFor()
}
}
best way to do this after iOS 11, it looks more smooth in that way.
func roundCorners(_ corners: UIRectCorner, radius: CGFloat) {
clipsToBounds = true
layer.cornerRadius = radius
layer.maskedCorners = CACornerMask(rawValue: corners.rawValue)
}
for original answer: https://stackoverflow.com/a/50289822/4206186

Resources