How to draw a directional arrow head - ios

I have lines in many directions and angles, I draw them using UIBezierpath.
I need to draw an arrow on one of the ends of the line; dynamically depending on given point.
Edit:
Edit 2: with Jake answer here is my code
let y2 = line.point2Y
let path = UIBezierPath()
path.move(to: CGPoint(x: x1, y: y1))
path.addLine(to: CGPoint(x: x2, y: y2))
path.addArrow(start: CGPoint(x: x1, y: y1), end: CGPoint(x: x2, y: y2), pointerLineLength: ...
path.close()
let shape = CAShapeLayer()
let shapeBorder = CAShapeLayer()
shapeBorder.strokeColor = UIColor.black.cgColor
shapeBorder.lineJoin = kCALineJoinRound
shapeBorder.lineCap = kCALineCapRound
shapeBorder.lineWidth = 10
shapeBorder.addSublayer(shape)
shape.path = path.cgPath
shapeBorder.path = shape.path
shape.lineJoin = kCALineJoinRound
shape.lineCap = kCALineCapRound
shape.lineWidth = shapeBorder.lineWidth-5.0
shape.strokeColor = color
But there is a shadow

This works for me:
extension UIBezierPath {
func addArrow(start: CGPoint, end: CGPoint, pointerLineLength: CGFloat, arrowAngle: CGFloat) {
self.move(to: start)
self.addLine(to: end)
let startEndAngle = atan((end.y - start.y) / (end.x - start.x)) + ((end.x - start.x) < 0 ? CGFloat(Double.pi) : 0)
let arrowLine1 = CGPoint(x: end.x + pointerLineLength * cos(CGFloat(Double.pi) - startEndAngle + arrowAngle), y: end.y - pointerLineLength * sin(CGFloat(Double.pi) - startEndAngle + arrowAngle))
let arrowLine2 = CGPoint(x: end.x + pointerLineLength * cos(CGFloat(Double.pi) - startEndAngle - arrowAngle), y: end.y - pointerLineLength * sin(CGFloat(Double.pi) - startEndAngle - arrowAngle))
self.addLine(to: arrowLine1)
self.move(to: end)
self.addLine(to: arrowLine2)
}
}
class MyViewController : UIViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let arrow = UIBezierPath()
arrow.addArrow(start: CGPoint(x: 200, y: 200), end: CGPoint(x: 50, y: 50), pointerLineLength: 30, arrowAngle: CGFloat(Double.pi / 4))
let arrowLayer = CAShapeLayer()
arrowLayer.strokeColor = UIColor.black.cgColor
arrowLayer.lineWidth = 3
arrowLayer.path = arrow.cgPath
arrowLayer.fillColor = UIColor.clear.cgColor
arrowLayer.lineJoin = kCALineJoinRound
arrowLayer.lineCap = kCALineCapRound
self.view.layer.addSublayer(arrowLayer)
}
}
EDIT
Also, make sure you set your the fillColor of your CAShapeLayer to UIColor.clear.cgColor. Otherwise, your layer will fill in the area between the start point of the arrow and one of the lines at the end.

Great example, thanks for posting #Jake. Small update, with Xcode 12 it should be:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let arrow = UIBezierPath()
arrow.addArrow(start: CGPoint(x: 200, y: 200), end: CGPoint(x: 50, y: 50), pointerLineLength: 30, arrowAngle: CGFloat(Double.pi / 4))
let arrowLayer = CAShapeLayer()
arrowLayer.strokeColor = UIColor.black.cgColor
arrowLayer.lineWidth = 3
arrowLayer.path = arrow.cgPath
arrowLayer.fillColor = UIColor.clear.cgColor
arrowLayer.lineJoin = CAShapeLayerLineJoin.round
arrowLayer.lineCap = CAShapeLayerLineCap.round
self.view.layer.addSublayer(arrowLayer)
}

Related

How to draw a line at the corner of a square with CAShapeLayer in Swift

I am developing a face recognition application of Vision library, and I am having trouble drawing lines with CAShapeLayer
here is the code after getting camera output:
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
return
}
let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: imageBuffer, orientation: .leftMirrored, options: [:])
let faceDetectionRequest = VNDetectFaceLandmarksRequest(completionHandler: { (request: VNRequest, error: Error?) in
DispatchQueue.main.async {
self.faceLayers.forEach({ drawing in drawing.removeFromSuperlayer() })
if let observations = request.results as? [VNFaceObservation] {
for observation in observations {
let faceRectConverted = self.videoPreviewLayer.layerRectConverted(fromMetadataOutputRect: observation.boundingBox)
let faceRectanglePath = CGPath(rect: faceRectConverted, transform: nil)
let faceLayer = CAShapeLayer()
faceLayer.path = faceRectanglePath
faceLayer.fillColor = UIColor.clear.cgColor
faceLayer.strokeColor = UIColor.systemPink.cgColor
self.faceLayers.append(faceLayer)
self.cameraView.layer.addSublayer(faceLayer)
}
}
}
})
do {
try imageRequestHandler.perform([faceDetectionRequest])
} catch {
print(error.localizedDescription)
}
}
result
the problem I am facing when I want to draw a short line at the corner of the square
Thanks for all the support!
You can use this:
let thinLayer = CAShapeLayer()
thinLayer.path = CGPath(rect: rect, transform: nil)
thinLayer.strokeColor = UIColor.purple.cgColor
thinLayer.fillColor = UIColor.clear.cgColor
thinLayer.lineWidth = 1.0
view.layer.addSublayer(thinLayer)
let cornerWidth: CGFloat = 20.0
let topLeftBezierPath = UIBezierPath()
topLeftBezierPath.move(to: CGPoint(x: rect.origin.x, y: rect.origin.y + cornerWidth))
topLeftBezierPath.addLine(to: CGPoint(x: rect.origin.x, y: rect.origin.y))
topLeftBezierPath.addLine(to: CGPoint(x: rect.origin.x + cornerWidth, y: rect.origin.y))
let topRightBezierPath = UIBezierPath()
topRightBezierPath.move(to: CGPoint(x: rect.maxX, y: rect.origin.y + cornerWidth))
topRightBezierPath.addLine(to: CGPoint(x: rect.maxX, y: rect.origin.y))
topRightBezierPath.addLine(to: CGPoint(x: rect.maxX - cornerWidth, y: rect.origin.y))
let bottomRightBezierPath = UIBezierPath()
bottomRightBezierPath.move(to: CGPoint(x: rect.maxX, y: rect.maxY - cornerWidth))
bottomRightBezierPath.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
bottomRightBezierPath.addLine(to: CGPoint(x: rect.maxX - cornerWidth, y: rect.maxY))
let bottomLeftBezierPath = UIBezierPath()
bottomLeftBezierPath.move(to: CGPoint(x: rect.origin.x, y: rect.maxY - cornerWidth))
bottomLeftBezierPath.addLine(to: CGPoint(x: rect.origin.x, y: rect.maxY))
bottomLeftBezierPath.addLine(to: CGPoint(x: rect.origin.x + cornerWidth, y: rect.maxY))
func cornerLayer(with bezierPath: UIBezierPath) -> CAShapeLayer {
let shape = CAShapeLayer()
shape.path = bezierPath.cgPath
shape.lineWidth = 4.0
shape.strokeColor = UIColor.purple.cgColor
shape.fillColor = UIColor.clear.cgColor
shape.lineCap = .round
return shape
}
let topLeftShape = cornerLayer(with: topLeftBezierPath)
view.layer.addSublayer(topLeftShape)
let topRightShape = cornerLayer(with: topRightBezierPath)
view.layer.addSublayer(topRightShape)
let bottomRightShape = cornerLayer(with: bottomRightBezierPath)
view.layer.addSublayer(bottomRightShape)
let bottomLeftShape = cornerLayer(with: bottomLeftBezierPath)
view.layer.addSublayer(bottomLeftShape)
Where:
view is the UIView on which to add the layer, in your case it's cameraView.
rect is the full rect of the face, in your case it's faceRectConverted
customize to fulfill your needs (lineWidh, strokeColor, cornerWidth which might me proportional to the size of the rect?)
Sample in Playground:
func drawing() -> UIView {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
view.backgroundColor = .orange
let rect = CGRect(x: 50, y: 50, width: 200, height: 200)
let thinLayer = CAShapeLayer()
thinLayer.path = CGPath(rect: rect, transform: nil)
thinLayer.strokeColor = UIColor.purple.cgColor
thinLayer.fillColor = UIColor.clear.cgColor
thinLayer.lineWidth = 1.0
view.layer.addSublayer(thinLayer)
let cornerWidth: CGFloat = 20.0
let topLeftBezierPath = UIBezierPath()
topLeftBezierPath.move(to: CGPoint(x: rect.origin.x, y: rect.origin.y + cornerWidth))
topLeftBezierPath.addLine(to: CGPoint(x: rect.origin.x, y: rect.origin.y))
topLeftBezierPath.addLine(to: CGPoint(x: rect.origin.x + cornerWidth, y: rect.origin.y))
let topRightBezierPath = UIBezierPath()
topRightBezierPath.move(to: CGPoint(x: rect.maxX, y: rect.origin.y + cornerWidth))
topRightBezierPath.addLine(to: CGPoint(x: rect.maxX, y: rect.origin.y))
topRightBezierPath.addLine(to: CGPoint(x: rect.maxX - cornerWidth, y: rect.origin.y))
let bottomRightBezierPath = UIBezierPath()
bottomRightBezierPath.move(to: CGPoint(x: rect.maxX, y: rect.maxY - cornerWidth))
bottomRightBezierPath.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
bottomRightBezierPath.addLine(to: CGPoint(x: rect.maxX - cornerWidth, y: rect.maxY))
let bottomLeftBezierPath = UIBezierPath()
bottomLeftBezierPath.move(to: CGPoint(x: rect.origin.x, y: rect.maxY - cornerWidth))
bottomLeftBezierPath.addLine(to: CGPoint(x: rect.origin.x, y: rect.maxY))
bottomLeftBezierPath.addLine(to: CGPoint(x: rect.origin.x + cornerWidth, y: rect.maxY))
func cornerLayer(with bezierPath: UIBezierPath) -> CAShapeLayer {
let shape = CAShapeLayer()
shape.path = bezierPath.cgPath
shape.lineWidth = 4.0
shape.strokeColor = UIColor.purple.cgColor
shape.fillColor = UIColor.clear.cgColor
shape.lineCap = .round
return shape
}
let topLeftShape = cornerLayer(with: topLeftBezierPath)
view.layer.addSublayer(topLeftShape)
let topRightShape = cornerLayer(with: topRightBezierPath)
view.layer.addSublayer(topRightShape)
let bottomRightShape = cornerLayer(with: bottomRightBezierPath)
view.layer.addSublayer(bottomRightShape)
let bottomLeftShape = cornerLayer(with: bottomLeftBezierPath)
view.layer.addSublayer(bottomLeftShape)
return view
}
let drawn = drawing()
drawn
Output:

Border only in corners with UIBezierPath() wrong space in lineDashPattern

Im trying to create a view with border only in corners (qrCode style) using lineDashPattern, but the space between the lines doesn't seem to match with the Pattern defined by me. This is my playground:
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let myView = UIView(frame: CGRect(x: 50, y: 50, width: 200, height: 200))
myView.backgroundColor = .black
let borderLayer = CAShapeLayer()
let d = (myView.bounds.width / 2) as NSNumber
borderLayer.strokeColor = UIColor.blue.cgColor
borderLayer.lineDashPattern = [d]
borderLayer.frame = myView.bounds
borderLayer.fillColor = nil
borderLayer.lineWidth = 10
borderLayer.strokeStart = 0.1
//borderLayer.lineDashPhase = -80
borderLayer.path = UIBezierPath(roundedRect: myView.bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 30, height: 30)).cgPath
borderLayer.lineCap = CAShapeLayerLineCap.round
myView.layer.addSublayer(borderLayer)
view.addSubview(myView)
self.view = view
}
I'm trying to adjust the start of the lines with the lineDashPhase and strokeStart attributes, but doesn't seems to work. Can someone help me?
Interesting idea - using long dashes on a rounded rect... But I don't believe you'll get your desired results.
You probably want to draw the 4 corners like this:
Your path would start with:
move to: A
line to: B
arc with center: C.x B.y and clockwise from 9 o'clock to 12 o'clock
line to: D
then update your points, and draw the top-right / bottom-right / bottom-left corners.
Here's an example class:
class BracketView: UIView {
var radius: CGFloat = 30
var lineLength: CGFloat = 30
var lineWidth: CGFloat = 10
var lineColor: UIColor = .blue
var shapeLayer: CAShapeLayer!
override class var layerClass: AnyClass {
return CAShapeLayer.self
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
shapeLayer = self.layer as? CAShapeLayer
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.lineCap = .round
}
override func layoutSubviews() {
super.layoutSubviews()
// set these properties here, so we can change them if desired
shapeLayer.strokeColor = lineColor.cgColor
shapeLayer.lineWidth = lineWidth
let pth = UIBezierPath()
var ptA: CGPoint = .zero
var ptB: CGPoint = .zero
var ptC: CGPoint = .zero
var ptD: CGPoint = .zero
var arcCenter: CGPoint = .zero
var startAngle: CGFloat = 0
// top-left corner
startAngle = .pi // 9 o'clock
ptA.x = bounds.minX
ptA.y = bounds.minY + radius + lineLength
ptB.x = bounds.minX
ptB.y = bounds.minY + radius
ptC.x = bounds.minX + radius
ptC.y = bounds.minY
ptD.x = bounds.minX + radius + lineLength
ptD.y = bounds.minY
arcCenter.x = ptC.x
arcCenter.y = ptB.y
pth.move(to: ptA)
pth.addLine(to: ptB)
pth.addArc(withCenter: arcCenter, radius: radius, startAngle: startAngle, endAngle: startAngle + .pi * 0.5, clockwise: true)
pth.addLine(to: ptD)
// top-right corner
startAngle += (.pi * 0.5) // 12 o'clock
ptA.x = bounds.maxX - (radius + lineLength)
ptA.y = bounds.minY
ptB.x = bounds.maxX - radius
ptB.y = bounds.minY
ptC.x = bounds.maxX
ptC.y = bounds.minY + radius
ptD.x = bounds.maxX
ptD.y = bounds.minY + radius + lineLength
arcCenter.x = ptB.x
arcCenter.y = ptC.y
pth.move(to: ptA)
pth.addLine(to: ptB)
pth.addArc(withCenter: arcCenter, radius: radius, startAngle: startAngle, endAngle: startAngle + .pi * 0.5, clockwise: true)
pth.addLine(to: ptD)
// bottom-right corner
startAngle += (.pi * 0.5) // 3 o'clock
ptA.x = bounds.maxX
ptA.y = bounds.maxY - (radius + lineLength)
ptB.x = bounds.maxX
ptB.y = bounds.maxY - radius
ptC.x = bounds.maxX - radius
ptC.y = bounds.maxY
ptD.x = bounds.maxX - (radius + lineLength)
ptD.y = bounds.maxY
arcCenter.x = ptC.x
arcCenter.y = ptB.y
pth.move(to: ptA)
pth.addLine(to: ptB)
pth.addArc(withCenter: arcCenter, radius: radius, startAngle: startAngle, endAngle: startAngle + .pi * 0.5, clockwise: true)
pth.addLine(to: ptD)
// bottom-left corner
startAngle += (.pi * 0.5) // 6 o'clock
ptA.x = bounds.minX + radius + lineLength
ptA.y = bounds.maxY
ptB.x = bounds.minX + radius
ptB.y = bounds.maxY
ptC.x = bounds.minX
ptC.y = bounds.maxY - radius
ptD.x = bounds.minX
ptD.y = bounds.maxY - (radius + lineLength)
arcCenter.x = ptB.x
arcCenter.y = ptC.y
pth.move(to: ptA)
pth.addLine(to: ptB)
pth.addArc(withCenter: arcCenter, radius: radius, startAngle: startAngle, endAngle: startAngle + .pi * 0.5, clockwise: true)
pth.addLine(to: ptD)
shapeLayer.path = pth.cgPath
}
}
Result with a 200 x 200 frame, green background:

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!

Draw male and female gender symbols in iOS

Does anyone know how to programmatically draw the male and female gender signs in iOS like the ones below?
An example for drawing the female symbol. First, make a Layer using UIBezierPath to draw it:
import UIKit
class FemaleLayer: CAShapeLayer {
override var frame: CGRect {
didSet{
self.draw()
}
}
private func draw() {
self.lineWidth = 20.0
self.fillColor = UIColor.clear.cgColor
self.strokeColor = UIColor.black.cgColor
let path = UIBezierPath()
let sideLength = fmin(self.frame.width, self.frame.height)
let circlesRadius = (sideLength / 2.0 - self.lineWidth) * 0.6
let circleCenterY = self.bounds.midY * 0.8
path.addArc(withCenter: CGPoint(x:self.bounds.midX, y:circleCenterY), radius: circlesRadius, startAngle: 0.0, endAngle: 2 * CGFloat.pi, clockwise: true)
let circleBottomY = circleCenterY + circlesRadius
path.move(to: CGPoint(x: self.bounds.midX, y: circleBottomY))
path.addLine(to: CGPoint(x: self.bounds.midX, y: circleBottomY + circlesRadius))
path.move(to: CGPoint(x: self.bounds.midX * 0.6, y: circleBottomY + circlesRadius * 0.5))
path.addLine(to: CGPoint(x: self.bounds.midX * 1.4, y: circleBottomY + circlesRadius * 0.5))
self.path = path.cgPath
}
}
Then use it by adding the layer to a UIView:
let femaleLayer = FemaleLayer()
femaleLayer.frame = self.view.bounds
self.view.layer.addSublayer(femaleLayer)
The final result:
Updated, for male:
class MaleLayer: CAShapeLayer {
override var frame: CGRect {
didSet{
self.draw()
}
}
private func draw() {
self.lineWidth = 20.0
self.fillColor = UIColor.clear.cgColor
self.strokeColor = UIColor.black.cgColor
let path = UIBezierPath()
let sideLength = fmin(self.frame.width, self.frame.height)
let circlesRadius = (sideLength / 2.0 - self.lineWidth) * 0.6
let circleCenterX = self.bounds.midX * 0.9
let circleCenterY = self.bounds.midY * 1.2
path.addArc(withCenter: CGPoint(x:circleCenterX, y:circleCenterY), radius: circlesRadius, startAngle: 0.0, endAngle: 2 * CGFloat.pi, clockwise: true)
let circleRightTopX = circleCenterX + circlesRadius * 0.686
let circleRightTopY = circleCenterY - circlesRadius * 0.686
let lineLength = circlesRadius * 0.7
path.move(to: CGPoint(x: circleRightTopX, y: circleRightTopY))
path.addLine(to: CGPoint(x: circleRightTopX + lineLength, y: circleRightTopY - lineLength))
path.move(to: CGPoint(x: circleRightTopX, y: circleRightTopY - lineLength))
path.addLine(to: CGPoint(x: circleRightTopX + lineLength + self.lineWidth / 2.0, y: circleRightTopY - lineLength))
path.move(to: CGPoint(x: circleRightTopX + lineLength, y: circleRightTopY - lineLength))
path.addLine(to: CGPoint(x: circleRightTopX + lineLength , y: circleRightTopY))
self.path = path.cgPath
}
}

How to display image in diamond shape in swift?

I want to display images in diamond shape when I am giving width and height 120 and apply the corner radiu. I am getting diamond shape approximately but not getting exact diamond shape so any one suggest me it helpful to me.
self.imageView.layer.cornerRadius = self.imageView.frame.size.width / 2
self.imageView.clipsToBounds = true
If you have an image view and want to crop it to a diamond (rhombus) shape, you should:
Create UIBezierPath in diamond shape;
Use that as the path of a CAShapeLayer;
Set that CAShapeLayer as the mask of the UIImageView's layer
In Swift 3 and later, that might look like:
extension UIView {
func addDiamondMask(cornerRadius: CGFloat = 0) {
let path = UIBezierPath()
path.move(to: CGPoint(x: bounds.midX, y: bounds.minY + cornerRadius))
path.addLine(to: CGPoint(x: bounds.maxX - cornerRadius, y: bounds.midY))
path.addLine(to: CGPoint(x: bounds.midX, y: bounds.maxY - cornerRadius))
path.addLine(to: CGPoint(x: bounds.minX + cornerRadius, y: bounds.midY))
path.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.strokeColor = UIColor.white.cgColor
shapeLayer.lineWidth = cornerRadius * 2
shapeLayer.lineJoin = kCALineJoinRound
shapeLayer.lineCap = kCALineCapRound
layer.mask = shapeLayer
}
}
So, just call addDiamondMask(cornerRadius:) (where the cornerRadius is optional), on your image view.
imageView.addDiamondMask()
That yields:
For Swift 2 rendition, see previous revision of this answer.
An alternate algorithm for rounding of corners might be:
extension UIView {
func addDiamondMask(cornerRadius: CGFloat = 0) {
let path = UIBezierPath()
let points = [
CGPoint(x: bounds.midX, y: bounds.minY),
CGPoint(x: bounds.maxX, y: bounds.midY),
CGPoint(x: bounds.midX, y: bounds.maxY),
CGPoint(x: bounds.minX, y: bounds.midY)
]
path.move(to: point(from: points[0], to: points[1], distance: cornerRadius, fromStart: true))
for i in 0 ..< 4 {
path.addLine(to: point(from: points[i], to: points[(i + 1) % 4], distance: cornerRadius, fromStart: false))
path.addQuadCurve(to: point(from: points[(i + 1) % 4], to: points[(i + 2) % 4], distance: cornerRadius, fromStart: true), controlPoint: points[(i + 1) % 4])
}
path.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.strokeColor = UIColor.clear.cgColor
layer.mask = shapeLayer
}
private func point(from point1: CGPoint, to point2: CGPoint, distance: CGFloat, fromStart: Bool) -> CGPoint {
let start: CGPoint
let end: CGPoint
if fromStart {
start = point1
end = point2
} else {
start = point2
end = point1
}
let angle = atan2(end.y - start.y, end.x - start.x)
return CGPoint(x: start.x + distance * cos(angle), y: start.y + distance * sin(angle))
}
}
Here I'm doing quad bezier in the corners, but I think the effect for rounded corners is slightly better than the above if the diamond is at all elongated.
Anyway, that yields:
SWIFT 5
extension UIView {
func addDiamondMask(cornerRadius: CGFloat = 0) {
let path = UIBezierPath()
path.move(to: CGPoint(x: bounds.midX, y: bounds.minY + cornerRadius))
path.addLine(to: CGPoint(x: bounds.maxX - cornerRadius, y: bounds.midY))
path.addLine(to: CGPoint(x: bounds.midX, y: bounds.maxY - cornerRadius))
path.addLine(to: CGPoint(x: bounds.minX + cornerRadius, y: bounds.midY))
path.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.fillColor = UIColor.white.cgColor
shapeLayer.strokeColor = UIColor.white.cgColor
shapeLayer.lineWidth = cornerRadius * 2
shapeLayer.lineJoin = .round
shapeLayer.lineCap = .round
layer.mask = shapeLayer
}
}

Resources