You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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
                }
        }
}

核心问题分析

  1. 坐标原点错误:直接使用屏幕左上角为原点的value.location计算向量,而非以圆形控件中心为极坐标原点,导致角度基准偏差。
  2. 弧度转角度逻辑错误: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获取圆形控件的全局坐标,以此作为极坐标原点,确保向量计算准确。
  • 修正角度计算逻辑:
    1. 计算拖拽点相对于圆形中心的偏移量dx、dy。
    2. 对dy取反,适配SwiftUI y轴向下的坐标系,使角度符合数学极坐标规则(0°在右侧,顺时针递增)。
    3. 将atan2返回的-π~π弧度转换为0~360°,负数角度直接加360°修正。
  • 调整角度区间:根据修正后的0-360°角度,重新匹配current值的切换区间,确保与按钮实际角度对应。

内容的提问来源于stack exchange,提问作者Swink

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 14:37:08