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

如何在SwiftUI中创建WedgePerimeter形状?单侧宽度无法缩小

问题描述

我不清楚这个几何形状的具体名称,尝试用SwiftUI的Shape组件复现它,但没法把其中一侧的宽度设置得更小。效果示意图为一段弧形条带,靠近起始角的一侧宽度较宽,靠近结束角的一侧宽度收窄。

当前实现代码

struct WedgePerimeter: Shape {
    var startAngle: Angle
    var endAngle: Angle
    var lineMaxWidth: CGFloat
    var lineMinWidth: CGFloat
    
    var animatableData: AnimatablePair<AnimatablePair<Double, Double>, AnimatablePair<Double, Double>> {
        get {
            AnimatablePair(
                AnimatablePair(startAngle.radians, endAngle.radians),
                AnimatablePair(Double(lineMaxWidth), Double(lineMinWidth))
            )
        }
        set {
            startAngle = Angle(radians: newValue.first.first)
            endAngle = Angle(radians: newValue.first.second)
            lineMaxWidth = CGFloat(newValue.second.first)
            lineMinWidth = CGFloat(newValue.second.second)
        }
    }

    func path(in rect: CGRect) -> Path {
        var path = Path()

        let center = CGPoint(x: rect.midX, y: rect.midY)
        let radius = min(rect.width, rect.height) / 2

        // Outer Arc
        path.addArc(center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: false)
        
        // Line to the inner radius at endAngle
        path.addLine(to: CGPoint(x: center.x + (radius - 0) * CGFloat(cos(endAngle.radians)),
                                 y: center.y + (radius - 0) * CGFloat(sin(endAngle.radians))))
        
        // Inner Arc
        path.addArc(center: center, radius: radius - lineMinWidth, startAngle: endAngle, endAngle: startAngle, clockwise: true)
        
        // Line to the outer radius at startAngle
        path.addLine(to: CGPoint(x: center.x + radius * CGFloat(cos(startAngle.radians)),
                                 y: center.y + radius * CGFloat(sin(startAngle.radians))))
        
        //path.closeSubpath()
        
        return path
    }
}

问题分析与修正代码

原代码的核心问题是:结束角位置的直线错误连接到了外半径(radius - 0),同时内弧使用固定半径,无法实现宽度从大到小的渐变。下面是修正后的实现:

方案1:结束端收为尖点

如果需要结束端完全收窄成尖点,代码如下:

struct TaperedWedge: Shape {
    var startAngle: Angle
    var endAngle: Angle
    var maxWidth: CGFloat // 起始端的宽度
    
    var animatableData: AnimatablePair<Double, Double> {
        get {
            AnimatablePair(startAngle.radians, endAngle.radians)
        }
        set {
            startAngle = Angle(radians: newValue.first)
            endAngle = Angle(radians: newValue.second)
        }
    }

    func path(in rect: CGRect) -> Path {
        var path = Path()
        let center = CGPoint(x: rect.midX, y: rect.midY)
        let outerRadius = min(rect.width, rect.height) / 2
        let innerRadiusStart = outerRadius - maxWidth
        
        // 绘制外弧:从起始角到结束角
        path.addArc(center: center, radius: outerRadius, startAngle: startAngle, endAngle: endAngle, clockwise: false)
        
        // 绘制尖点连接线:从外弧终点指向圆心
        path.addLine(to: center)
        
        // 绘制内边缘:从圆心回到起始端内边缘
        path.addLine(to: CGPoint(
            x: center.x + innerRadiusStart * CGFloat(cos(startAngle.radians)),
            y: center.y + innerRadiusStart * CGFloat(sin(startAngle.radians))
        ))
        
        // 闭合路径
        path.closeSubpath()
        
        return path
    }
}

方案2:结束端保留最小宽度

如果需要结束端保留指定的最小宽度,可增加minWidth参数:

struct TaperedWedge: Shape {
    var startAngle: Angle
    var endAngle: Angle
    var maxWidth: CGFloat // 起始端宽度
    var minWidth: CGFloat // 结束端最小宽度
    
    var animatableData: AnimatablePair<AnimatablePair<Double, Double>, AnimatablePair<Double, Double>> {
        get {
            AnimatablePair(
                AnimatablePair(startAngle.radians, endAngle.radians),
                AnimatablePair(Double(maxWidth), Double(minWidth))
            )
        }
        set {
            startAngle = Angle(radians: newValue.first.first)
            endAngle = Angle(radians: newValue.first.second)
            maxWidth = CGFloat(newValue.second.first)
            minWidth = CGFloat(newValue.second.second)
        }
    }

    func path(in rect: CGRect) -> Path {
        var path = Path()
        let center = CGPoint(x: rect.midX, y: rect.midY)
        let outerRadius = min(rect.width, rect.height) / 2
        let innerRadiusStart = outerRadius - maxWidth
        let innerRadiusEnd = outerRadius - minWidth
        
        // 绘制外弧
        path.addArc(center: center, radius: outerRadius, startAngle: startAngle, endAngle: endAngle, clockwise: false)
        
        // 绘制结束端宽度线:从外弧终点到内弧终点
        path.addLine(to: CGPoint(
            x: center.x + innerRadiusEnd * CGFloat(cos(endAngle.radians)),
            y: center.y + innerRadiusEnd * CGFloat(sin(endAngle.radians))
        ))
        
        // 绘制内边缘:连接结束端内点与起始端内点
        path.addLine(to: CGPoint(
            x: center.x + innerRadiusStart * CGFloat(cos(startAngle.radians)),
            y: center.y + innerRadiusStart * CGFloat(sin(startAngle.radians))
        ))
        
        // 闭合路径
        path.closeSubpath()
        
        return path
    }
}

修正说明

  • 调整了结束端的连接线逻辑,根据设置的宽度参数计算对应内半径位置,实现宽度渐变效果
  • 保留了动画支持,确保角度、宽度参数变化时能平滑过渡
  • 两种方案可根据需求选择,方案1适合尖尾效果,方案2适合保留最小宽度的场景

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 18:06:01