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;
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;
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!
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.
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.
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:
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:
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()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With