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

SwiftUI中如何为形状的描边添加内阴影效果

问题原因

原有内阴影实现使用完整的实心形状作为阴影层的遮罩,模糊后的阴影会铺满整个形状的内部区域,因此会溢出描边范围覆盖中间空心区域,无法实现仅附着在描边上的效果。

兼容iOS16以下版本的实现方案

核心调整点:

  • 先裁剪阴影层,去掉形状外侧溢出的阴影,仅保留向内扩散的内阴影方向内容
  • 使用奇偶填充规则生成环形遮罩,挖掉中间空心区域的阴影,让阴影仅出现在描边对应的环形范围内

完整代码

struct ContentView: View {
    var body: some View {
        VStack {
            // 对照:普通无阴影描边
            PolygonShape(sides: 7)
                .stroke(Color.white, lineWidth: 10)
            // 带描边内阴影的效果
            PolygonShape(sides: 7)
                .strokeWithInnerShadow(
                    strokeColor: .white,
                    lineWidth: 10,
                    shadowColor: .red,
                    shadowRadius: 4
                )
        }
        .padding(32)
        .background(Color.black.ignoresSafeArea())
    }
}

extension Shape {
    func strokeWithInnerShadow(
        strokeColor: Color,
        lineWidth: CGFloat,
        shadowColor: Color,
        shadowRadius: CGFloat
    ) -> some View {
        // 基础描边层
        self.stroke(strokeColor, lineWidth: lineWidth)
            .overlay(
                self
                    .stroke(shadowColor, lineWidth: lineWidth)
                    .blur(radius: shadowRadius)
                    // 裁掉形状外部的阴影,仅保留向内扩散的内阴影
                    .clipShape(self)
                    .mask(
                        GeometryReader { proxy in
                            let size = proxy.size
                            // 内缩距离为描边宽度的1/2,对齐描边的内沿
                            let inset = lineWidth / 2
                            Path { path in
                                // 外边界:原形状完整路径
                                path.addPath(self.path(in: CGRect(origin: .zero, size: size)))
                                // 内边界:向内缩进后的形状路径
                                let innerRect = CGRect(origin: .zero, size: size)
                                    .insetBy(dx: inset, dy: inset)
                                path.addPath(self.path(in: innerRect))
                            }
                            // 奇偶填充生成环形遮罩,挖空中间区域
                            .fill(style: FillStyle(eoFill: true, antialiased: true))
                        }
                    )
            )
    }
}

struct PolygonShape: Shape {
    let sides: Int

    func path(in rect: CGRect) -> Path {
        let c = CGPoint(x: rect.width/2.0, y: rect.height/2.0)
        let r = Double(min(rect.width,rect.height)) / 2.0
        var vertices:[CGPoint] = []
        for i in 0...sides {
            let angle = (2.0 * Double.pi * Double(i)/Double(sides))
            let pt = CGPoint(x: r * cos(angle), y: r * sin(angle))
            vertices.append(CGPoint(x: pt.x + c.x, y: pt.y + c.y))
        }
        var path = Path()
        for (n, pt) in vertices.enumerated() {
            n == 0 ? path.move(to: pt) : path.addLine(to: pt)
        }
        path.closeSubpath()
        return path
    }
}

struct StackedElementsView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

说明

  • 该实现兼容iOS13及以上版本,无iOS16专属API依赖
  • 对于系统自带Shape(Circle、RoundedRectangle、Capsule等)和所有自动在传入rect内居中绘制的自定义Shape,该方法都可以正常生效
  • 可通过调整shadowRadius参数控制阴影模糊范围,需要更真实的阴影效果可以给阴影层添加小幅内偏移,例如在.blur后调用.offset(x: 0, y: 1)模拟光照方向

内容的提问来源于stack exchange,提问作者TruMan1

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 23:51:23