import SwiftUI /// 顶部带“内凹”弧形的连续圆角矩形 struct ScoopRoundedRect: Shape { var cornerRadius: CGFloat = 20 /// 凹凸的“深度”:>0 向下凸(更接近你截图的效果),<0 为向上凹 var scoopDepth: CGFloat = 10 /// 半宽:控制水平占据范围(越大越“平缓”) var scoopHalfWidth: CGFloat = 18 /// 相对位置(0~1,0.5 正中) var scoopCenterX: CGFloat = 0.33 /// 是否向下凸;为 false 时表现为向上“凹”(与 notch 类似) var convexDown: Bool = true /// 凹陷/鼓包底部的“平底”半宽(0 为无平底) var flatHalfWidth: CGFloat = 8 func path(in rect: CGRect) -> Path { let r = min(cornerRadius, min(rect.width, rect.height) * 0.5) let topY = rect.minY // 约束中心与范围,避免穿出圆角 let minX = rect.minX + r let maxX = rect.maxX - r let centerX = rect.minX + rect.width * scoopCenterX let hw = min(scoopHalfWidth, (maxX - minX) * 0.45) let flatHW = max(0, min(flatHalfWidth, hw * 0.8)) let shoulder = max(1, hw - flatHW) // 两侧曲线的水平长度 let startX = max(minX, centerX - (flatHW + shoulder)) let endX = min(maxX, centerX + (flatHW + shoulder)) let leftFlatX = max(minX, centerX - flatHW) let rightFlatX = min(maxX, centerX + flatHW) let depth = (convexDown ? 1 : -1) * scoopDepth // 左曲线:P0 -> Lf(水平);右曲线:Rf -> P3(水平);Lf~Rf 之间是一段水平直线 let P0 = CGPoint(x: startX, y: topY) let Lf = CGPoint(x: leftFlatX, y: topY + depth) let Rf = CGPoint(x: rightFlatX, y: topY + depth) let P3 = CGPoint(x: endX, y: topY) // 使用圆角近似系数控制手柄长度(基于 shoulder) let k = shoulder * 0.5522847498 let C1 = CGPoint(x: P0.x + k, y: P0.y) // P0 水平切线 let C2 = CGPoint(x: Lf.x - k, y: Lf.y) // Lf 水平切线 let C3 = CGPoint(x: Rf.x + k, y: Rf.y) // Rf 水平切线 let C4 = CGPoint(x: P3.x - k, y: P3.y) // P3 水平切线 var p = Path() // 顶部从左上圆角起 p.move(to: CGPoint(x: rect.minX + r, y: topY)) // 左侧直线到凹/凸开始 p.addLine(to: P0) // 左侧进入曲线 p.addCurve(to: Lf, control1: C1, control2: C2) // 平底水平直线 p.addLine(to: Rf) // 右侧离开曲线 p.addCurve(to: P3, control1: C3, control2: C4) // 顶部到右上圆角 p.addLine(to: CGPoint(x: rect.maxX - r, y: topY)) // 右上圆角 p.addQuadCurve(to: CGPoint(x: rect.maxX, y: rect.minY + r), control: CGPoint(x: rect.maxX, y: rect.minY)) // 右侧直线到右下圆角 p.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - r)) // 右下圆角 p.addQuadCurve(to: CGPoint(x: rect.maxX - r, y: rect.maxY), control: CGPoint(x: rect.maxX, y: rect.maxY)) // 底边到左下圆角 p.addLine(to: CGPoint(x: rect.minX + r, y: rect.maxY)) // 左下圆角 p.addQuadCurve(to: CGPoint(x: rect.minX, y: rect.maxY - r), control: CGPoint(x: rect.minX, y: rect.maxY)) // 左侧到左上圆角 p.addLine(to: CGPoint(x: rect.minX, y: rect.minY + r)) // 左上圆角 p.addQuadCurve(to: CGPoint(x: rect.minX + r, y: rect.minY), control: CGPoint(x: rect.minX, y: rect.minY)) p.closeSubpath() return p } } struct ScoopRoundedRect_Previews: PreviewProvider { static var previews: some View { VStack(spacing: 30) { // 向下“鼓包”且底部有平直段 ScoopRoundedRect(cornerRadius: 24, scoopDepth: 8, scoopHalfWidth: 26, scoopCenterX: 0.25, convexDown: true, flatHalfWidth: 12) .fill(Color.orange) .frame(height: 140) .shadow(color: .black.opacity(0.08), radius: 12, y: 6) .padding() // 中央更深、更宽,平底更宽 ScoopRoundedRect(cornerRadius: 28, scoopDepth: 12, scoopHalfWidth: 36, scoopCenterX: 0.5, convexDown: true, flatHalfWidth: 18) .fill(Color.orange) .frame(height: 140) .shadow(color: .black.opacity(0.08), radius: 12, y: 6) .padding() // 作为对比:向上“凹陷”的 notch,带平底 ScoopRoundedRect(cornerRadius: 24, scoopDepth: 10, scoopHalfWidth: 22, scoopCenterX: 0.6, convexDown: false, flatHalfWidth: 10) .fill(Color.orange) .frame(height: 140) .shadow(color: .black.opacity(0.08), radius: 12, y: 6) .padding() } .background(Color(white: 0.96)) .previewLayout(.sizeThatFits) } }