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
相关产品推荐
相关产品推荐

