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
}
}
Related
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:
I'm using CATransform3D and CAShapeLayer to create a layer like below
Here is my code.
let path = CGMutablePath()
let startPoint = CGPoint(x: center.x - width / 2, y: center.y - height / 2)
path.move(to: startPoint)
path.addLine(to: CGPoint(x: startPoint.x + width, y: startPoint.y))
path.addLine(to: CGPoint(x: startPoint.x + width, y: startPoint.y + height))
path.addLine(to: CGPoint(x: startPoint.x, y: startPoint.y + height))
path.closeSubpath()
let backgroundLayer = CAShapeLayer()
backgroundLayer.path = path
backgroundLayer.fillColor = UIColor.clear.cgColor
backgroundLayer.strokeColor = boarderColor.cgColor
var transform = CATransform3DIdentity
transform.m34 = -1 / 500
let angle = 45.toRadians
backgroundLayer.transform = CATransform3DRotate(transform, angle, 1, 0, 0)
The output is like below.
What is the reason for the difference of shape?
The backgroundLayer needs a frame and a position. If these are added, the result is as follows:
Source
Here a slightly modified version of your code that gives the result shown in the screenshot.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let boarderColor = UIColor.red
let height: CGFloat = 400
let width: CGFloat = 250
let center = CGPoint(x: width / 2.0, y: height / 2)
let path = CGMutablePath()
let startPoint = CGPoint(x: center.x - width / 2, y: center.y - height / 2)
path.move(to: startPoint)
path.addLine(to: CGPoint(x: startPoint.x + width, y: startPoint.y))
path.addLine(to: CGPoint(x: startPoint.x + width, y: startPoint.y + height))
path.addLine(to: CGPoint(x: startPoint.x, y: startPoint.y + height))
path.closeSubpath()
let backgroundLayer = CAShapeLayer()
backgroundLayer.path = path
backgroundLayer.fillColor = UIColor.clear.cgColor
backgroundLayer.strokeColor = boarderColor.cgColor
//these two lines are missing
backgroundLayer.frame = CGRect(x: 0, y: 0, width: width, height: height)
backgroundLayer.position = CGPoint(x: self.view.bounds.width / 2.0, y: self.view.bounds.height / 2)
var transform = CATransform3DIdentity
transform.m34 = -1 / 500
let angle = CGFloat(45 * Double.pi / 180.0)
backgroundLayer.transform = CATransform3DRotate(transform, angle, 1, 0, 0)
self.view.layer.addSublayer(backgroundLayer)
}
}
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)
}
I am attempting to create three shapes: a Circle, Square & Triangle. I have created the circle & square, but am unable to create the triangle. My biggest issue is to keep all three shapes in the center of the screen. The circle and square are fine, but when I attempt to make the triangle it does not work. I am also trying to make the triangle look like a "play button" so that the "tip" of the triangle is facing to the right. Here is the code.
func trainglePathWithCenter(center: CGPoint, side: CGFloat) -> UIBezierPath {
let path = UIBezierPath()
let startX = center.x - side / 2
let startY = center.y - side / 2
path.move(to: CGPoint(x: startX, y: startY))
path.addLine(to: CGPoint(x: startX, y: startY - side))
path.addLine(to: CGPoint(x: startX + side, y: startY + side/2))
path.close()
return path
}
func circlePathWithCenter(center: CGPoint, radius: CGFloat) -> UIBezierPath {
let circlePath = UIBezierPath()
circlePath.addArc(withCenter: center, radius: radius, startAngle: -CGFloat(M_PI), endAngle: -CGFloat(M_PI/2), clockwise: true)
circlePath.addArc(withCenter: center, radius: radius, startAngle: -CGFloat(M_PI/2), endAngle: 0, clockwise: true)
circlePath.addArc(withCenter: center, radius: radius, startAngle: 0, endAngle: CGFloat(M_PI/2), clockwise: true)
circlePath.addArc(withCenter: center, radius: radius, startAngle: CGFloat(M_PI/2), endAngle: CGFloat(M_PI), clockwise: true)
circlePath.close()
return circlePath
}
func squarePathWithCenter(center: CGPoint, side: CGFloat) -> UIBezierPath {
let squarePath = UIBezierPath()
let startX = center.x - side / 2
let startY = center.y - side / 2
squarePath.move(to: CGPoint(x: startX, y: startY))
squarePath.addLine(to: squarePath.currentPoint)
squarePath.addLine(to: CGPoint(x: startX + side, y: startY))
squarePath.addLine(to: squarePath.currentPoint)
squarePath.addLine(to: CGPoint(x: startX + side, y: startY + side))
squarePath.addLine(to: squarePath.currentPoint)
squarePath.addLine(to: CGPoint(x: startX, y: startY + side))
squarePath.addLine(to: squarePath.currentPoint)
squarePath.close()
return squarePath
}
Where am I going wrong with the geometry for the triangle?
You subtracted when you need to add. Remember, +Y is down.
Change:
path.addLine(to: CGPoint(x: startX, y: startY - side))
To:
path.addLine(to: CGPoint(x: startX, y: startY + side))
Here it is running in a Playground:
Here's the full code for the Playground demo:
class Custom: UIView {
override func draw(_ rect: CGRect) {
let path = trainglePathWithCenter(center: self.center, side: self.bounds.width / 2)
path.stroke()
}
func trainglePathWithCenter(center: CGPoint, side: CGFloat) -> UIBezierPath {
let path = UIBezierPath()
let startX = center.x - side / 2
let startY = center.y - side / 2
path.move(to: CGPoint(x: startX, y: startY))
path.addLine(to: CGPoint(x: startX, y: startY + side))
path.addLine(to: CGPoint(x: startX + side, y: startY + side/2))
path.close()
return path
}
}
let custom = Custom(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
custom.backgroundColor = .white
Result triangles
Code in swift5
//TriangleView
extension UIView {
func setRightTriangle(targetView:UIView?){
let heightWidth = targetView!.frame.size.width //you can use triangleView.frame.size.height
let path = CGMutablePath()
path.move(to: CGPoint(x: heightWidth/2, y: 0))
path.addLine(to: CGPoint(x:heightWidth, y: heightWidth/2))
path.addLine(to: CGPoint(x:heightWidth/2, y:heightWidth))
path.addLine(to: CGPoint(x:heightWidth/2, y:0))
let shape = CAShapeLayer()
shape.path = path
shape.fillColor = UIColor.blue.cgColor
targetView!.layer.insertSublayer(shape, at: 0)
}
func setLeftTriangle(targetView:UIView?){
let heightWidth = targetView!.frame.size.width
let path = CGMutablePath()
path.move(to: CGPoint(x: heightWidth/2, y: 0))
path.addLine(to: CGPoint(x:0, y: heightWidth/2))
path.addLine(to: CGPoint(x:heightWidth/2, y:heightWidth))
path.addLine(to: CGPoint(x:heightWidth/2, y:0))
let shape = CAShapeLayer()
shape.path = path
shape.fillColor = UIColor.blue.cgColor
targetView!.layer.insertSublayer(shape, at: 0)
}
func setUpTriangle(targetView:UIView?){
let heightWidth = targetView!.frame.size.width
let path = CGMutablePath()
path.move(to: CGPoint(x: 0, y: heightWidth))
path.addLine(to: CGPoint(x:heightWidth/2, y: heightWidth/2))
path.addLine(to: CGPoint(x:heightWidth, y:heightWidth))
path.addLine(to: CGPoint(x:0, y:heightWidth))
let shape = CAShapeLayer()
shape.path = path
shape.fillColor = UIColor.blue.cgColor
targetView!.layer.insertSublayer(shape, at: 0)
}
func setDownTriangle(targetView:UIView?){
let heightWidth = targetView!.frame.size.width
let path = CGMutablePath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x:heightWidth/2, y: heightWidth/2))
path.addLine(to: CGPoint(x:heightWidth, y:0))
path.addLine(to: CGPoint(x:0, y:0))
let shape = CAShapeLayer()
shape.path = path
shape.fillColor = UIColor.blue.cgColor
targetView!.layer.insertSublayer(shape, at: 0)
}
}
I want to rounder my UIView with the value like that
top-left-radius:20; bottom-right-radius:5; bottom-left-radius:5; and top-right-radius:10;
//For rounder `UIRectCornerBottomLeft & UIRectCornerBottomRight` I use
UIBezierPath *maskPath0 = [UIBezierPath bezierPathWithRoundedRect:self.messageView.bounds byRoundingCorners:(UIRectCornerBottomLeft | UIRectCornerBottomRight) cornerRadii:CGSizeMake(5.0, 5.0)];
CAShapeLayer *maskLayer0 = [[CAShapeLayer alloc] init];
maskLayer0.frame = self.bounds;
maskLayer0.path = maskPath0.CGPath;
self.messageView.layer.mask = maskLayer0;
//For rounder `TopRight` I use
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.messageView.bounds byRoundingCorners:(UIRectCornerTopRight) cornerRadii:CGSizeMake(10.0, 10.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
self.messageView.layer.mask = maskLayer;
//For rounder `TopLeft` I use
UIBezierPath *maskPath2 = [UIBezierPath bezierPathWithRoundedRect:self.messageView.bounds byRoundingCorners:(UIRectCornerTopLeft) cornerRadii:CGSizeMake(20.0, 20.0)];
CAShapeLayer *maskLayer2 = [[CAShapeLayer alloc] init];
maskLayer2.frame = self.bounds;
maskLayer2.path = maskPath2.CGPath;
self.messageView.layer.mask = maskLayer2;
But the result I get is the View with corner radius TopLeft with value 20.
How can I achieve this rounder? Any help would be much appreciated.
UIBezierPath has no such method. But you can use bunch of addLineToPoint and addArcWithCenter methods to do it:
let minx = CGRectGetMinX(rect)
let miny = CGRectGetMinY(rect)
let maxx = CGRectGetMaxX(rect)
let maxy = CGRectGetMaxY(rect)
let path = UIBezierPath()
path.moveToPoint(CGPointMake(minx + topLeftRadius, miny))
path.addLineToPoint(CGPointMake(maxx - topRightRadius, miny))
path.addArcWithCenter(CGPointMake(maxx - topRightRadius, miny + topRightRadius), radius: topRightRadius, startAngle:3 * M_PI_2, endAngle: 0, clockwise: true)
path.addLineToPoint(CGPointMake(maxx, maxy - bottomRightRadius))
path.addArcWithCenter(CGPointMake(maxx - bottomRightRadius, maxy - bottomRightRadius), radius: bottomRightRadius, startAngle: 0, endAngle: M_PI_2, clockwise: true)
path.addLineToPoint(CGPointMake(minx + bottomLeftRadius, maxy))
path.addArcWithCenter(CGPointMake(minx + bottomLeftRadius, maxy - bottomLeftRadius), radius: bottomLeftRadius, startAngle: M_PI_2, endAngle: M_PI, clockwise: true)
path.addLineToPoint(CGPointMake(minx, miny + topLeftRadius))
path.addArcWithCenter(CGPointMake(minx + topLeftRadius, miny + topLeftRadius), radius: topLeftRadius, startAngle: M_PI, endAngle: 3 * M_PI_2, clockwise: true)
path.closePath()
For Obj-C:
CGFloat topLeftRadius = 20;
CGFloat topRightRadius = 10;
CGFloat bottomRightRadius = 5;
CGFloat bottomLeftRadius = 5;
CGFloat minx = CGRectGetMinX(self.messageView.bounds);
CGFloat miny = CGRectGetMinY(self.messageView.bounds);
CGFloat maxx = CGRectGetMaxX(self.messageView.bounds);
CGFloat maxy = CGRectGetMaxY(self.messageView.bounds);
UIBezierPath *path = [[UIBezierPath alloc] init];
[path moveToPoint:CGPointMake(minx + topLeftRadius, miny)];
[path addLineToPoint:CGPointMake(maxx - topRightRadius, miny)];
[path addArcWithCenter:CGPointMake(maxx - topRightRadius, miny + topRightRadius) radius: topRightRadius startAngle: 3 * M_PI_2 endAngle: 0 clockwise: YES];
[path addLineToPoint:CGPointMake(maxx, maxy - bottomRightRadius)];
[path addArcWithCenter:CGPointMake(maxx - bottomRightRadius, maxy - bottomRightRadius) radius: bottomRightRadius startAngle: 0 endAngle: M_PI_2 clockwise: YES];
[path addLineToPoint:CGPointMake(minx + bottomLeftRadius, maxy)];
[path addArcWithCenter:CGPointMake(minx + bottomLeftRadius, maxy - bottomLeftRadius) radius: bottomLeftRadius startAngle: M_PI_2 endAngle:M_PI clockwise: YES];
[path addLineToPoint:CGPointMake(minx, miny + topLeftRadius)];
[path addArcWithCenter:CGPointMake(minx + topLeftRadius, miny + topLeftRadius) radius: topLeftRadius startAngle: M_PI endAngle:3 * M_PI_2 clockwise: YES];
[path closePath];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.path = path.CGPath;
self.messageView.layer.mask = maskLayer;
Here's a UIBezierPath category that does what you want, following the pattern of some of the existing initializers:
extension UIBezierPath {
public convenience init(roundedRect rect: CGRect, topLeftRadius: CGFloat?, topRightRadius: CGFloat?, bottomLeftRadius: CGFloat?, bottomRightRadius: CGFloat?) {
self.init()
assert(((bottomLeftRadius ?? 0) + (bottomRightRadius ?? 0)) <= rect.size.width)
assert(((topLeftRadius ?? 0) + (topRightRadius ?? 0)) <= rect.size.width)
assert(((topLeftRadius ?? 0) + (bottomLeftRadius ?? 0)) <= rect.size.height)
assert(((topRightRadius ?? 0) + (bottomRightRadius ?? 0)) <= rect.size.height)
// corner centers
let tl = CGPoint(x: rect.minX + (topLeftRadius ?? 0), y: rect.minY + (topLeftRadius ?? 0))
let tr = CGPoint(x: rect.maxX - (topRightRadius ?? 0), y: rect.minY + (topRightRadius ?? 0))
let bl = CGPoint(x: rect.minX + (bottomLeftRadius ?? 0), y: rect.maxY - (bottomLeftRadius ?? 0))
let br = CGPoint(x: rect.maxX - (bottomRightRadius ?? 0), y: rect.maxY - (bottomRightRadius ?? 0))
//let topMidpoint = CGPoint(rect.midX, rect.minY)
let topMidpoint = CGPoint(x: rect.midX, y: rect.minY)
makeClockwiseShape: do {
self.move(to: topMidpoint)
if let topRightRadius = topRightRadius {
self.addLine(to: CGPoint(x: rect.maxX - topRightRadius, y: rect.minY))
self.addArc(withCenter: tr, radius: topRightRadius, startAngle: -CGFloat.pi/2, endAngle: 0, clockwise: true)
}
else {
self.addLine(to: tr)
}
if let bottomRightRadius = bottomRightRadius {
self.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - bottomRightRadius))
self.addArc(withCenter: br, radius: bottomRightRadius, startAngle: 0, endAngle: CGFloat.pi/2, clockwise: true)
}
else {
self.addLine(to: br)
}
if let bottomLeftRadius = bottomLeftRadius {
self.addLine(to: CGPoint(x: rect.minX + bottomLeftRadius, y: rect.maxY))
self.addArc(withCenter: bl, radius: bottomLeftRadius, startAngle: CGFloat.pi/2, endAngle: CGFloat.pi, clockwise: true)
}
else {
self.addLine(to: bl)
}
if let topLeftRadius = topLeftRadius {
self.addLine(to: CGPoint(x: rect.minX, y: rect.minY + topLeftRadius))
self.addArc(withCenter: tl, radius: topLeftRadius, startAngle: CGFloat.pi, endAngle: -CGFloat.pi/2, clockwise: true)
}
else {
self.addLine(to: tl)
}
self.close()
}
}
}
Swift 5 UIView extension version of #Mikhail-Grebionkin's answer:
extension UIView {
func makeCustomRound(topLeft: CGFloat = 0, topRight: CGFloat = 0, bottomLeft: CGFloat = 0, bottomRight: CGFloat = 0) {
let minX = bounds.minX
let minY = bounds.minY
let maxX = bounds.maxX
let maxY = bounds.maxY
let path = UIBezierPath()
path.move(to: CGPoint(x: minX + topLeft, y: minY))
path.addLine(to: CGPoint(x: maxX - topRight, y: minY))
path.addArc(withCenter: CGPoint(x: maxX - topRight, y: minY + topRight), radius: topRight, startAngle:CGFloat(3 * Double.pi / 2), endAngle: 0, clockwise: true)
path.addLine(to: CGPoint(x: maxX, y: maxY - bottomRight))
path.addArc(withCenter: CGPoint(x: maxX - bottomRight, y: maxY - bottomRight), radius: bottomRight, startAngle: 0, endAngle: CGFloat(Double.pi / 2), clockwise: true)
path.addLine(to: CGPoint(x: minX + bottomLeft, y: maxY))
path.addArc(withCenter: CGPoint(x: minX + bottomLeft, y: maxY - bottomLeft), radius: bottomLeft, startAngle: CGFloat(Double.pi / 2), endAngle: CGFloat(Double.pi), clockwise: true)
path.addLine(to: CGPoint(x: minX, y: minY + topLeft))
path.addArc(withCenter: CGPoint(x: minX + topLeft, y: minY + topLeft), radius: topLeft, startAngle: CGFloat(Double.pi), endAngle: CGFloat(3 * Double.pi / 2), clockwise: true)
path.close()
let mask = CAShapeLayer()
mask.path = path.cgPath
layer.mask = mask
}
}