SwiftUI圆形滑块问题:使用两个控制点时未显示正确的描边。

3
我有一个圆形视图,带有两个控制旋钮和它们之间的描边,如您从图像中所见。
数值是正确的,但问题是如何显示蓝色描边相反?用户可能希望范围为NW、N到NE,而不是包括S到NE,但用户需要能够从两者中选择。下面的代码可以直接粘贴以显示与图像相同的内容。

Swiftui circular slider

import SwiftUI

struct CircularSliderView: View {
var body: some View {
    VStack(){
        DirectionView()
        
        Spacer()
     }
   }
}

struct SwellCircularSliderView_Previews: PreviewProvider {
static var previews: some View {
    CircularSliderView()
 }
    }

    struct DirectionView: View {

    @State var directionValue: CGFloat = 0.0
    @State var secondaryDirectionValue: CGFloat = 0.0

var body: some View {
    VStack {
        Text("\(directionValue, specifier: "%.0f")° - \(secondaryDirectionValue, specifier: "%.0f")°   \(Double().degreesToCompassDirection(degree: Double(directionValue))) - \(Double().degreesToCompassDirection(degree: Double(secondaryDirectionValue)))")
            .font(.body)
        
        
         DirectionControlView(directionValue: $directionValue, secondaryDirectionValue: $secondaryDirectionValue)
            .padding(.top, 60)
        
        Spacer()
    }//: VSTACK
}
}

struct DirectionControlView: View {
@Binding var directionValue: CGFloat
@State var dirAngleValue: CGFloat = 0.0

@Binding var secondaryDirectionValue: CGFloat
@State var secondaryDirAngleValue: CGFloat = 0.0


let minimumValue: CGFloat = 0
let maximumValue: CGFloat = 360.0
let totalValue: CGFloat = 360.0
let knobRadius: CGFloat = 10.0
let radius: CGFloat = 125.0

private let tickHeight: CGFloat = 8
private let longTickHeight: CGFloat = 14
private let tickWidth: CGFloat = 2

func minimumTrimValue() -> CGFloat{
    if directionValue > secondaryDirectionValue {
        return secondaryDirectionValue/totalValue
    } else {
        return directionValue/totalValue
    }
}

func maximumTrimValue() -> CGFloat{
    if  directionValue > secondaryDirectionValue {
        return directionValue/totalValue
    } else {
        return secondaryDirectionValue/totalValue
    }
}

var body: some View {
    ZStack {
        
        Circle()
            .trim(from: minimumTrimValue(), to: maximumTrimValue())
            .stroke(
                AngularGradient(gradient: Gradient(
                                    colors: [Color.blue.opacity(0.2), Color.blue.opacity(1), Color.blue.opacity(0.2)]),
                                center: .center,
                                startAngle: .degrees(Double(secondaryDirectionValue)),
                                endAngle: .degrees(Double(directionValue))),
                style: StrokeStyle(lineWidth: 8, lineCap: .round)
            )
            .frame(width: radius * 2, height: radius * 2)
            .rotationEffect(.degrees(-90))
        
        KnobCircle(radius: knobRadius * 2, padding: 6)
            .offset(y: -radius)
            .rotationEffect(Angle.degrees(Double(dirAngleValue)))
            .shadow(color: Color.black.opacity(0.2), radius: 3, x: -3)
            .gesture(DragGesture(minimumDistance: 0.0)
                        .onChanged({ angleValue in
                            knobChange(location: angleValue.location)
                        }))
        
        KnobCircle(radius: knobRadius * 2, padding: 6)
            .offset(y: -radius)
            .rotationEffect(Angle.degrees(Double(secondaryDirectionValue)))
            .shadow(color: Color.black.opacity(0.2), radius: 3, x: -3)
            .gesture(DragGesture(minimumDistance: 0.0)
                        .onChanged({ angleValue in
                            knobSecondaryChange(location: angleValue.location)
                        }))
        
        CompassView(count: 240,
                    longDivider: 15,
                    longTickHeight: self.longTickHeight,
                    tickHeight: self.tickHeight,
                    tickWidth: self.tickWidth,
                    highlightedColorDivider: 30,
                    highlightedColor: .blue,
                    normalColor: .black.opacity(0.2))
            .frame(width: 350, height: 350)
        
        
        CompassNumber(numbers: self.getNumbers(count: 16))
            .frame(width: 310, height: 310)
        
    }//: ZSTACK
    .onAppear(){
        updateInitialValue()
    }
}

private func getNumbers(count: Int) -> [Float] {
    var numbers: [Float] = []
    numbers.append(Float(count) * 30)
    for index in 1..<count {
        numbers.append(Float(index) * 30)
    }
    return numbers
}

private func updateInitialValue(){
    directionValue = minimumValue
    dirAngleValue = CGFloat(directionValue/totalValue) * 360
}


private func knobChange(location: CGPoint) {
    let vector = CGVector(dx: location.x, dy: location.y)
    let angle = atan2(vector.dy - knobRadius, vector.dx - knobRadius) + .pi/2.0
    let fixedAngle = angle < 0.0 ? angle + 2.0 * .pi : angle
    let value = fixedAngle / (2.0 * .pi) * totalValue
    
    if value > minimumValue && value < maximumValue {
        directionValue = value
        dirAngleValue = fixedAngle * 180 / .pi
    }
    
}

private func knobSecondaryChange(location: CGPoint) {
    let vector = CGVector(dx: location.x, dy: location.y)
    let angle = atan2(vector.dy - knobRadius, vector.dx - knobRadius) + .pi/2.0
    let fixedAngle = angle < 0.0 ? angle + 2.0 * .pi : angle
    let value = fixedAngle / (2.0 * .pi) * totalValue
    
    if value > minimumValue && value < maximumValue {
        secondaryDirectionValue = value
        secondaryDirAngleValue = fixedAngle * 180 / .pi
    }
    
  }
}

struct KnobCircle: View {
let radius: CGFloat
let padding: CGFloat

var body: some View {
    ZStack(){
        Circle()
            .fill(Color.init(white: 0.96))
            .frame(width: radius, height: radius)
            .shadow(color: Color.black.opacity(0.1), radius: 10, x: -10, y: 8)
        
        Circle()
            .fill(Color.white)
            .frame(width: radius - padding, height: radius - padding)
    }//: ZSTACK
  }
}

struct CompassView: View {
let count: Int
let longDivider: Int
let longTickHeight: CGFloat
let tickHeight: CGFloat
let tickWidth: CGFloat

let highlightedColorDivider: Int
let highlightedColor: Color
let normalColor: Color

var body: some View {
    ZStack(){
        ForEach(0..<self.count) { index in
            let height = (index % self.longDivider == 0) ? self.longTickHeight : self.tickHeight
            let color = (index % self.highlightedColorDivider == 0) ? self.highlightedColor : self.normalColor
            let degree: Double = Double.pi * 2 / Double(self.count)
            TickShape(tickHeight: height)
                .stroke(lineWidth: self.tickWidth)
                .rotationEffect(.radians(degree * Double(index)))
                .foregroundColor(color)
            
        }
    }//: ZSTACK
}//: VIEW
}

struct TickShape: Shape {
let tickHeight: CGFloat

func path(in rect: CGRect) -> Path {
    var path = Path()
    path.move(to: CGPoint(x: rect.midX, y: rect.minY))
    path.addLine(to: CGPoint(x: rect.midX, y: rect.minY + self.tickHeight))
    return path
}
}


struct CompassNumber: View {
let numbers: [Float]
let direction: [String] = ["N","NE","E","SE","S","SW","W","NW"]

var body: some View {
    ZStack(){
        ForEach(0..<self.direction.count) { index in
            let degree: Double = Double.pi * 2 / Double(self.direction.count)
            let itemDegree = degree * Double(index)
            VStack(){
                Text(self.direction[index])
                    .font(.footnote)
                    .rotationEffect(.radians(-itemDegree))
                    .foregroundColor(.blue)
                Spacer()
            }//: VSTACK
            .rotationEffect(.radians(itemDegree))
        }
    }//: ZSTACK
}
}


extension Double {

func degreesToCompassDirection(degree: Double) -> String {
    
    switch degree {
    case 0..<11.25:
        return "N"
    case 11.25..<33.75:
        return "NNE"
    case 33.75..<56.25:
        return "NE"
    case 56.25..<78.75:
        return "ENE"
    case 78.75..<101.25:
        return "E"
    case 101.25..<123.75:
        return "ESE"
    case 123.75..<146.25:
        return "SE"
    case 146.25..<168.75:
        return "SSE"
    case 168.75..<191.25:
        return "S"
    case 191.25..<213.75:
        return "SSW"
    case 213.75..<236.25:
        return "SW"
    case 236.25..<258.75:
        return "WSW"
    case 258.75..<281.25:
        return "W"
    case 281.25..<303.75:
        return "WNW"
    case 303.75..<326.25:
        return "NW"
    case 326.25..<348.75:
        return "NNW"
    case 348.75..<360:
        return "N"
    default:
        return "ERROR"
    }
}
}

感谢您的帮助。
1个回答

1

我进行了ZStack并显示了背景描边。如果我想在0位置(北)上显示,我只需交换背景和前景颜色。

var body: some View {
ZStack {
    
    ZStack{
        Circle() //Background
            .stroke((directionValue < secondaryDirectionValue) ? Color.white : Color.blue)
            .frame(width: radius * 2, height: radius * 2)
            .rotationEffect(.degrees(-90))
        Circle() //Foreground
            .trim(from: minimumTrimValue(), to: maximumTrimValue())
            .stroke((directionValue < secondaryDirectionValue) ? Color.blue : Color.white)
            .frame(width: radius * 2, height: radius * 2)
            .rotationEffect(.degrees(-90))
    }

你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community

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