Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Drawing Rounded Corners Using UIBezierPath

I have a design element that I'm having trouble figuring out; hoping someone may be able to point me in the right direction. The element I am trying to build is like so;

Rounded Button With No Bottom Border

Effectively, it's a rounded rectangle with a stroke on the left, top, and right sides (the bottom should have no stroke).

I've dabbled in using the following code;

// Create the rounded rectangle
let maskPath = UIBezierPath(roundedRect: myView.bounds, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: 4.0, height: 4.0))

// Setup a shape layer
let shape = CAShapeLayer()

// Create the shape path
shape.path = maskPath.cgPath

// Apply the mask
myView.layer.mask = shape

Subsequently, I'm using the following to draw the stroke around the rect;

// Add border
let borderLayer = CAShapeLayer()
borderLayer.path = maskPath.cgPath
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.strokeColor = UIColor.white.cgColor
borderLayer.lineWidth = 2.0
borderLayer.frame = self.bounds
self.layer.addSublayer(borderLayer)

This results in the following image;

Rounded Rect with Stroke

I've not been able to figure out how to either remove the bottom stroke or draw the item using a UIBezierPath(), but rounding the corners in a way that would be identical to the rounded rect (I'm using another rounded rect in the same view for a different purpose, and the rounded corners would need to be identical).

Thanks!

like image 297
ZbadhabitZ Avatar asked Apr 16 '17 17:04

ZbadhabitZ


People also ask

How do I make rounded corners in MS Paint?

In the upper left corner, select “Draw Filled Shape“. Draw the rounded rectangle over the area you would like to keep for your rounded corners image. Use the Magic Wand to select the area of the rounded rectangle.

How do you make a box with rounded corners in Sketchup?

Press the Up Arrow or Down Arrow key while creating the rectangle. Change the rounding of rounded rectangles. Immediately after you draw a rectangle with the Rounded Rectangle tool, you can specify a radius for the rounded corners by typing a unit of measure and the letter r in the Measurements box.


2 Answers

The CGMutablePath method addArc(tangent1End:tangent2End:radius:transform:) is designed to make round corners easily.

extension CGMutablePath {
    static func bottomlessRoundedRect(in rect: CGRect, radius: CGFloat) -> CGMutablePath {
        let path = CGMutablePath()
        path.move(to: CGPoint(x: rect.minX, y: rect.maxY))
        path.addArc(tangent1End: CGPoint(x: rect.minX, y: rect.minY), tangent2End: CGPoint(x: rect.maxX, y: rect.minY), radius: radius)
        path.addArc(tangent1End: CGPoint(x: rect.maxX, y: rect.minY), tangent2End: CGPoint(x: rect.maxX, y: rect.maxY), radius: radius)
        path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
        return path
    }
}

Once you have that method, it's best to use a custom view to manage the CAShapeLayer so it can adapt to size changes automatically. Demo:

class MyFrameView: UIView {
    override class var layerClass: AnyClass { return CAShapeLayer.self }

    override func layoutSubviews() {
        super.layoutSubviews()
        let layer = self.layer as! CAShapeLayer
        layer.lineWidth = 2
        layer.strokeColor = UIColor.white.cgColor
        layer.fillColor = nil
        layer.path = CGMutablePath.bottomlessRoundedRect(in: bounds.insetBy(dx: 10, dy: 10), radius: 8)
    }
}

import PlaygroundSupport

let view = UIView(frame: CGRect(x: 0, y: 0, width: 120, height: 60))
view.backgroundColor = #colorLiteral(red: 0.7034167647, green: 0.4845994711, blue: 0.6114708185, alpha: 1)
let frameView = MyFrameView(frame: view.bounds)
frameView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(frameView)
let label = UILabel(frame: view.bounds)
label.text = "Hello"
label.textColor = .white
label.textAlignment = .center
view.addSubview(label)

PlaygroundPage.current.liveView = view

Result:

demo

like image 95
rob mayoff Avatar answered Sep 29 '22 04:09

rob mayoff


Don't use a shape layer. Use a layer (or a view). Draw the UIBezierPath's path into it and stroke it, and then erase the bottom line by drawing it and stroking it with a .clear blend mode.

Result:

enter image description here

Code (modify as desired; I use here a clear UIView that draws the shape as its draw code):

let p = UIBezierPath(roundedRect: self.bounds,
                     byRoundingCorners: [.topLeft, .topRight],
                     cornerRadii: CGSize(width: 4.0, height: 4.0))
UIColor.white.setStroke()
p.stroke()
let p2 = UIBezierPath()
p2.move(to: CGPoint(x:0, y:self.bounds.height))
p2.addLine(to: CGPoint(x:self.bounds.width, y:self.bounds.height))
p2.lineWidth = 2
p2.stroke(with: .clear, alpha: 1)

EDIT Another way would have been to clip out the bottom line area before drawing the rounded rect:

let p1 = UIBezierPath(rect: CGRect(origin:.zero,
                                   size:CGSize(width:self.bounds.width, height:self.bounds.height-2)))
p1.addClip()
let p = UIBezierPath(roundedRect: self.bounds,
                     byRoundingCorners: [.topLeft, .topRight],
                     cornerRadii: CGSize(width: 4.0, height: 4.0))
UIColor.white.setStroke()
p.stroke()
like image 37
matt Avatar answered Sep 29 '22 04:09

matt