Inverted Rounded Rectangle Swift - ios

How would I draw an inverted rounded rectangle as shown below in Swift using a UIBezierPath? To clarify, I want to ONLY draw the shape in black.

While most of us would simply put a white view with rounded corners on top of the black view (it is simpler and it more accurately reflects the visual result), if you really want to draw that shape, create a UIBezierPath that consists of the upper left arc, the upper right arc, and the add lines to the two bottom corners. E.g.
let path = UIBezierPath(
arcCenter: CGPoint(x: rect.minX + cornerRadius, y: rect.minY),
radius: cornerRadius,
startAngle: .pi,
endAngle: .pi / 2,
clockwise: false
)
path.addArc(
withCenter: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY),
radius: cornerRadius,
startAngle: .pi / 2,
endAngle: 0,
clockwise: false
)
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
path.close()
There are a ton of ways to render this:
Create CAShapeLayer with this path and use that as a mask (which I show below);
Add this CAShapeLayer as a sublayer of the view’s layer;
Create a UIImage using UIGraphicsImageRenderer, and fill that UIBezierPath;
etc.
For example:
#IBDesignable class BottomView: UIView {
#IBInspectable var cornerRadius: CGFloat = 15 { didSet { setNeedsLayout() } }
override func layoutSubviews() {
super.layoutSubviews()
let path = UIBezierPath(
arcCenter: CGPoint(x: bounds.minX + cornerRadius, y: bounds.minY),
radius: cornerRadius,
startAngle: .pi,
endAngle: .pi / 2,
clockwise: false
)
path.addArc(
withCenter: CGPoint(x: bounds.maxX - cornerRadius, y: bounds.minY),
radius: cornerRadius,
startAngle: .pi / 2,
endAngle: 0,
clockwise: false
)
path.addLine(to: CGPoint(x: bounds.maxX, y: bounds.maxY))
path.addLine(to: CGPoint(x: bounds.minX, y: bounds.maxY))
path.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.fillColor = UIColor.white.cgColor // the color here is irrelevant; only used to define the mask
layer.mask = shapeLayer
}
}
That yields the following when I (a) add this BottomView to my view hierarchy; and (b) set the backgroundColor of this view to .black (or whatever you want):
I made this #IBDesignable so that I could either add it in Interface Builder storyboard, but you can also add it programmatically, too.
Or you can use this UIBezierPath with any of the patterns enumerated above. Whatever works for you.

What I do is draw two views: the black rectangle, and the top region view with the curves (which I call a "roundyThingy"). So the only interesting part is how to draw the top region view. It too is just a black rectangle, but it has a mask that cuts out the roundy part. So the only really interesting part is the mask:
let size = roundyThingy.bounds.size
let renderer = UIGraphicsImageRenderer(size: size)
let image = renderer.image { context in
UIColor.black.setFill()
context.fill(.init(origin: .zero, size: size))
let path = UIBezierPath(
roundedRect: .init(origin: .zero, size: size),
byRoundingCorners: [.bottomLeft, .bottomRight],
cornerRadii: .init(width: 12, height: 12)
)
path.fill(with: .clear, alpha: 1)
}
roundyThingy.mask = UIImageView(image: image)
roundyThingy.mask?.frame = roundyThingy.bounds

Related

How to add Curve left and right side in UIView for swift

I tried to add a curve but not working.
I have attached the image left side is given by the designer, the right side I have done in swift.
here the if I add corner radius not working for the curve if add curve corner radius not working.
I have added left side and right side separate views, how it's possible to achieve the remove the super view background-color and curve.
extension UIView {
func roundCorners(
corners: UIRectCorner,
radius: CGFloat
) {
let path = UIBezierPath(
roundedRect: bounds,
byRoundingCorners: corners,
cornerRadii: CGSize(
width: radius,
height: radius
)
)
let mask = CAShapeLayer()
mask.path = path.cgPath
layer.mask = mask
}
}
// here I am using on UITableviewCell in layoutSubview
leftCurve.roundCorners(corners: [.topRight,.bottomRight], radius: 20)
rightCurver.roundCorners(corners: [.topLeft,.bottomLeft], radius: 20)
Here is a demo for add curve to both side using UIBezierPath
class ShapeView: UIView {
var path: UIBezierPath = UIBezierPath()
// Set you circle position for verticle.
var circleYPosition: CGFloat = 50 {
didSet {
self.setNeedsDisplay()
}
}
override func awakeFromNib() {
super.awakeFromNib()
self.backgroundColor = UIColor.clear
// Here apply shadow
}
override func draw(_ rect: CGRect) {
super.draw(rect)
// 4 corners radious
UIBezierPath(roundedRect: rect, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 10, height: 10)).addClip()
// Left - right circle
path.move(to: CGPoint(x: 0, y: self.frame.size.height))
//left side
path.addArc(withCenter: CGPoint(x: 0, y: self.circleYPosition - 15),
radius: 10,
startAngle: CGFloat((90 * Double.pi) / 180),
endAngle: CGFloat((270 * Double.pi) / 180),
clockwise: false)
path.addLine(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: self.frame.size.width, y: 0))
path.addLine(to: CGPoint(x: self.frame.size.width, y: self.frame.size.height))
path.move(to: CGPoint(x: self.frame.size.width, y: self.frame.size.height))
//right side
path.addArc(withCenter: CGPoint(x: self.frame.size.width, y: self.circleYPosition - 15),
radius: 10,
startAngle: CGFloat((270 * Double.pi) / 180),
endAngle: CGFloat((90 * Double.pi) / 180),
clockwise: false)
path.close()
UIColor.white.setFill()
path.fill()
// Center Dash path
let dashPath = UIBezierPath()
dashPath.move(to: CGPoint(x: self.bounds.minX + 12, y: self.circleYPosition - 15))
dashPath.addLine(to: CGPoint(x: self.bounds.maxX - 12, y: self.circleYPosition - 15))
dashPath.setLineDash([5,5], count: 2, phase: 0.0)
dashPath.lineWidth = 1.0
dashPath.lineCapStyle = .butt
UIColor.lightGray.set()
dashPath.stroke()
}
}

Drawing Shape layer using Coregraphics - iOS

I am trying to achieve following shape using coregraphics.
I am able to create a rounded rect
func createRoundedRect() {
let path = UIBezierPath(roundedRect: self.bounds, cornerRadius: 15.0)
// Specify the point that the path should start get drawn.
path.move(to: CGPoint(x: 0.0, y: 0.0))
// Create a line between the starting point and the bottom-left side of the view.
path.addLine(to: CGPoint(x: 0.0, y: self.frame.size.height))
// Create the bottom line (bottom-left to bottom-right).
path.addLine(to: CGPoint(x: self.frame.size.width, y: self.frame.size.height))
// Create the vertical line from the bottom-right to the top-right side.
path.addLine(to: CGPoint(x: self.frame.size.width, y: 0.0))
// Close the path. This will create the last line automatically.
path.close()
}
But I am not sure how to make a view of above shape. Any help or idea is appreciated.
You render this with just two arcs, one for the top and one for the bottom. Just use a fat lineWidth and set the strokeColor to be the same as the fillColor to achieve the desired corner radius.
For example:
#IBDesignable
class TvView: UIView {
override class var layerClass: AnyClass { CAShapeLayer.self }
var shapeLayer: CAShapeLayer { return layer as! CAShapeLayer}
#IBInspectable var curveHeight: CGFloat = 10 { didSet { setNeedsLayout() } }
#IBInspectable var cornerRadius: CGFloat = 10 { didSet { setNeedsLayout() } }
override func layoutSubviews() {
super.layoutSubviews()
shapeLayer.fillColor = UIColor.red.cgColor
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.path = path()?.cgPath
shapeLayer.lineWidth = cornerRadius * 2
shapeLayer.lineJoin = .round
}
func path() -> UIBezierPath? {
let rect = bounds.insetBy(dx: cornerRadius, dy: cornerRadius)
guard
rect.height > 2 * curveHeight,
rect.width > 0,
curveHeight > 0
else {
return nil
}
let angle: CGFloat = 2 * (atan2(curveHeight, rect.width / 2))
let radius = rect.width / 2 / sin(angle)
let path = UIBezierPath(arcCenter: CGPoint(x: rect.midX, y: rect.minY + radius), radius: radius, startAngle: .pi * 3 / 2 - angle, endAngle: .pi * 3 / 2 + angle, clockwise: true)
path.addArc(withCenter: CGPoint(x: rect.midX, y: rect.maxY - radius), radius: radius, startAngle: .pi / 2 - angle, endAngle: .pi / 2 + angle, clockwise: true)
path.close()
return path
}
}
Using the same color for stroke and fill, that yields:
Or, so you can see what’s going on, here it is with the stroke rendered in a different color:

How to add a custom shape to an UIImageView in Swift?

I'm trying to add a custom shape to an imageView. Please check the below images.
This is the required one:
This is what I have done so far:
I'm new to Core Graphics and I have done this so far:
private func customImageClipper(imageV: UIImageView){
let path = UIBezierPath()
let size = imageV.frame.size
print(size)
path.move(to: CGPoint(x: 0.0, y: size.height))
path.addLine(to: CGPoint(x: 0.8, y: size.height/2))
path.close()
let shape = CAShapeLayer()
shape.path = path.cgPath
imageV.layer.sublayers = [shape]
}
I'm creating a function to achieve a shape like this, but whenever I pass the imageView into this function, I can not see any change at all. I know that I have to move from points to another point to achieve this shape, but I have never done this. Any help would be appreciated. This is how I'm calling this function:
imageV.layoutIfNeeded()
customImageClipper(imageV: imageV)
P.S.: I'm not using Storyboard, I have created this programmatically.
There are many ways to create shapes using UIBezierPaths. This post here discusses the use of the draw function to create a shape.
Here is an example using your clip function within the cell.
func clip(imageView: UIView, withOffset offset: CGFloat) {
let path = UIBezierPath()
//Move to Top Left
path.move(to: .init(x: imageView.bounds.size.width * offset, y: 0))
//Draw line from Top Left to Top Right
path.addLine(to: .init(x: imageView.bounds.size.width, y: 0))
//Draw Line from Top Right to Bottom Right
path.addLine(to: .init(x: imageView.bounds.size.width * (1 - offset), y: imageView.bounds.size.height))
//Draw Line from Bottom Right to Bottom Left
path.addLine(to: .init(x: 0, y: imageView.bounds.size.height))
//Close Path
path.close()
//Create the Shape Mask for the ImageView
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.fillColor = UIColor.black.cgColor
imageView.layer.mask = shapeLayer
}
In this function, the offset is the amount of angle you would like on the shape, ranging from 0 to 1. (0.4) seems to work for your requirements.
This shares a lot of similarities with Apseri's answer, except I chose the route of percentages, rather than exact size. Nothing wrong with either approach, I just found it easier to understand with percentages. :)
One last note to point out, I used this function in the layoutSubviews function.
override func layoutSubviews() {
super.layoutSubviews()
imageView.layoutIfNeeded()
clip(imageView: self.imageView, withOffset: 0.4)
}
This output the following image:
Hope this helps.
Here is example of some path clipping. Of course path can be also put via parameters, and this can be applied to any view, as shown.
Before:
After (grey background is below ScrollView background):
func customImageClipper(imageV: UIView){
let path = UIBezierPath()
let size = imageV.frame.size
path.move(to: CGPoint(x: size.width/3.0, y: 0))
path.addLine(to: CGPoint(x: size.width/3.0 + 50, y: 0))
path.addLine(to: CGPoint(x: size.width/3.0, y: size.height))
path.addLine(to: CGPoint(x: size.width/3.0 - 50, y: size.height))
path.addLine(to: CGPoint(x: size.width/3.0, y: 0))
path.close()
let shape = CAShapeLayer()
shape.path = path.cgPath
shape.fillColor = UIColor.black.cgColor
imageV.layer.mask = shape
}
1- Subclassing your UIImageView
2- implement your custom drawings inside setNeedsLayout using UIBezierPath
class MyCustomImageView: UIImageView {
override func setNeedsLayout() {
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: self.frame.size.width/2, y: self.frame.size.height))
path.addLineToPoint(CGPoint(x: self.frame.size.width, y: self.frame.size.height/2))
path.addLineToPoint(CGPoint(x: self.frame.size.width/2, y: 0))
path.addArcWithCenter(CGPoint(x: self.frame.size.width/2, y: self.frame.size.height/2), radius: self.frame.size.width/2, startAngle:-CGFloat(M_PI_2), endAngle: CGFloat(M_PI_2), clockwise: false)
path.moveToPoint(CGPoint(x: self.frame.size.width/2, y: self.frame.size.height))
path.closePath()
UIColor.redColor().setFill()
path.stroke()
path.bezierPathByReversingPath()
let shapeLayer = CAShapeLayer()
shapeLayer.frame = self.bounds
shapeLayer.path = path.CGPath
shapeLayer.fillColor = UIColor.redColor().CGColor
self.layer.mask = shapeLayer;
self.layer.masksToBounds = true;
}
}

how to make cropped UIView to show the bottom of that UIView?

I need to make something like this
as you can see, there are two half round views in the right and left side that will show the bottom UIView( UIView with dark blue background color).how to achieve this?
I simplify the problem to be like the picture below:
what should I do to that white view to make cropped effect and actually show the background view (blue view) ?
edit: no, I can't give blue color to that white view. as you can see in the first picture, the background color is actually gradient color, thats why i need to "crop" this UIView to show the background color of the bottom UIView
Here is the view that you want to achieve. The goal is that you need to draw this view with corners and arcs. If you need any help or explanation about how I did that you can just ask.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .blue
view.addSubview(croppedView)
croppedView.cutViewCornersWith(cornerRadius: 20, arcRadius: 14)
}
lazy var croppedView: CroppedView = {
let cv = CroppedView(frame: CGRect(x: 0,
y: 0,
width: self.view.frame.width - 120,
height: 400))
cv.center = view.center
cv.backgroundColor = .lightGray
return cv
}()
}
import UIKit
class CroppedView: UIView {
func cutViewCornersWith(cornerRadius: CGFloat, arcRadius: CGFloat) {
let path = UIBezierPath()
let width = self.frame.width
let height = self.frame.height
let arcCenter = height - height/3
path.move(to: CGPoint(x: 0, y: cornerRadius))
path.addArc(withCenter: CGPoint(x: cornerRadius, y: cornerRadius),
radius: cornerRadius,
startAngle: CGFloat(180.0).toRadians(),
endAngle: CGFloat(270.0).toRadians(),
clockwise: true)
path.addLine(to: CGPoint(x: width - cornerRadius, y: 0.0))
path.addArc(withCenter: CGPoint(x: width - cornerRadius, y: cornerRadius),
radius: cornerRadius,
startAngle: CGFloat(90.0).toRadians(),
endAngle: CGFloat(0.0).toRadians(),
clockwise: true)
path.addLine(to: CGPoint(x: width, y: arcCenter - arcRadius))
path.addArc(withCenter: CGPoint(x: width, y: arcCenter),
radius: arcRadius,
startAngle: CGFloat(270.0).toRadians(),
endAngle: CGFloat(90.0).toRadians(),
clockwise: false)
path.addLine(to: CGPoint(x: width, y: height - cornerRadius))
path.addArc(withCenter: CGPoint(x: width - cornerRadius, y: height - cornerRadius),
radius: cornerRadius,
startAngle: CGFloat(0.0).toRadians(),
endAngle: CGFloat(90.0).toRadians(),
clockwise: true)
path.addLine(to: CGPoint(x: cornerRadius, y: height))
path.addArc(withCenter: CGPoint(x: cornerRadius, y: height - cornerRadius),
radius: cornerRadius,
startAngle: CGFloat(90.0).toRadians(),
endAngle: CGFloat(180.0).toRadians(),
clockwise: true)
path.addLine(to: CGPoint(x: 0, y: arcCenter + arcRadius))
path.addArc(withCenter: CGPoint(x: 0, y: arcCenter),
radius: arcRadius,
startAngle: CGFloat(90.0).toRadians(),
endAngle: CGFloat(270.0).toRadians(),
clockwise: false)
path.addLine(to: CGPoint(x: 0, y: arcCenter - arcRadius))
path.addLine(to: CGPoint(x: 0, y: 0))
path.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
self.layer.mask = shapeLayer
}
}
extension CGFloat {
func toRadians() -> CGFloat {
return self * .pi / 180.0
}
}
There is a way to do this in code. You need to set a 'mask'. I am not sure if you can do this using the InterfaceBuilder (I stay well clear of it). Here is the gist of the code that I used (cut from my code, but untested). Here I make a transparent strip the width of the view and holeHeight tall - I was custom drawing the middle of a picker.
class HoledView: UIView {
override func draw(_ rect: CGRect) {
backgroundColor?.setFill()
UIRectFill(rect)
let layer = CAShapeLayer()
let path = CGMutablePath()
let rect = CGRect(x: 0, y: (self.frame.height - holeHeight) / 2, width: self.frame.width, height: holeHeight)
path.addRect(rect)
path.addRect(self.bounds)
layer.path = path
layer.fillRule = kCAFillRuleEvenOdd
self.layer.mask = layer
}
}
The basic idea is that the mask defines the area that the view will draw into. In my example there is a hole in the middle of the mask, so the view will not draw into that area, and it stays completely transparent.
Although I did this using layers, it may be doable using a mask view instead:
The view’s alpha channel determines how much of the view’s content and background shows through. Fully or partially opaque pixels allow the underlying content to show through but fully transparent pixels block that content.

How to add rounded corner to a UIBezierPath custom rectangle?

I managed to create the rounded corners, but I'm having trouble with the first rounded corner (lower right )
Question :
Can I add an (addArcWithCenter) method before the ( moveToPoint ) method ?
How can i get rid of the straight line at the beginning of the rectangle (lower right) ?
here is my code for the custom rectangle and a screenshot :
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: 300, y: 0))
path.addArcWithCenter(CGPoint(x: 300-10, y: 50), radius: 10 , startAngle: 0 , endAngle: CGFloat(M_PI/2) , clockwise: true) //1st rounded corner
path.addArcWithCenter(CGPoint(x: 200, y: 50), radius:10, startAngle: CGFloat(2 * M_PI / 3), endAngle:CGFloat(M_PI) , clockwise: true)// 2rd rounded corner
path.addArcWithCenter(CGPoint(x: 200, y: 10), radius:10, startAngle: CGFloat(M_PI), endAngle:CGFloat(3 * M_PI / 2), clockwise: true)// 3rd rounded corner
// little triangle at the bottom
path.addLineToPoint(CGPoint(x:240 , y:0))
path.addLineToPoint(CGPoint(x: 245, y: -10))
path.addLineToPoint(CGPoint(x:250, y: 0))
path.addArcWithCenter(CGPoint(x: 290, y: 10), radius: 10, startAngle: CGFloat(3 * M_PI / 2), endAngle: CGFloat(2 * M_PI ), clockwise: true)
path.closePath()
I think what you're doing is overly complicated. UIBezierPath gives you UIBezierPath(roundedRect:) so why not use it? Stroke the rounded rectangle; erase the spot where you're going to put the little triangle; add the triangle; fill the compound path; and stroke the missing two sides of the triangle. Like this (this is just some code I happened to have lying around - you should change the numbers to fit your shape, of course):
let con = UIGraphicsGetCurrentContext()
CGContextTranslateCTM(con, 10, 10)
UIColor.blueColor().setStroke()
UIColor.blueColor().colorWithAlphaComponent(0.4).setFill()
let p = UIBezierPath(roundedRect: CGRectMake(0,0,250,180), cornerRadius: 10)
p.stroke()
CGContextClearRect(con, CGRectMake(20,170,10,11))
let pts = [
CGPointMake(20,180), CGPointMake(20,200),
CGPointMake(20,200), CGPointMake(30,180)
]
p.moveToPoint(pts[0])
p.addLineToPoint(pts[1])
p.addLineToPoint(pts[3])
p.fill()
CGContextStrokeLineSegments(con, pts, 4)
A couple of observations:
Make sure that you take the view bounds and inset it by half of the line width. That ensures that the entire stroked border falls within the bounds of the view. If your line width is 1, this might not be so obvious, but with larger line widths, the problem becomes more pronounced.
If using draw(_:) method, don’t use the rect that is passed to this method, but rather refer to the bounds (inset, as described above). The CGRect passed to draw(_:) is the rectangle being drawn, not necessarily the full bounds. (It generally is, but not always, so always refer to the bounds of the view, not the rect passed to this method.)
As the documentation says (emphasis added):
The portion of the view’s bounds that needs to be updated. The first time your view is drawn, this rectangle is typically the entire visible bounds of your view. However, during subsequent drawing operations, the rectangle may specify only part of your view.
I’d give all of the the various properties of the view a didSet observer that will trigger the view to be redrawn. That way, any IB overrides or programmatically set values will be reflected in the resulting view automatically.
If you want, you can make the whole thing #IBDesignable and make the properties #IBInspectable, so you can see this rendered in Interface Builder. It’s not necessary, but can be useful if you want to see this rendered in storyboards or NIBs.
While you can round corners using a circular arc, using a quad curve is easier, IMHO. You just specify where the arc ends and the corner of the rectangle, and the quadratic bezier will produce a nicely rounded corner. Using this technique, no calculation of angles or the center of the arc is necessary.
Thus:
#IBDesignable
public class BubbleView: UIView {
#IBInspectable public var lineWidth: CGFloat = 1 { didSet { setNeedsDisplay() } }
#IBInspectable public var cornerRadius: CGFloat = 10 { didSet { setNeedsDisplay() } }
#IBInspectable public var calloutSize: CGSize = CGSize(width: 10, height: 5) { didSet { setNeedsDisplay() } }
#IBInspectable public var fillColor: UIColor = .yellow { didSet { setNeedsDisplay() } }
#IBInspectable public var strokeColor: UIColor = .black { didSet { setNeedsDisplay() } }
override public func draw(_ rect: CGRect) {
let rect = bounds.insetBy(dx: lineWidth / 2, dy: lineWidth / 2)
let path = UIBezierPath()
// lower left corner
path.move(to: CGPoint(x: rect.minX + cornerRadius, y: rect.maxY - calloutSize.height))
path.addQuadCurve(to: CGPoint(x: rect.minX, y: rect.maxY - calloutSize.height - cornerRadius),
controlPoint: CGPoint(x: rect.minX, y: rect.maxY - calloutSize.height))
// left
path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + cornerRadius))
// upper left corner
path.addQuadCurve(to: CGPoint(x: rect.minX + cornerRadius, y: rect.minY),
controlPoint: CGPoint(x: rect.minX, y: rect.minY))
// top
path.addLine(to: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY))
// upper right corner
path.addQuadCurve(to: CGPoint(x: rect.maxX, y: rect.minY + cornerRadius),
controlPoint: CGPoint(x: rect.maxX, y: rect.minY))
// right
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - calloutSize.height - cornerRadius))
// lower right corner
path.addQuadCurve(to: CGPoint(x: rect.maxX - cornerRadius, y: rect.maxY - calloutSize.height),
controlPoint: CGPoint(x: rect.maxX, y: rect.maxY - calloutSize.height))
// bottom (including callout)
path.addLine(to: CGPoint(x: rect.midX + calloutSize.width / 2, y: rect.maxY - calloutSize.height))
path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.midX - calloutSize.width / 2, y: rect.maxY - calloutSize.height))
path.close()
fillColor.setFill()
path.fill()
strokeColor.setStroke()
path.lineWidth = lineWidth
path.stroke()
}
}
That yields:
Instead of starting the code with a straight line :
path.moveToPoint(CGPoint(x: 300, y: 0))
I instead start with an arc (upper right):
path.addArcWithCenter(CGPoint(x: 300-10, y: 50), radius: 10 , startAngle: 0 , endAngle: CGFloat(M_PI/2) , clockwise: true) //1st rounded corner
and by doing this, I have four rounded corners and I just need to add a straight line at the end of the code right before:
path.closePath()
Here is the code and a screenshot:
let path = UIBezierPath()
path.addArcWithCenter(CGPoint(x: 300-10, y: 50), radius: 10 , startAngle: 0 , endAngle: CGFloat(M_PI/2) , clockwise: true) //1st rounded corner
path.addArcWithCenter(CGPoint(x: 200, y: 50), radius:10, startAngle: CGFloat(2 * M_PI / 3), endAngle:CGFloat(M_PI) , clockwise: true)// 2rd rounded corner
path.addArcWithCenter(CGPoint(x: 200, y: 10), radius:10, startAngle: CGFloat(M_PI), endAngle:CGFloat(3 * M_PI / 2), clockwise: true)// 3rd rounded corner
// little triangle
path.addLineToPoint(CGPoint(x:240 , y:0))
path.addLineToPoint(CGPoint(x: 245, y: -10))
path.addLineToPoint(CGPoint(x:250, y: 0))
path.addArcWithCenter(CGPoint(x: 290, y: 10), radius: 10, startAngle: CGFloat(3 * M_PI / 2), endAngle: CGFloat(2 * M_PI ), clockwise: true)
path.addLineToPoint(CGPoint(x:300 , y:50))
path.closePath()
Swift 5 with configuration variables:
override func draw(_ rect: CGRect) {
let arrowXOffset: CGFloat = 13
let cornerRadius: CGFloat = 6
let arrowHeight: CGFloat = 6
let mainRect = CGRect(origin: rect.origin, size: CGSize(width: rect.width, height: rect.height - arrowHeight))
let leftTopPoint = mainRect.origin
let rightTopPoint = CGPoint(x: mainRect.maxX, y: mainRect.minY)
let rightBottomPoint = CGPoint(x: mainRect.maxX, y: mainRect.maxY)
let leftBottomPoint = CGPoint(x: mainRect.minX, y: mainRect.maxY)
let leftArrowPoint = CGPoint(x: leftBottomPoint.x + arrowXOffset, y: leftBottomPoint.y)
let centerArrowPoint = CGPoint(x: leftArrowPoint.x + arrowHeight, y: leftArrowPoint.y + arrowHeight)
let rightArrowPoint = CGPoint(x: leftArrowPoint.x + 2 * arrowHeight, y: leftArrowPoint.y)
let path = UIBezierPath()
path.addArc(withCenter: CGPoint(x: rightTopPoint.x - cornerRadius, y: rightTopPoint.y + cornerRadius), radius: cornerRadius,
startAngle: CGFloat(3 * Double.pi / 2), endAngle: CGFloat(2 * Double.pi), clockwise: true)
path.addArc(withCenter: CGPoint(x: rightBottomPoint.x - cornerRadius, y: rightBottomPoint.y - cornerRadius), radius: cornerRadius,
startAngle: 0, endAngle: CGFloat(Double.pi / 2), clockwise: true)
path.addLine(to: rightArrowPoint)
path.addLine(to: centerArrowPoint)
path.addLine(to: leftArrowPoint)
path.addArc(withCenter: CGPoint(x: leftBottomPoint.x + cornerRadius, y: leftBottomPoint.y - cornerRadius), radius: cornerRadius,
startAngle: CGFloat(Double.pi / 2), endAngle: CGFloat(Double.pi), clockwise: true)
path.addArc(withCenter: CGPoint(x: leftTopPoint.x + cornerRadius, y: leftTopPoint.y + cornerRadius), radius: cornerRadius,
startAngle: CGFloat(Double.pi), endAngle: CGFloat(3 * Double.pi / 2), clockwise: true)
path.addLine(to: rightTopPoint)
path.close()
}
You can't do this automatically. You have to make the lines shorter and then use arcs of the radius that you want the corner radius to be.
So. Instead of adding a line to x,y you add the line to x-radius, y.
Then add the arc. Then the next line starts at x, y+radius.

Resources