SwiftUI:内部描边的楔形形状

4

我正在尝试制作类似于图片中所示的图表。

enter image description here

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

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[$0].startAngle),
                    endAngle: Angle(degrees: self.wedges[$0].endAngle)
                )
                .stroke(self.wedges[$0].color, lineWidth: 44)
            }
        }
    }
}

我知道可以通过调整视图的框架来处理笔画。 但是,我想要像 Circle().strokeBorder(Color.green, lineWidth: 44) 这样的结果,其中笔画已经被考虑在内了。 InsettableShape 协议似乎对此有帮助,但我不确定如何使用 inset(by:) 方法。

5个回答

3
通过使用 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() {
            $0.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)

好观点!诚然,对于调用者来说,使用 progress 可能更容易。但是这个答案的注意事项是,为了实现我问题中的图像,数据需要按降序排序(以防止较大的值覆盖较小的值)。根据您想要的内容,可能还需要旋转。 - JWK
澄清一下,我指的是使用“progress”而不是起始和结束角度时需要注意的问题。使用“strokedPath”比使用2个弧线的实现要简单得多 :) - JWK
当然,我的目标不是为您提供准备好的“Wedge”实现,而是展示如何在不超出“Shape”边界的情况下绘制描边弧。个人而言,我更喜欢上述技术,因为它比2个弧线更简单。关于“progress”,只是将其留在这里以便示例编译,但我认为它与您的答案无关。 - average Joe
虽然这似乎不是问题的答案,但这正是我在寻找的。 - Michael Ozeryansky

1
这是我创建楔形的方法。我先绘制了整个楔形路径,然后可以设置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)
}


不错!我没有想过使用两个弧。 - JWK

0

基于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[$0].startAngle),
                    endAngle: Angle(degrees: self.wedges[$0].endAngle),
                    lineWidth: 44
                )
                .foregroundColor(self.wedges[$0].color)
            }
        }
    }
}

此外,还有一种替代方案,使用普通用户Joe的答案:

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() {
            $0.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[$0].progress,
                    lineWidth: 44
                )
                .foregroundColor(self.wedges[$0].color)
            }
        }
        .rotationEffect(Angle(degrees: -90))
    }
}

0

有人让我把这段代码留在这里,但我不能透露他的名字。他注意到这段代码是安全的,不会窃取你的加密货币。这段代码基于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()
    }
}

0

我更喜欢使用带有修剪的圆形形状

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)
    }
}

这里的所有内容都是硬编码的,但你能理解它的意思。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接