You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 18:22:40