Reputation: 3269
I want to draw a custom shape similar to the image below.
I achieved this in Android in the following way.
float radiusClear = halfWidth - strokeSize / 2f; // 1
canvas.drawRect(0, 0, width, radiusClear, rootPaint); // 2
canvas.drawCircle(0, radiusClear, radiusClear, clearPaint); // 3
canvas.drawCircle(width, radiusClear, radiusClear, clearPaint); // 4
canvas.drawLine(halfWidth, 0, halfWidth, halfHeight, rootPaint); // 5
canvas.drawLine(halfWidth, halfHeight, halfWidth, height, iconPaint); // 6
canvas.drawCircle(halfWidth, halfHeight, halfWidth, iconPaint); // 7
canvas.drawCircle(halfWidth, halfHeight, thirdWidth, clearPaint); // 8
What would be the equivalent or better approach on swift?
Upvotes: 1
Views: 2264
Reputation: 3269
//
// ConnectorView.swift
//
// Created by harsh vishwakrama on 5/24/18.
//
import UIKit
private let grayColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
private let purpleColor = UIColor(red: 0.387, green: 0.416, blue: 0.718, alpha: 1.000)
@IBDesignable
class ConnectorView: UIView {
var mode: Mode = .end{
didSet{
let width = bounds.width
let height = bounds.height
let halfWidth = bounds.width / 2
let halfHeight = bounds.height / 2
let thirdWidth = bounds.width / 3
let strokeWidth = width / 5
let midPoint = CGPoint(x: bounds.midX, y: bounds.midY)
switch mode {
case .start:
drawStart(width, thirdWidth, halfWidth, halfHeight, midPoint,strokeWidth)
case .node:
drawNode(halfWidth, thirdWidth, halfHeight, midPoint,strokeWidth)
case .end:
drawEnd(halfWidth, thirdWidth, halfHeight, midPoint,strokeWidth)
case .only:
drawOnly(width, thirdWidth, halfWidth, halfHeight, strokeWidth, midPoint)
}
layoutSubviews()
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
clipsToBounds = true
}
enum Mode {
case start, node, end, only
}
}
extension ConnectorView{
fileprivate func drawStart(_ width: CGFloat, _ thirdWidth: CGFloat, _ halfWidth: CGFloat, _ halfHeight: CGFloat, _ midPoint: CGPoint, _ strokeWidth: CGFloat) {
layer.sublayers?.forEach{ layer in
layer.removeFromSuperlayer()
}
let linePathTop = UIBezierPath()
linePathTop.move(to: CGPoint(x: -width, y: -thirdWidth))
linePathTop.addCurve(to: CGPoint(x: halfWidth, y: halfHeight - thirdWidth), controlPoint1: CGPoint(x: halfWidth, y: -thirdWidth ), controlPoint2: CGPoint(x: halfWidth, y: -thirdWidth))
linePathTop.move(to: CGPoint(x: 2 * width, y: -thirdWidth))
linePathTop.addCurve(to: CGPoint(x: halfWidth, y: halfHeight - thirdWidth), controlPoint1: CGPoint(x: halfWidth, y: -thirdWidth ), controlPoint2: CGPoint(x: halfWidth, y: -thirdWidth))
linePathTop.move(to: CGPoint(x: 0, y: -thirdWidth))
linePathTop.addLine(to: CGPoint(x: width, y: -thirdWidth))
linePathTop.move(to: CGPoint(x: halfWidth, y: -thirdWidth))
linePathTop.addLine(to: CGPoint(x: halfWidth, y: halfHeight - thirdWidth))
linePathTop.close()
let shapeLayerTop = CAShapeLayer()
shapeLayerTop.path = linePathTop.cgPath
shapeLayerTop.fillColor = UIColor.clear.cgColor
shapeLayerTop.strokeColor = purpleColor.cgColor
shapeLayerTop.lineWidth = strokeWidth
layer.addSublayer(shapeLayerTop)
let shapeLayerBottom = CAShapeLayer()
let linePathBottom = UIBezierPath()
linePathBottom.move(to: CGPoint(x: halfWidth, y: halfHeight + thirdWidth))
linePathBottom.addLine(to: CGPoint(x: halfWidth, y: bounds.height))
linePathBottom.close()
shapeLayerBottom.path = linePathBottom.cgPath
shapeLayerBottom.strokeColor = grayColor.cgColor
shapeLayerBottom.fillColor = UIColor.clear.cgColor
shapeLayerBottom.lineWidth = strokeWidth
layer.addSublayer(shapeLayerBottom)
let shapeLayerMid = CAShapeLayer()
let circlePath = UIBezierPath(arcCenter: midPoint , radius: thirdWidth, startAngle: 0, endAngle: CGFloat(Double.pi * 2), clockwise: true)
shapeLayerMid.path = circlePath.cgPath
shapeLayerMid.strokeColor = grayColor.cgColor
shapeLayerMid.fillColor = UIColor.clear.cgColor
shapeLayerMid.lineWidth = strokeWidth
layer.addSublayer(shapeLayerMid)
}
fileprivate func drawEnd(_ halfWidth: CGFloat, _ thirdWidth: CGFloat, _ halfHeight: CGFloat, _ midPoint: CGPoint,_ strokeWidth: CGFloat) {
layer.sublayers?.forEach{ layer in
layer.removeFromSuperlayer()
}
let linePath = UIBezierPath()
linePath.move(to: CGPoint(x: halfWidth, y: -thirdWidth))
linePath.addLine(to: CGPoint(x: halfWidth, y: halfHeight - thirdWidth))
linePath.close()
let shapeLayerLine = CAShapeLayer()
shapeLayerLine.fillColor = UIColor.clear.cgColor
shapeLayerLine.strokeColor = grayColor.cgColor
shapeLayerLine.lineWidth = strokeWidth
shapeLayerLine.path = linePath.cgPath
layer.addSublayer(shapeLayerLine)
let shapeLayerMid = CAShapeLayer()
let circlePath = UIBezierPath(arcCenter: midPoint , radius: thirdWidth, startAngle: 0, endAngle: CGFloat(Double.pi * 2), clockwise: true)
shapeLayerMid.path = circlePath.cgPath
shapeLayerMid.strokeColor = grayColor.cgColor
shapeLayerMid.fillColor = UIColor.clear.cgColor
shapeLayerMid.lineWidth = strokeWidth
layer.addSublayer(shapeLayerMid)
}
fileprivate func drawNode(_ halfWidth: CGFloat, _ thirdWidth: CGFloat, _ halfHeight: CGFloat, _ midPoint: CGPoint,_ strokeWidth: CGFloat) {
layer.sublayers?.forEach{ layer in
layer.removeFromSuperlayer()
}
let linePath = UIBezierPath()
linePath.move(to: CGPoint(x: halfWidth, y: -thirdWidth))
linePath.addLine(to: CGPoint(x: halfWidth, y: halfHeight - thirdWidth))
linePath.move(to: CGPoint(x: halfWidth, y: halfHeight + thirdWidth))
linePath.addLine(to: CGPoint(x: halfWidth, y: bounds.height))
linePath.close()
let shapeLayerLine = CAShapeLayer()
shapeLayerLine.fillColor = UIColor.clear.cgColor
shapeLayerLine.strokeColor = grayColor.cgColor
shapeLayerLine.lineWidth = strokeWidth
shapeLayerLine.path = linePath.cgPath
layer.addSublayer(shapeLayerLine)
let shapeLayerMid = CAShapeLayer()
let circlePath = UIBezierPath(arcCenter: midPoint , radius: thirdWidth, startAngle: 0, endAngle: CGFloat(Double.pi * 2), clockwise: true)
shapeLayerMid.path = circlePath.cgPath
shapeLayerMid.strokeColor = grayColor.cgColor
shapeLayerMid.fillColor = UIColor.clear.cgColor
shapeLayerMid.lineWidth = strokeWidth
layer.addSublayer(shapeLayerMid)
}
fileprivate func drawOnly(_ width: CGFloat, _ thirdWidth: CGFloat, _ halfWidth: CGFloat, _ halfHeight: CGFloat, _ strokeWidth: CGFloat, _ midPoint: CGPoint) {
layer.sublayers?.forEach{ layer in
layer.removeFromSuperlayer()
}
let linePathTop = UIBezierPath()
linePathTop.move(to: CGPoint(x: -width, y: -thirdWidth))
linePathTop.addCurve(to: CGPoint(x: halfWidth, y: halfHeight - thirdWidth), controlPoint1: CGPoint(x: halfWidth, y: -thirdWidth ), controlPoint2: CGPoint(x: halfWidth, y: -thirdWidth))
linePathTop.move(to: CGPoint(x: 2 * width, y: -thirdWidth))
linePathTop.addCurve(to: CGPoint(x: halfWidth, y: halfHeight - thirdWidth), controlPoint1: CGPoint(x: halfWidth, y: -thirdWidth ), controlPoint2: CGPoint(x: halfWidth, y: -thirdWidth))
linePathTop.move(to: CGPoint(x: 0, y: -thirdWidth))
linePathTop.addLine(to: CGPoint(x: width, y: -thirdWidth))
linePathTop.move(to: CGPoint(x: halfWidth, y: -thirdWidth))
linePathTop.addLine(to: CGPoint(x: halfWidth, y: halfHeight - thirdWidth))
linePathTop.close()
let shapeLayerTop = CAShapeLayer()
shapeLayerTop.path = linePathTop.cgPath
shapeLayerTop.fillColor = UIColor.clear.cgColor
shapeLayerTop.strokeColor = purpleColor.cgColor
shapeLayerTop.lineWidth = strokeWidth
layer.addSublayer(shapeLayerTop)
let shapeLayerMid = CAShapeLayer()
let circlePath = UIBezierPath(arcCenter: midPoint , radius: thirdWidth, startAngle: 0, endAngle: CGFloat(Double.pi * 2), clockwise: true)
shapeLayerMid.path = circlePath.cgPath
shapeLayerMid.strokeColor = grayColor.cgColor
shapeLayerMid.fillColor = UIColor.clear.cgColor
shapeLayerMid.lineWidth = strokeWidth
layer.addSublayer(shapeLayerMid)
}
}
This is the first solution I was able to come up with. May need some tweaks but this works for me. I need 3 stages of a UI to place in UITableViewCell. One for the first cell, one for the last cell and other for the remaining cells.
The result is like this
Upvotes: 3
Reputation: 1270
I made quick code according to your requirement. I hope it may help you.
//// Color Declarations
let color = UIColor(red: 0.387, green: 0.416, blue: 0.718, alpha: 1.000)
let color2 = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)
let color3 = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 1.000)
let color4 = UIColor(red: 0.300, green: 0.586, blue: 0.712, alpha: 1.000)
//// Oval Drawing
let ovalPath = UIBezierPath(ovalIn: CGRect(x: 41, y: 39, width: 20, height: 20))
color.setStroke()
ovalPath.lineWidth = 2.5
ovalPath.stroke()
//// Rectangle 2 Drawing
let rectangle2Path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 100, height: 17))
color4.setFill()
rectangle2Path.fill()
//// Bezier 2 Drawing
let bezier2Path = UIBezierPath()
bezier2Path.move(to: CGPoint(x: -6.5, y: 18.5))
bezier2Path.addCurve(to: CGPoint(x: 41.76, y: 18.5), controlPoint1: CGPoint(x: 40.61, y: 18.5), controlPoint2: CGPoint(x: 41.76, y: 18.5))
bezier2Path.addCurve(to: CGPoint(x: 47.5, y: 22.7), controlPoint1: CGPoint(x: 41.76, y: 18.5), controlPoint2: CGPoint(x: 47.5, y: 18.5))
bezier2Path.addCurve(to: CGPoint(x: 47.5, y: 39.5), controlPoint1: CGPoint(x: 47.5, y: 26.9), controlPoint2: CGPoint(x: 47.5, y: 39.5))
color3.setStroke()
bezier2Path.lineWidth = 1
bezier2Path.stroke()
//// Bezier 3 Drawing
let bezier3Path = UIBezierPath()
bezier3Path.move(to: CGPoint(x: 100.5, y: 17.5))
bezier3Path.addCurve(to: CGPoint(x: 58.5, y: 17.5), controlPoint1: CGPoint(x: 59.5, y: 17.5), controlPoint2: CGPoint(x: 58.5, y: 17.5))
bezier3Path.addCurve(to: CGPoint(x: 55.5, y: 21.5), controlPoint1: CGPoint(x: 58.5, y: 17.5), controlPoint2: CGPoint(x: 55.5, y: 18.5))
bezier3Path.addCurve(to: CGPoint(x: 55.5, y: 39.5), controlPoint1: CGPoint(x: 55.5, y: 24.5), controlPoint2: CGPoint(x: 55.5, y: 39.5))
color2.setStroke()
bezier3Path.lineWidth = 1
bezier3Path.stroke()
//// Rectangle Drawing
let rectanglePath = UIBezierPath(rect: CGRect(x: 47, y: 59, width: 8, height: 41))
color.setFill()
rectanglePath.fill()
Upvotes: 1
Reputation: 1165
You can user CAShape layer and UIBezierPath to do this if its not customeview // if its custom view its easier just create the path in draw and set the color below are some methods you will need to create the path and other properties like setting color etc. In case it's not the custom view you can use CAShapelayer with path to achieve the same.
//create your path
let xpos: CGFloat = yourXpos // do your calculation and set the x and y
let ypos:cfFloat = yourYPos
let path = UIBezierPath() // UIBezierPath is like a pan you draw line arch circle, like pen you can move from one position to another, if you want to close(connected starting and end point) you just call close()
path.move(to: CGPoint(x: xpos, y: yPos)) //move is like the
path.addLine(to: CGPoint(x: xpos , y: yPos + 25))
path.addArc(withCenter: CGPoint(x: xpos + 1, y: yPos + 25), radius: 2, startAngle: 0, endAngle: CGFloat(Double.pi * 2), clockwise: true)
path.close()
path.move()//move to some new place
You have to calculate x,y like you already done for Android and just set the correct x and y values
//create shape layer and set the path you just created to the shapelayer and shapelayer to your view
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
self.yourView.layer.addSublayer(shapeLayer)
shapeLayer.lineWidth = 0.5 // setting the stoke width// thinkness of drawing
you can set the stroke colour or can fill it
shapeLayer.fillColor = UIColor.black.cgColor
shapeLayer.strokeColor = UIColor.green.cgColor
PS: I Haven't done the actual calculation but you have already done, so maybe with above help you can easly replicate for iOS.
Upvotes: 0