带 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()
}
}
所以我试图用旋钮复制正常的 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()
}
}