SwiftUI:具有内部笔划的楔形

SwiftUI: Wedge shape with inner stroke

我正在尝试制作如图所示的图表。

下面的代码很好,但我真的很想用内部笔划(而不是居中)绘制每个楔形的边界:

import SwiftUI

struct WedgeShape: Shape {
    var startAngle: Angle
    var endAngle: Angle

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

        p.addArc(
            center: CGPoint(x: rect.size.width/2, y: rect.size.width/2),
            radius: rect.size.width/2,
            startAngle: startAngle,
            endAngle: endAngle,
            clockwise: false
        )

        return p
    }
}

struct Wedge {
    var startAngle: Double
    var endAngle: Double
    var color: Color
}

struct ContentView: View {
    var wedges = [
        Wedge(startAngle: 0, endAngle: 90, color: Color.red),
        Wedge(startAngle: 90, endAngle: 180, color: Color.green),
        Wedge(startAngle: 180, endAngle: 360, color: Color.blue)
    ]

    var body: some View {
        ZStack {
            ForEach(0 ..< wedges.count) {
                WedgeShape(
                    startAngle: Angle(degrees: self.wedges[[=10=]].startAngle),
                    endAngle: Angle(degrees: self.wedges[[=10=]].endAngle)
                )
                .stroke(self.wedges[[=10=]].color, lineWidth: 44)
            }
        }
    }
}

我知道我可以简单地调整视图的框架来考虑笔划。但是,我想要像 Circle().strokeBorder(Color.green, lineWidth: 44) 这样的东西,其中已经考虑了中风。 InsettableShape 协议看起来可能对此有所帮助,但我不确定如何使用 inset(by:) 方法。

这是我创建楔形的方法。我画了楔子的完整路径,然后你可以设置foregroundColor。只需更改 a0a1 即可更改楔形的长度。

struct WedgeShape: Shape {
  func path(in rect: CGRect) -> Path {
    var p = Path()
    let a0 = Angle(degrees: 0.0)
    let a1 = Angle(degrees: 270.0)
    let cen =  CGPoint(x: rect.size.width/2, y: rect.size.width/2)
    let r0 = rect.size.width/3.5
    let r1 = rect.size.width/2
    p.addArc(center: cen, radius: r0, startAngle: a0, endAngle: a1, clockwise: false)
    p.addArc(center: cen, radius: r1, startAngle: a1, endAngle: a0, clockwise: true)
    p.closeSubpath()
    return p
  }
}

// Then in your `View`
var body: some View {
    WedgeShape()
      .frame(width: 20, height: 20, alignment: .center)
      .foregroundColor(.green)
}

根据 Ricky Padilla 的回答更新了解决方案,重构 WedgeShape 以在调用站点获取 startAngleendAnglelineWidth

import SwiftUI

struct WedgeShape: Shape {
    let startAngle: Angle
    let endAngle: Angle
    let lineWidth: CGFloat

    func path(in rect: CGRect) -> Path {
        var p = Path()
        let center =  CGPoint(x: rect.size.width/2, y: rect.size.width/2)
        let r1 = rect.size.width/2
        p.addArc(center: center, radius: abs(lineWidth - r1), startAngle: startAngle, endAngle: endAngle, clockwise: false)
        p.addArc(center: center, radius: r1, startAngle: endAngle, endAngle: startAngle, clockwise: true)
        p.closeSubpath()
        return p
    }
}

struct Wedge {
    var startAngle: Double
    var endAngle: Double
    var color: Color
}

struct ContentView: View {
    let wedges = [
        Wedge(startAngle: -45, endAngle: 45, color: Color.blue),
        Wedge(startAngle: 45, endAngle: 180, color: Color.green),
        Wedge(startAngle: 180, endAngle: -45, color: Color.red)
    ]

    var body: some View {
        ZStack {
            ForEach(0 ..< wedges.count) {
                WedgeShape(
                    startAngle: Angle(degrees: self.wedges[[=10=]].startAngle),
                    endAngle: Angle(degrees: self.wedges[[=10=]].endAngle),
                    lineWidth: 44
                )
                .foregroundColor(self.wedges[[=10=]].color)
            }
        }
    }
}

此外,使用普通乔的答案的替代选项:

import SwiftUI

struct WedgeShape: Shape {
    var progress: Double
    var lineWidth: CGFloat
    var lineCap: CGLineCap = .butt

    func path(in rect: CGRect) -> Path {
        let insetWidth = lineWidth/2
        let rect = rect.insetBy(dx: insetWidth, dy: insetWidth)

        return Path() {
            [=11=].addArc(
                center: CGPoint(x: rect.midX, y: rect.midY),
                radius: min(rect.width, rect.height)/2,
                startAngle: Angle(degrees: -90),
                endAngle: Angle(degrees: -90 + 360 * progress),
                clockwise: false
            )
        }
        .strokedPath(StrokeStyle(lineWidth: lineWidth, lineCap: lineCap))
    }
}

struct Wedge {
    var progress: Double
    var color: Color
}

struct ContentView: View {
    let wedges = [
        Wedge(progress: 1, color: Color.green),
        Wedge(progress: 0.625, color: Color.blue),
        Wedge(progress: 0.375, color: Color.red)
    ]

    var body: some View {
        ZStack {
            ForEach(0 ..< wedges.count) {
                WedgeShape(
                    progress: self.wedges[[=11=]].progress,
                    lineWidth: 44
                )
                .foregroundColor(self.wedges[[=11=]].color)
            }
        }
        .rotationEffect(Angle(degrees: -90))
    }
}

strokedPath() 的帮助下,像这里这样调整 rect 就足够了:

struct Ring: Shape {

    var progress: Double = 0.0
    var thickness: Double = 10.0

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

        let halfThickness = CGFloat(thickness / 2.0)
        let rect = rect.insetBy(dx: halfThickness, dy: halfThickness)

        return Path() {
            [=10=].addArc(center: CGPoint(x: rect.midX, y: rect.midY),
                      radius: min(rect.width, rect.height) / 2,
                      startAngle: Angle(degrees: -90),
                      endAngle: Angle(degrees: -90 + 360 * progress),
                      clockwise: false)
        }.strokedPath(StrokeStyle(lineWidth: CGFloat(thickness), lineCap: .round))
    }

    var animatableData: Double {
        get { progress }
        set { progress = min(1.0, max(0, newValue)) }
    }
}

然后

Ring(progress: 0.5).foregroundColor(.red)

我不能说出名字的人让我把这个代码留在这里。他注意到 运行 是安全的,不会窃取你的加密货币。基于 Ricky Padilla 和 JWK 的示例。

struct WedgeShape: Shape {
    let startAngle: Angle
    let endAngle: Angle
    let outterScale: CGFloat
    let innerScale: CGFloat

    func path(in rect: CGRect) -> Path {
        var p = Path()
        let center =  CGPoint(x: rect.size.width/2, y: rect.size.width/2)
        //let r1 = rect.size.width/2
        p.addArc(center: center,
                 radius: outterScale*rect.size.width/2,
                 //radius: abs(lineWidth - r1),
                 startAngle: startAngle,
                 endAngle: endAngle,
                 clockwise: false)
        p.addArc(center: center,
                 //radius: abs(lineWidth - r1),
                 radius: innerScale*rect.size.width/6,
                 startAngle: endAngle,
                 endAngle: startAngle,
                 clockwise: true)
        p.closeSubpath()
        return p
    }
}

struct Wedge {
    var startAngle: Double
    var endAngle: Double
    var color: Color
}

struct ContentViewWedge: View {
    let pokemonWedges = [
        //Wedge(startAngle: -45, endAngle: 45, color: Color.blue),
        Wedge(startAngle: 90, endAngle: 270, color: Color.white),
        Wedge(startAngle: -90, endAngle: 90, color: Color.red)
    ]

    let backgroundWedge = Wedge(startAngle: 0, endAngle: 360, color: Color.black)

    var body: some View {
        ZStack(alignment: .center) {
            WedgeShape(startAngle: Angle(degrees: self.backgroundWedge.startAngle-90),
                       endAngle: Angle(degrees: self.backgroundWedge.endAngle-90),
                       outterScale: 1.07,
                       innerScale: 1)
                .foregroundColor(self.backgroundWedge.color)
            WedgeShape(
                startAngle: Angle(degrees: self.pokemonWedges[1].startAngle-90),
                endAngle: Angle(degrees: self.pokemonWedges[1].endAngle-90),
                outterScale: 1,
                innerScale: 1.3
            ).foregroundColor(self.pokemonWedges[1].color).offset(y: -3)
            WedgeShape(
             startAngle: Angle(degrees: self.pokemonWedges[0].startAngle-90),
             endAngle: Angle(degrees: self.pokemonWedges[0].endAngle-90),
             outterScale: 1,
             innerScale: 1.3
             ).foregroundColor(self.pokemonWedges[0].color).offset(y: +3)
        }
        .aspectRatio(1, contentMode: .fit)
        .padding(20)
    }
}

struct WedgeShapeView_Previews: PreviewProvider {
    static var previews: some View {
        ContentViewWedge()
    }
}

我更喜欢将圆形与 trim

一起使用
struct WedgeView: View {
    let lineWidth: CGFloat = 60

    var body: some View {
        ZStack {
            Circle()
                .trim(from: 0, to: 0.4)
                .stroke(Color.red, style: StrokeStyle(lineWidth: lineWidth, lineCap: .butt)
            ).rotationEffect(.degrees(-180))
            Circle()
                .trim(from: 0, to: 0.2)
                .stroke(Color.blue, style: StrokeStyle(lineWidth: lineWidth, lineCap: .butt)
            ).rotationEffect(.degrees(-180 + 360.0 * 0.4))
            Circle()
                .trim(from: 0, to: 0.4)
                .stroke(Color.green, style: StrokeStyle(lineWidth: lineWidth, lineCap: .butt)
            ).rotationEffect(.degrees(-180.0 + 360.0 * 0.6)) // 0.6 = 0.4 + 0.2

        }
             // Stroke draws over the edge, so we add some padding to keep things within the frame
            .padding(lineWidth/2)
    }
}

这里的一切都是硬编码的,但您明白了它的要点。