SwiftUI拖拽圆形时如何获取无重复的0-360°连续角度?
问题解决:拖拽圆形获取0-360°连续无重复角度
问题背景
拖拽圆形控件时,获取的角度初始会减小,拖拽至约四分之一位置后又增大,出现重复角度值,需要修正为0-360°范围内连续无重复的角度计算逻辑。
原代码
struct TabWheelButton: View { var circleWidth = UIScreen.main.bounds.width / 2.5 var text: String var angle: Double var index: Int @Binding var current: Int var body: some View { Text(text) .foregroundColor(.black) .rotationEffect(.init(degrees: -angle)) .padding() .frame(width: 60, height: 60) .background(current == index ? Color.green : Color.blue) .clipShape(RoundedRectangle(cornerRadius: 80, style: .continuous)) .offset(x: circleWidth / 2) .rotationEffect(.init(degrees: angle)) } } struct CircularDisplayForTab: View { @Binding var current: Int func onChangeOfTabWheel(value: DragGesture.Value) { let vector = CGVector(dx: value.location.x, dy: value.location.y) let radians = atan2(vector.dy, vector.dx) var angle = 180 - (radians * 180 / .pi) print(angle) if angle > 0 && value.translation.width != 0 { if angle > 91 && angle < 96 { current = 1 } else if angle > 96 && angle < 99 { current = 2 } else if angle > 99 && angle < 106 { current = 3 } else if angle > 246 && angle < 250 { current = 4 } } } var body: some View { let circleWidth = UIScreen.main.bounds.width / 2.5 let circleHeight = UIScreen.main.bounds.height Circle() .trim(from: 0.24, to: 1.0) .stroke(Color.blue.opacity(1.0), lineWidth: 60) .rotationEffect(Angle(degrees: 20)) .frame(width: circleWidth, height: circleHeight) .gesture(DragGesture(minimumDistance: 0).onChanged(onChangeOfTabWheel(value:))) TabWheelButton(text: "H", angle: 145, index: 1, current: $current) TabWheelButton(text: "B", angle: 215, index: 2, current: $current) TabWheelButton(text: "P", angle: 285, index: 3, current: $current) TabWheelButton(text: "S", angle: 355, index: 4, current: $current) } } struct ContentView: View { @State private var showAdditionalTabFeatures = false @State private var current: Int = 1 var body: some View { VStack { Text("Press me") // show circle .onTapGesture { showAdditionalTabFeatures = true } } }
核心问题分析
- 坐标原点错误:直接使用屏幕左上角为原点的
value.location计算向量,而非以圆形控件中心为极坐标原点,导致角度基准偏差。 - 弧度转角度逻辑错误:
atan2(vector.dy, vector.dx)返回范围为-π到π,原转换方式会导致角度反向、重复,且未适配SwiftUI y轴向下的坐标系。
修正后的代码
struct TabWheelButton: View { var circleWidth = UIScreen.main.bounds.width / 2.5 var text: String var angle: Double var index: Int @Binding var current: Int var body: some View { Text(text) .foregroundColor(.black) .rotationEffect(.init(degrees: -angle)) .padding() .frame(width: 60, height: 60) .background(current == index ? Color.green : Color.blue) .clipShape(RoundedRectangle(cornerRadius: 80, style: .continuous)) .offset(x: circleWidth / 2) .rotationEffect(.init(degrees: angle)) } } struct CircularDisplayForTab: View { @Binding var current: Int @State private var circleCenter: CGPoint = .zero func onChangeOfTabWheel(value: DragGesture.Value) { // 计算拖拽点相对于圆形中心的偏移向量 let dx = value.location.x - circleCenter.x let dy = value.location.y - circleCenter.y // 适配SwiftUI坐标系(y轴向下,取反dy匹配数学极坐标规则) let radians = atan2(-dy, dx) // 转换为0-360°的连续角度 var angle = radians * 180 / .pi if angle < 0 { angle += 360 } print("当前角度:\(angle)") // 根据修正后的角度区间切换current值(可按需调整) if angle >= 130 && angle < 160 { current = 1 } else if angle >= 200 && angle < 230 { current = 2 } else if angle >= 270 && angle < 300 { current = 3 } else if angle >= 340 || angle < 20 { current = 4 } } var body: some View { let circleWidth = UIScreen.main.bounds.width / 2.5 let circleHeight = UIScreen.main.bounds.height GeometryReader { proxy in ZStack { Circle() .trim(from: 0.24, to: 1.0) .stroke(Color.blue.opacity(1.0), lineWidth: 60) .rotationEffect(Angle(degrees: 20)) .frame(width: circleWidth, height: circleHeight) .onAppear { // 获取圆形控件的全局中心坐标 let circleFrame = proxy.frame(in: .global) circleCenter = CGPoint(x: circleFrame.midX, y: circleFrame.midY) } .gesture(DragGesture(minimumDistance: 0).onChanged(onChangeOfTabWheel(value:))) TabWheelButton(text: "H", angle: 145, index: 1, current: $current) TabWheelButton(text: "B", angle: 215, index: 2, current: $current) TabWheelButton(text: "P", angle: 285, index: 3, current: $current) TabWheelButton(text: "S", angle: 355, index: 4, current: $current) } } } } struct ContentView: View { @State private var showAdditionalTabFeatures = false @State private var current: Int = 1 var body: some View { VStack { if showAdditionalTabFeatures { CircularDisplayForTab(current: $current) } else { Text("Press me") // show circle .onTapGesture { showAdditionalTabFeatures = true } } } } }
关键修改点
- 获取圆形中心坐标:通过
GeometryReader获取圆形控件的全局坐标,以此作为极坐标原点,确保向量计算准确。 - 修正角度计算逻辑:
- 计算拖拽点相对于圆形中心的偏移量
dx、dy。 - 对
dy取反,适配SwiftUI y轴向下的坐标系,使角度符合数学极坐标规则(0°在右侧,顺时针递增)。 - 将
atan2返回的-π~π弧度转换为0~360°,负数角度直接加360°修正。
- 计算拖拽点相对于圆形中心的偏移量
- 调整角度区间:根据修正后的0-360°角度,重新匹配
current值的切换区间,确保与按钮实际角度对应。
内容的提问来源于stack exchange,提问作者Swink
相关产品推荐
相关产品推荐

