SwiftUI中如何绘制向外扩展的形状轮廓?
需求场景
- 已获取自定义形状路径(默认形状也可)
- 需要绘制该形状的轮廓,
lineWidth依赖于@State变量 - 轮廓必须向外扩展,线条变粗时不能丢失原轮廓形状
- 内部填充可为透明
直接使用strokeBorder或stroke都无法满足需求:
strokeBorder的轮廓是以路径实际轮廓为中心绘制的stroke的效果类似,轮廓同样居中绘制
复现代码:
struct ContentView: View { @State private var outlineSize: CGFloat = 1 var body: some View { VStack { Circle() .stroke(.red, lineWidth: self.outlineSize) .frame(width: 100, height: 100) Slider(value: self.$outlineSize, in: 1...10) } } }
解决方案
要实现轮廓完全向外扩展的效果,以下两种方法可以直接落地:
方法一:缩放适配描边
利用缩放让居中描边的内侧部分移出视图范围,只保留向外的轮廓:
struct ContentView: View { @State private var outlineSize: CGFloat = 1 var body: some View { VStack { Circle() .stroke(.red, lineWidth: outlineSize) // 缩放比例 = 1 + 线条宽度 / 原始尺寸(这里原始尺寸为100) .scaleEffect(1 + outlineSize / 100) .frame(width: 100, height: 100) Slider(value: $outlineSize, in: 1...10) } } }
原理:默认描边是居中分布的,缩放后原本向内的部分会被frame裁剪掉,最终只呈现向外扩展的轮廓,同时原始形状的视觉位置保持不变。
方法二:反向内缩+描边
通过inset(by:)让形状先向外扩展,再做居中描边,最终实现完全外扩的效果:
struct ContentView: View { @State private var outlineSize: CGFloat = 1 var body: some View { VStack { Circle() .inset(by: -outlineSize / 2) .stroke(.red, lineWidth: outlineSize) .frame(width: 100, height: 100) .clipped() Slider(value: $outlineSize, in: 1...10) } } }
原理:inset(by: -x)会让形状向外扩展x距离,此时再做居中描边,相当于把描边的全部宽度都落在原始形状的外侧,clipped()确保超出原始frame的部分不会影响整体布局。
自定义形状适配
如果是自定义Shape,只需让其实现InsettableShape协议(系统默认形状如Circle、Rectangle已自带该实现),即可直接使用方法二。示例扩展如下:
extension CustomShape: InsettableShape { func inset(by amount: CGFloat) -> some InsettableShape { CustomShapeInset(original: self, insetAmount: amount) } } // 实现自定义的内缩形状结构体 struct CustomShapeInset: InsettableShape { let original: CustomShape var insetAmount: CGFloat func path(in rect: CGRect) -> Path { // 根据insetAmount调整原始形状的路径 original.path(in: rect.insetBy(dx: -insetAmount, dy: -insetAmount)) } func inset(by amount: CGFloat) -> some InsettableShape { var copy = self copy.insetAmount += amount return copy } }
内容的提问来源于stack exchange,提问作者Baffo rasta
相关产品推荐
相关产品推荐

