在 SwiftUI 里,如果你只想要一个"顶部两个角是圆角,底部是直角"的矩形,第一反应可能是用 .cornerRadius()
。
但 .cornerRadius()
会同时作用在四个角,没办法单独控制某些角,这时候就需要自定义 Shape。
这篇文章,我们就从零实现一个 RoundedTopRectangle
,并详细解释绘制的每一步。
为什么用 Shape
在 SwiftUI 里,Shape 是最底层的绘制单位 。它的 path(in:)
方法允许我们在给定的矩形范围内,自己决定如何绘制路径。
你可以理解成:给了我们一张画布(rect),我们要在上面用"画笔"按顺序描出路径。
move(to:)
就是把画笔移动到某个点(不画线)。addLine(to:)
是画直线。addArc(...)
是画圆弧。closeSubpath()
是闭合路径,自动连回起点。
这样拼起来,就能绘制出任何你想要的形状。
完整代码
less
import SwiftUI
struct RoundedTopRectangle: Shape {
func path(in rect: CGRect) -> Path {
let width = rect.width
let height = rect.height
let cornerRadius: CGFloat = 24
var path = Path()
// 1. 起点:从左上角圆弧的下方开始 (0, cornerRadius)
path.move(to: CGPoint(x: 0, y: cornerRadius))
// 2. 左上角圆弧:180° → 270°
path.addArc(
center: CGPoint(x: cornerRadius, y: cornerRadius),
radius: cornerRadius,
startAngle: .degrees(180),
endAngle: .degrees(270),
clockwise: false
)
// 3. 顶部水平线:连接到右上角圆弧的起点
path.addLine(to: CGPoint(x: width - cornerRadius, y: 0))
// 4. 右上角圆弧:270° → 360°
path.addArc(
center: CGPoint(x: width - cornerRadius, y: cornerRadius),
radius: cornerRadius,
startAngle: .degrees(270),
endAngle: .degrees(360),
clockwise: false
)
// 5. 右侧直线到底部
path.addLine(to: CGPoint(x: width, y: height))
// 6. 底部直线到左下角
path.addLine(to: CGPoint(x: 0, y: height))
// 7. 左侧直线回到圆弧起点
path.addLine(to: CGPoint(x: 0, y: cornerRadius))
// 8. 闭合路径
path.closeSubpath()
return path
}
}
角度怎么理解?
在 SwiftUI 里,角度是数学坐标系的标准方向:
- 0° = 水平方向(正右)
- 90° = 垂直方向(正上)
- 180° = 水平方向(正左)
- 270° = 垂直方向(正下)
所以:
- 左上角圆弧,我们要从左往下画:180° → 270°
- 右上角圆弧,我们要从下往右画:270° → 360°
这就是为什么要用这些角度范围。
效果展示
在 VStack
里放一个 RoundedTopRectangle
:
css
struct ContentView: View {
var body: some View {
VStack {
Spacer()
RoundedTopRectangle()
.fill(Color.blue)
.frame(height: 300)
.shadow(radius: 5)
}
.edgesIgnoringSafeArea(.bottom)
}
}
运行效果:
- 底部是一个蓝色矩形
- 顶部两个角是圆角,底部两个角是直角
- 非常适合做 Bottom Sheet、播放器控制面板、支付弹窗等 UI。
改进思路
-
参数化圆角
目前
cornerRadius
写死为 24,可以改成一个参数:swiftstruct RoundedTopRectangle: Shape { var cornerRadius: CGFloat = 24 func path(in rect: CGRect) -> Path { ... } }
-
只控制某些角
如果你想要"底部圆角,顶部直角",只需要调整绘制的顺序,把圆弧放到底部。
-
支持可配置的四角圆角
甚至可以写成一个更通用的
CustomRoundedRectangle
,传入[topLeft: true, topRight: false, ...]
这样的布尔配置,来决定哪些角是圆角。
总结
.cornerRadius()
方便,但太"平均"。Shape
给了我们精细的控制,可以定义任何奇怪的矩形。- 理解 Path + 坐标 + 角度,你就能自由画出想要的图形。
自定义 Shape 是 SwiftUI 的隐藏宝藏,玩明白之后,不仅可以做圆角矩形,还能画波浪、曲线、对角线圆角等各种有趣的 UI。