如何在SwiftUI父View拖拽时识别子View并获取其Date值?
解决方案
方法一:让子视图处理拖拽手势并回调父视图
这种方案无需父视图的全局拖拽手势,直接给每个GrassViewCell添加拖拽识别逻辑,通过闭包将子视图的date传递给父视图更新text变量,避免父视图手势覆盖子视图响应的问题。
1. 修改GrassViewCell结构
给子视图添加拖拽手势,设置最小识别距离为0(捕捉拖拽进入事件),并通过高优先级手势确保子视图优先响应:
struct GrassViewCell: View { let date: String // 按实际类型调整,比如Date转String let color: Color let inputLevel: Int let onDragEnter: (String) -> Void // 定义回调闭包 var body: some View { // 替换成你原有GrassViewCell的视图内容,比如带颜色的方块 Rectangle() .fill(color) .frame(width: 22, height: 22) .gesture( DragGesture(minimumDistance: 0) .onChanged { _ in onDragEnter(date) // 拖拽进入时传递当前cell的date } ) .highPriorityGesture( DragGesture(minimumDistance: 0) // 确保子视图手势优先被捕获 ) } }
2. 修改父视图GrassView
移除父视图的全局拖拽手势,直接通过子视图的回调更新text:
public struct GrassView: some View { @State private var text: String = "" private let row: Int private let col: Int private let today: Date private let blockColor: Color private let formatter: DateFormatter = { let fmt = DateFormatter() fmt.dateStyle = .medium return fmt }() // 补充初始化参数,按实际需求调整 init(row: Int, col: Int, today: Date, blockColor: Color) { self.row = row self.col = col self.today = today self.blockColor = blockColor } public var body: some View { VStack(alignment: .leading){ Text(text) .font(.headline) ForEach(0..<row, id: \.self){ row in HStack(spacing: 4){ ForEach(0..<col, id: \.self){ col in GrassViewCell( date: getDate(rowcol: [row, col], today: today), color: blockColor, inputLevel: getLevel(rowcol: [row, col]), onDragEnter: { targetDate in self.text = targetDate } ) } } } } .padding() } // 实现你的日期计算逻辑 private func getDate(rowcol: [Int], today: Date) -> String { let daysOffset = -(rowcol[0] * col + rowcol[1]) let targetDate = today.addingTimeInterval(TimeInterval(daysOffset * 86400)) return formatter.string(from: targetDate) } // 实现你的等级计算逻辑 private func getLevel(rowcol: [Int]) -> Int { // 按实际业务返回对应等级 1 } }
方法二:父视图通过手势位置识别子视图
如果必须保留父视图的全局拖拽手势,可以通过手势坐标计算当前拖拽点对应的子视图,进而获取date。这种方案需要固定子视图尺寸,避免布局变化导致坐标计算错误。
修改父视图GrassView
使用GeometryReader获取父视图坐标空间,在拖拽时计算目标行列:
public struct GrassView: some View { @State private var text: String = "" private let row: Int private let col: Int private let today: Date private let blockColor: Color private let cellSize: CGFloat = 22 private let cellSpacing: CGFloat = 4 private let formatter: DateFormatter = { let fmt = DateFormatter() fmt.dateStyle = .medium return fmt }() init(row: Int, col: Int, today: Date, blockColor: Color) { self.row = row self.col = col self.today = today self.blockColor = blockColor } public var body: some View { GeometryReader { geo in VStack(alignment: .leading, spacing: 4){ Text(text) .font(.headline) .frame(height: 24) // 固定Text高度,方便计算偏移 ForEach(0..<row, id: \.self){ row in HStack(spacing: cellSpacing){ ForEach(0..<col, id: \.self){ col in GrassViewCell( date: getDate(rowcol: [row, col], today: today), color: blockColor, inputLevel: getLevel(rowcol: [row, col]) ) .frame(width: cellSize, height: cellSize) } } } } .padding() .gesture( DragGesture(minimumDistance: 0) .onChanged { gesture in let location = gesture.location // 计算Y轴偏移:减去padding和Text高度 let yPos = location.y - 16 - 24 guard yPos >= 0 else { return } // 计算目标行 let targetRow = Int(yPos / (cellSize + cellSpacing)) guard targetRow < row else { return } // 计算X轴偏移:减去padding let xPos = location.x - 16 guard xPos >= 0 else { return } // 计算目标列 let targetCol = Int(xPos / (cellSize + cellSpacing)) guard targetCol < col else { return } // 更新text为对应子视图的date text = getDate(rowcol: [targetRow, targetCol], today: today) } .onEnded{ _ in text = formatter.string(from: today) } ) } } private func getDate(rowcol: [Int], today: Date) -> String { let daysOffset = -(rowcol[0] * col + rowcol[1]) let targetDate = today.addingTimeInterval(TimeInterval(daysOffset * 86400)) return formatter.string(from: targetDate) } private func getLevel(rowcol: [Int]) -> Int { 1 } }
方案对比
- 方法一:逻辑简洁,无需手动计算坐标,适配布局变化,推荐使用。
- 方法二:适合必须保留父视图全局手势的场景,但依赖固定布局,维护成本较高。
内容的提问来源于stack exchange,提问作者MinSeong Kim
相关产品推荐
相关产品推荐

