带有 SwiftUI 的旋钮

问题描述:

所以我试图用旋钮复制普通的 SwiftUI 滑块功能.我已经对 UI 进行了编码,并且目前可以连接到标准的 SwiftUI 滑块以旋转它.

So I'm attempting to replicate the normal SwiftUI slider functionality with a rotary knob. I've got the UI coded up and functioning currently connected to a standard SwiftUI slider in order to rotate it.

现在我需要添加其余的滑块功能(即 $value、范围、步幅)和触摸功能(即上下左右拖动时旋钮旋转).老实说,我对做到这一点的最佳方式一无所知.非常感谢任何帮助.

Now I need to add the rest of the slider functionality(ie $value, range, stride) and the touch functionality(ie knob rotating when dragging up and down, left and right). And Honestly I'm at a lost on the best way to do this. Any help is greatly appreciated.

以下是主文件,可以在 上找到该项目Github 滑块项目

Below is the main file and the project can be found here on 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)
    }
}

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

Here's a version that I use in one of my projects.

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

When the drag starts, it sets an initial value (stored in startDragValue). This is because you always want the modification of the value to be based on what the knob value was when you started.

然后,我决定只更改基于 y 轴的值.理由是这样的:可以根据从 x1, y1 到 x2, y2 的绝对距离进行更改,但是在尝试获得负值时遇到了问题.例如,右上象限整体增加可能是有道理的——但是左上象限呢——它会导致正变化(因为 y 轴变化)还是负变化(因为 x轴)?

Then, I've made the decision to only change values based on the y axis. The rational is this: one could change based on absolute distance from x1, y1 to x2, y2, but you run into problems with trying to get negative values. For instance, it would probably make sense that the upper right quadrant would be an overall increase -- but what about the upper left quadrant -- would it lead to positive change (because of the y axis change) or negative (because of the x axis)?

如果你决定走 x,y 变化的路线,这个方法仍然会让你设置.

If you decide to go the route of x,y change, this method will still get you set up.

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 的值,但您显然也可以根据自己的喜好更改这些值.

My slider bases the values on 100pt up or down from the control, but you can obviously change those to your preferences as well.

就范围而言,我建议始终将旋钮从 0.0 调整到 1.0,然后再对值进行插值.

In terms of range, I'd suggest always having the knob go from 0.0 to 1.0 and then interpolating the values afterwards.