带 SwiftUI 的旋转旋钮

Rotary Knob with SwiftUI

所以我试图用旋钮复制正常的 SwiftUI 滑块功能。我已经将 UI 编码并运行,目前已连接到标准 SwiftUI 滑块以旋转它。

现在我需要添加其余的滑块功能(即 $value、range、stride)和触摸功能(即旋钮在上下、左右拖动时旋转)。老实说,我对执行此操作的最佳方法一无所知。非常感谢任何帮助。

下面是主要文件,项目可以在 Github Slider Project

上找到
//
//  FKKnob.swift
//
//  Created by Brent Brinkley on 3/7/21.
//

import SwiftUI

struct FKKnob: View {
    // Set the color for outer ring and inner dash
    let color: Color
    
    // Minimum appearance value:
    let circleMin: CGFloat = 0.0
    
    // Maximum appearance value
    let circleMax: CGFloat = 0.9
    
    // Because our circle is missing a chunk of degrees we have to account
    // for this adjustment
    let circOffsetAmnt: CGFloat = 1 / 0.09
    
    // Offset needed to align knob properly
    let knobOffset: Angle = .degrees(110)
    
    // calculate the our circle's mid point
    var cirMidPoint: CGFloat {
        0.4 * circOffsetAmnt
    }
    
    // User modfiable control value
    @State var value: CGFloat = 0.0
    
    var body: some View {
        
        VStack {
            ZStack {
                
                // MARK: - Knob with dashline
                
                Knob(color: color)
                    .rotationEffect(
                        // Currently controlled by slider
                        .degrees(max(0, Double(360 * value )))
                    )
                    .gesture(DragGesture(minimumDistance: 0)
                                .onChanged({ value  in
                                    
                                    // Need help here setting amount based on x and y touch drag
                                    
                                }))
                
                // MARK: - Greyed Out Ring
                
                Circle()
                    .trim(from: circleMin, to: circleMax)
                    .stroke(Color.gray ,style: StrokeStyle(lineWidth: 6, lineCap: .round, dash: [0.5,8], dashPhase: 20))
                    .frame(width: 100, height: 100)
                
                // MARK: - Colored ring inidicating change
                
                Circle()
                    .trim(from: circleMin, to: value)
                    .stroke(color ,style: StrokeStyle(lineWidth: 6, lineCap: .round, dash: [0.5,8], dashPhase: 20))
                    .frame(width: 100, height: 100)
                
            }
            .rotationEffect(knobOffset)
            
            Text("\(value * circOffsetAmnt, specifier: "%.0f")")
            
            Slider(value: $value, in: circleMin...circleMax)
                .frame(width: 300)
                .accentColor(.orange)
        }
    }
}

struct DashedCircle_Previews: PreviewProvider {
    static var previews: some View {
        FKKnob(color: Color.orange)
    }
}

这是我在我的一个项目中使用的版本。

当拖动开始时,它会设置一个初始值(存储在startDragValue中)。这是因为您总是希望根据开始时旋钮的值修改该值。

然后,我决定只更改基于 y 轴的值。理性是这样的:一个 可以 根据从 x1, y1 到 x2, y2 的绝对距离而改变,但是你 运行 遇到了试图获得负值的问题。例如,右上象限可能是整体增长——但左上象限呢——它会导致积极变化(因为 y 轴变化)还是消极​​变化(因为 x轴)?

如果您决定改变 x,y 的路线,此方法仍会帮助您进行设置。

struct ContentView: View {
    @State var value : Double = 0.0
    @State private var startDragValue : Double = -1.0
    var body: some View {
        Text("Knob \(value)")
            .gesture(DragGesture(minimumDistance: 0)
            .onEnded({ _ in
                startDragValue = -1.0
            })
            .onChanged { dragValue in
                let diff =  dragValue.startLocation.y - dragValue.location.y
                if startDragValue == -1 {
                    startDragValue = value
                }
                let newValue = startDragValue + Double(diff)
                value = newValue < 0 ? 0 : newValue > 100 ? 100 : newValue
            })
    }
}

我的滑块基于控件向上或向下 100pt 的值,但您显然也可以根据自己的喜好更改这些值。

就范围而言,我建议始终让旋钮从 0.0 变为 1.0,然后再对值进行插值。

我发现 SwiftUI RotationGesture() 可以完美地解决这个问题,因为它允许直观地旋转旋钮,而没有上面答案中列出的缺点。

唯一独特的缺点是 可能 用户已经习惯了单指旋钮操作(尤其是在 iPhone 上),单指拖动手势可能是如果您的应用程序中有小旋钮,两根手指很难触及,那就去吧。

话虽如此,您始终可以附加多个手势并获得两者。无论如何,感谢 @jnpdx 从您使用 startRotation 停止的地方开始的关于未来手势的一些信息,事不宜迟:

import SwiftUI


struct KnobView: View {
    @State private var rotation: Double = 0.0
    @State private var startRotation: Double = 0.0
    
    var body: some View {
        Knob()
            .rotationEffect(.degrees(rotation))
            .gesture(
                RotationGesture()
                    .onChanged({ angle in
                        if startRotation == 0 {
                            startRotation = rotation
                        }
                        rotation = startRotation + angle.degrees
                    })
                    .onEnded({ _ in
                        startRotation = 0
                    })
            )
    }
}

// convience Knob to checkout this Stack Overflow answer
// that can be later replaced with your own
struct Knob: View {
    var body: some View {
        RoundedRectangle(cornerRadius: 12)
            .frame(width: 200, height: 200)
    }
}

struct KnobView_Previews: PreviewProvider {
    static var previews: some View {
        KnobView()
    }
}