I've tried everything, but it does not make what I need. I think it's impossible. The half circle is too sharp, but it won't build a more cornered one. Middle ark won't become more curved no matter what I try. You can see it in c4 part
struct TabBarShape: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
let middleRad: CGFloat = rect.height - 10.0
let cornerRad: CGFloat = 12.0
// Define corner points
let topLeftC = CGPoint(x: rect.minX + cornerRad, y: rect.minY + cornerRad)
let topRightC = CGPoint(x: rect.maxX - cornerRad, y: rect.minY + cornerRad)
let botRightC = CGPoint(x: rect.maxX - cornerRad, y: rect.maxY - cornerRad)
let botLeftC = CGPoint(x: rect.minX + cornerRad, y: rect.maxY - cornerRad)
// 1: Start at the top left arc point
var pt = CGPoint(x: rect.minX, y: rect.minY + cornerRad)
path.move(to: pt)
// c1: Top-left corner
path.addArc(center: topLeftC, radius: cornerRad, startAngle: .degrees(180), endAngle: .degrees(270), clockwise: false)
// 2: Move to middle left before the curve
pt = CGPoint(x: rect.midX - middleRad, y: rect.minY)
path.addLine(to: pt)
// c2: Top-left middle arc
pt.y += middleRad * 0.5
path.addArc(center: pt, radius: middleRad * 0.5, startAngle: .degrees(-90), endAngle: .degrees(0), clockwise: false)
// c3: Top-middle right arc
pt.x += middleRad
path.addArc(center: pt, radius: middleRad * 0.5, startAngle: .degrees(180), endAngle: .degrees(0), clockwise: true)
// c4: Complete middle-right arc
pt.x += middleRad
path.addArc(center: pt, radius: middleRad * 0.5, startAngle: .degrees(180), endAngle: .degrees(270), clockwise: false)
// 3: Top-right line to corner
pt = CGPoint(x: rect.maxX - cornerRad, y: rect.minY)
path.addLine(to: pt)
// c5: Top-right corner
path.addArc(center: topRightC, radius: cornerRad, startAngle: .degrees(-90), endAngle: .degrees(0), clockwise: false)
// 4: Right side down to bottom-right corner
pt = CGPoint(x: rect.maxX, y: rect.maxY - cornerRad)
path.addLine(to: pt)
// c6: Bottom-right corner
path.addArc(center: botRightC, radius: cornerRad, startAngle: .degrees(0), endAngle: .degrees(90), clockwise: false)
// 5: Bottom-left line to corner
pt = CGPoint(x: rect.minX + cornerRad, y: rect.maxY)
path.addLine(to: pt)
// c7: Bottom-left corner
path.addArc(center: botLeftC, radius: cornerRad, startAngle: .degrees(90), endAngle: .degrees(180), clockwise: false)
path.closeSubpath()
return path
}
}
You can do this by using .addCurve
with control points for the "gap" instead of .addArc
:
Sample view subclass:
class TabBarShapeView: UIView {
// adjust these as desired
let gapWidth: CGFloat = 120.0
let gapHeight: CGFloat = 36.0
let cornerRad: CGFloat = 12.0
let fillColor: UIColor = .init(red: 0.0, green: 0.0, blue: 0.5, alpha: 1.0)
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()
}
private func commonInit() {
shapeLayer = self.layer as? CAShapeLayer
shapeLayer.fillColor = fillColor.cgColor
}
func updateMe() {
let b = bounds.insetBy(dx: 1.0 / UIScreen.main.scale, dy: 1.0 / UIScreen.main.scale)
let leftPt: CGPoint = .init(x: b.midX - gapWidth * 0.5, y: 0.0)
let rightPt: CGPoint = .init(x: bounds.maxX - leftPt.x, y: leftPt.y)
let bottomCenterPt: CGPoint = .init(x: b.midX, y: gapHeight)
// control points for left side of gap
let lcp1: CGPoint = .init(x: leftPt.x + gapWidth * 0.25, y: 0.0)
let lcp2: CGPoint = .init(x: leftPt.x + gapWidth * 0.25, y: bottomCenterPt.y)
// control points for right side of gap
let rcp1: CGPoint = .init(x: b.maxX - lcp2.x, y: lcp2.y)
let rcp2: CGPoint = .init(x: b.maxX - lcp1.x, y: lcp1.y)
let shape: UIBezierPath = UIBezierPath()
shape.move(to: leftPt)
// add left-side of gap curve
shape.addCurve(to: bottomCenterPt, controlPoint1: lcp1, controlPoint2: lcp2)
// add right-side of gap curve
shape.addCurve(to: rightPt, controlPoint1: rcp1, controlPoint2: rcp2)
// add top-right, bottom-right, bottom-left, top-left curved corners
shape.addArc(withCenter: .init(x: b.maxX - cornerRad, y: b.minY + cornerRad), radius: cornerRad, startAngle: -.pi * 0.5, endAngle: .pi * 0.0, clockwise: true)
shape.addArc(withCenter: .init(x: b.maxX - cornerRad, y: b.maxY - cornerRad), radius: cornerRad, startAngle: .pi * 0.0, endAngle: .pi * 0.5, clockwise: true)
shape.addArc(withCenter: .init(x: b.minX + cornerRad, y: b.maxY - cornerRad), radius: cornerRad, startAngle: .pi * 0.5, endAngle: .pi * 1.0, clockwise: true)
shape.addArc(withCenter: .init(x: b.minX + cornerRad, y: b.minY + cornerRad), radius: cornerRad, startAngle: .pi * 1.0, endAngle: .pi * 1.5, clockwise: true)
shape.close()
shapeLayer.path = shape.cgPath
}
override func layoutSubviews() {
super.layoutSubviews()
updateMe()
}
}
Controller to show the view:
class TabBarShapeTestVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
let pv = TabBarShapeView()
pv.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(pv)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
pv.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
pv.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 12.0),
pv.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -12.0),
pv.heightAnchor.constraint(equalToConstant: 60.0),
])
}
}
Result: