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

结构体引用视图: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:06:07