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

SwiftUI中实现拖拽连接圆形(类安卓图案锁)的问题求助

SwiftUI 安卓图案锁屏拖拽连线问题修复

问题分析

你的代码存在以下几个关键问题导致无法正确连线:

  • onAppear中错误覆盖了初始正确起点的索引,导致拖拽起点判断逻辑失效
  • nextCorrectCirclePosition变量未正确初始化和更新,判断拖拽终点时使用了未赋值的变量
  • 拖拽点计算错误:用起始点加平移量的方式会导致坐标偏移,应该直接使用手势提供的当前位置
  • 路径更新逻辑混乱:getNextCircle固定取路径数组的第二个元素,没有根据当前进度动态获取下一个目标点
  • 依赖sortedCoordinates获取位置,但该数组在onAppear时可能还未初始化完成

修复后的完整代码

import SwiftUI

struct Line: Hashable {
    var start: CGPoint
    var end: CGPoint
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(start.x)
        hasher.combine(start.y)
        hasher.combine(end.x)
        hasher.combine(end.y)
    }
    
    static func ==(lhs: Line, rhs: Line) -> Bool {
        return lhs.start == rhs.start && lhs.end == rhs.end
    }
}

struct CirclePositionsKey: PreferenceKey {
    static var defaultValue: [Int: CGPoint] { [:] }
    
    static func reduce(value: inout [Int : CGPoint], nextValue: () -> [Int : CGPoint]) {
        value.merge(nextValue(), uniquingKeysWith: { $1 })
    }
}

struct LineDragGesture: View {
    
    @State private var lines: [Line] = [] 
    @State private var currentDragPoint: CGPoint? = nil
    @State private var selectedCircleIndex: Int? 
    
    @State private var correctPaths: [Int] = [0, 3, 6] // 正确路径序列
    @State private var currentPathStep: Int = 0 // 当前进度,从0开始
    
    @State private var circlePositions: [Int: CGPoint] = [:] 
    
    let columns: [GridItem] = [
        GridItem(.flexible()),
        GridItem(.flexible()),
        GridItem(.flexible())
    ]
    
    // 当前需要拖拽的起点索引
    private var currentStartIndex: Int? {
        guard currentPathStep < correctPaths.count else { return nil }
        return correctPaths[currentPathStep]
    }
    
    // 下一个需要到达的目标索引
    private var nextTargetIndex: Int? {
        guard currentPathStep + 1 < correctPaths.count else { return nil }
        return correctPaths[currentPathStep + 1]
    }
    
    // 当前起点的位置
    private var currentStartPosition: CGPoint? {
        guard let index = currentStartIndex else { return nil }
        return circlePositions[index]
    }
    
    // 下一个目标的位置
    private var nextTargetPosition: CGPoint? {
        guard let index = nextTargetIndex else { return nil }
        return circlePositions[index]
    }
    
    var body: some View {
        ZStack {
            // 绘制已完成的连线
            ForEach(lines, id: \.self) { line in
                Path { path in
                    path.move(to: line.start)
                    path.addLine(to: line.end)
                }
                .stroke(Color.black, lineWidth: 6)
            }
            
            // 绘制拖拽中的临时连线
            if let currentDragPoint = currentDragPoint, let startPos = currentStartPosition {
                Path { path in
                    path.move(to: startPos)
                    path.addLine(to: currentDragPoint)
                }
                .stroke(Color.black, lineWidth: 6)
            }
            
            LazyVGrid(columns: columns, spacing: 20) {
                ForEach(0..<9) { index in
                    GeometryReader { geo in
                        let center = geo.frame(in: .named("GridContainer")).center
                        Circle()
                            .fill(currentStartIndex == index ? Color.blue : Color.gray)
                            .preference(key: CirclePositionsKey.self, value: [index: center])
                            .gesture(
                                DragGesture(coordinateSpace: .named("GridContainer"))
                                    .onChanged { value in
                                        // 只有从当前正确起点开始拖拽才生效
                                        if selectedCircleIndex == nil && index == currentStartIndex {
                                            selectedCircleIndex = index
                                        }
                                        
                                        // 如果是有效拖拽,更新当前拖拽点
                                        if selectedCircleIndex != nil {
                                            currentDragPoint = value.location
                                        }
                                    }
                                    .onEnded { value in
                                        guard let startIndex = selectedCircleIndex,
                                              startIndex == currentStartIndex,
                                              let targetPos = nextTargetPosition,
                                              distance(from: value.location, to: targetPos) <= 30 else {
                                            // 拖拽无效,重置状态
                                            currentDragPoint = nil
                                            selectedCircleIndex = nil
                                            return
                                        }
                                        
                                        // 添加正确连线
                                        if let startPos = currentStartPosition {
                                            lines.append(Line(start: startPos, end: targetPos))
                                        }
                                        
                                        // 更新进度
                                        currentPathStep += 1
                                        
                                        // 重置拖拽状态
                                        currentDragPoint = nil
                                        selectedCircleIndex = nil
                                    }
                            )
                    }
                    .frame(width: 50, height: 50)
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                }
            }
        }
        .onPreferenceChange(CirclePositionsKey.self) { value in
            circlePositions = value
        }
        .coordinateSpace(name: "GridContainer")
        .padding()
    }
    
    // 计算两点间距离
    private func distance(from: CGPoint, to: CGPoint) -> CGFloat {
        sqrt(pow(from.x - to.x, 2) + pow(from.y - to.y, 2))
    }
}

#Preview {
    LineDragGesture()
}

关键修复点说明

  • 修正拖拽点计算:使用DragGesture(coordinateSpace: .named("GridContainer"))直接获取相对于网格容器的位置,避免坐标偏移
  • 重构路径进度管理:用currentPathStep跟踪当前完成的步骤,动态计算当前起点和下一个目标点
  • 优化起点判断:只有从当前正确起点开始的拖拽才会触发连线逻辑
  • 修复初始化逻辑:移除了混乱的correctCircleIndex等冗余变量,改用计算属性动态获取位置
  • 增加视觉反馈:当前正确起点用蓝色填充,方便用户识别
  • 调整距离阈值:将判断拖拽是否到达目标的阈值从25调整为30,提升容错性

内容的提问来源于stack exchange,提问作者Jeevan Daniel Mahtani

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 22:18:16