结构体引用视图:SwiftUI中结构体与视图关联及邻接检测咨询
最优方案:关联Hexagon结构体与View布局,实现邻接/重叠检测
你的核心需求是让Model中的Hexagon结构体和对应的HexagonView布局信息绑定,这样遍历结构体数组就能直接计算视图间的邻接或重叠关系。结合你当前的手动布局场景,我整理了两种适配SwiftUI的最优实现思路:
方案1:给Hexagon结构体添加布局属性(简单直接,适合固定手动布局)
既然你已经通过getFrameFor手动计算每个六边形的frame,最直接的方式就是把布局数据存在Hexagon模型里,让Model层直接持有视图的位置信息:
步骤1:修改Hexagon结构体
给你的Hexagon模型补充布局相关属性:
struct Hexagon: Identifiable { let id: Int var isSelected: Bool // 添加存储布局的属性 var frame: CGRect = .zero // 快速获取中心点 var center: CGPoint { CGPoint(x: frame.midX, y: frame.midY) } // 其他原有属性... }
步骤2:计算布局时同步更新Model
在生成HexagonView的循环中,把计算出的frame赋值给对应的Hexagon实例(注意用绑定来修改Model):
// 改用绑定的ForEach,才能修改Model中的属性 ForEach($viewModel.hexagons) { $hexagon in let hexagonFrame = getFrameFor(hexagon: hexagon, minWidth: smallSide) // 同步更新Model里的frame hexagon.frame = hexagonFrame ZStack() { // 你原有的HexagonView和Text交互代码... } .position(x: hexagonFrame.origin.x, y: scrollViewGeometry.size.height - (hexagonFrame.size.height/2) - hexagonFrame.origin.y) .frame(width: hexagonFrame.size.width, height: hexagonFrame.size.height, alignment:.center) }
步骤3:实现邻接/重叠检测逻辑
现在可以在ViewModel里直接遍历hexagons数组,基于存储的frame计算关系:
extension YourViewModel { // 检测两个六边形是否重叠 func isOverlapping(_ hexA: Hexagon, _ hexB: Hexagon) -> Bool { return hexA.frame.intersects(hexB.frame) } // 检测两个六边形是否邻接(根据六边形几何特性调整阈值) func isAdjacent(_ hexA: Hexagon, _ hexB: Hexagon) -> Bool { let distance = sqrt(pow(hexA.center.x - hexB.center.x, 2) + pow(hexA.center.y - hexB.center.y, 2)) // 假设smallSide是六边形短边长,邻接中心距离约为2*smallSide(根据你的布局规则微调) let adjacentThreshold = smallSide * 2.0 // 加小误差值避免浮点精度问题 return distance <= adjacentThreshold + 1.0 } // 遍历所有六边形,批量检测邻接/重叠 func checkAllLayoutRelations() { for i in 0..<hexagons.count { let currentHex = hexagons[i] for j in i+1..<hexagons.count { let compareHex = hexagons[j] if isOverlapping(currentHex, compareHex) { print("Hex \(currentHex.id+1) overlaps with \(compareHex.id+1)") } if isAdjacent(currentHex, compareHex) { print("Hex \(currentHex.id+1) is adjacent to \(compareHex.id+1)") } } } } }
方案2:使用PreferenceKey收集View布局信息(符合SwiftUI数据流规范)
如果想严格遵循MVVM职责分离(不让Model持有View层的布局数据),可以用SwiftUI的PreferenceKey机制,从View层收集实际布局信息并传递给ViewModel:
步骤1:定义PreferenceKey
// 用来收集每个Hexagon的frame,key是Hexagon的id struct HexagonFramePreferenceKey: PreferenceKey { static var defaultValue: [Int: CGRect] = [:] static func reduce(value: inout [Int: CGRect], nextValue: () -> [Int: CGRect]) { value.merge(nextValue()) { $1 } } }
步骤2:在HexagonView中注入布局信息
在你的ZStack中添加backgroundPreferenceValue,把当前View的frame传递出去:
ZStack() { // 你原有的HexagonView和Text交互代码... } .position(...) .frame(...) .background( GeometryReader { geo in Color.clear .preference( key: HexagonFramePreferenceKey.self, // 用scrollView坐标系确保所有frame的参考系统一 value: [hexagon.id: geo.frame(in: .scrollView)] ) } )
步骤3:在父View中接收并同步到ViewModel
在ScrollView上层添加onPreferenceChange,收集所有Hexagon的frame并传给ViewModel:
ScrollView(.horizontal, showsIndicators: true) { // 你原有的GeometryReader和ForEach代码... } .onPreferenceChange(HexagonFramePreferenceKey.self) { frames in viewModel.updateHexagonFrames(frames: frames) }
步骤4:在ViewModel中处理布局数据
给ViewModel添加存储和检测逻辑:
class YourViewModel: ObservableObject { @Published var hexagons: [Hexagon] = [] private var hexagonFrames: [Int: CGRect] = [:] func updateHexagonFrames(frames: [Int: CGRect]) { self.hexagonFrames = frames // 按需触发检测逻辑 checkAllLayoutRelations() } // 检测逻辑和方案1类似,只是从hexagonFrames中取frame func isOverlapping(_ hexA: Hexagon, _ hexB: Hexagon) -> Bool { guard let frameA = hexagonFrames[hexA.id], let frameB = hexagonFrames[hexB.id] else { return false } return frameA.intersects(frameB) } // ...其他检测方法 }
方案选择建议
- 如果你的布局逻辑固定,手动计算的frame和实际View完全匹配,方案1更简单高效,代码量少,适合快速落地。
- 如果你的View存在动态布局(比如自适应尺寸、字体变化),或者想严格遵循MVVM架构,方案2更符合SwiftUI的设计理念,数据流向更清晰。
另外,针对六边形邻接检测,如果你是蜂窝状布局,可以给Hexagon添加row、column属性,通过行列关系判断邻接(比坐标计算更高效);如果是直线排列,直接用坐标距离判断即可。
内容的提问来源于stack exchange,提问作者HillInHarwich
相关产品推荐
相关产品推荐

