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

如何在自定义布局中查找子视图位置,判断离鼠标最近的App图标

问题描述

我实现了一个带有自定义布局WheelLayout的View,运行效果良好,简化代码如下:

struct SomeContent: View {
    ...
    var body: some View {
        WheelLayout {
            ForEach(targets, id: \.self) {
                app in
                VStack {
                    app.icon != nil ? Image(nsImage: app.icon!) : Image(systemName: "rectangle.dashed")
                    Text(app.name).bold()
                }
            }
        }
    }
}

现在我想知道哪个app图标离鼠标光标最近。我认为解决方案的一部分是将上述VStack替换为持有app引用的自定义View类,但在包含SomeContent的窗口代码中,如何查询该类的所有子视图以检查它们的位置?


解决方案

1. 用自定义View封装并自动上报位置

先把每个app的视图封装成自定义View,通过GeometryReader获取自身在全局坐标系的位置和尺寸,同时监听鼠标位置计算距离:

struct AppItemView: View {
    let app: AppModel // 替换为你的实际app数据模型
    @Binding var nearestApp: AppModel?
    @Binding var nearestDistance: CGFloat
    @State private var globalFrame: CGRect = .zero

    var body: some View {
        VStack {
            app.icon != nil ? Image(nsImage: app.icon!) : Image(systemName: "rectangle.dashed")
            Text(app.name).bold()
        }
        .background(
            GeometryReader { geo in
                Color.clear
                    .onAppear { globalFrame = geo.frame(in: .global) }
                    .onChange(of: geo.frame(in: .global)) { globalFrame = $0 }
            }
        )
        .onReceive(MouseMonitor.shared.positionPublisher) { mousePos in
            let currentDistance = hypot(mousePos.x - globalFrame.midX, mousePos.y - globalFrame.midY)
            if currentDistance < nearestDistance {
                nearestDistance = currentDistance
                nearestApp = app
            }
        }
    }
}

2. 全局监听鼠标位置

实现一个全局鼠标位置监听工具,通过NSEvent捕获鼠标移动事件:

class MouseMonitor {
    static let shared = MouseMonitor()
    let positionPublisher = PassthroughSubject<CGPoint, Never>()
    
    private init() {
        NSEvent.addLocalMonitorForEvents(matching: .mouseMoved) { [weak self] event in
            self?.positionPublisher.send(event.locationInWindow)
            return event
        }
    }
}

在App启动时自动初始化监听(比如在App结构体的init里调用_ = MouseMonitor.shared)。

3. 父视图管理全局状态

修改SomeContent,维护最近app和对应距离的状态,传递给每个子视图:

struct SomeContent: View {
    let targets: [AppModel]
    @State private var nearestApp: AppModel?
    @State private var nearestDistance: CGFloat = .greatestFiniteMagnitude

    var body: some View {
        WheelLayout {
            ForEach(targets, id: \.self) { app in
                AppItemView(
                    app: app,
                    nearestApp: $nearestApp,
                    nearestDistance: $nearestDistance
                )
            }
        }
    }
}

4. 更高效的方案:在自定义布局中直接计算

如果你的WheelLayout是自定义Layout协议实现,可以在布局过程中直接记录每个子视图的位置,统一计算最近app,避免每个子视图单独监听:

struct WheelLayout: Layout {
    let targets: [AppModel]
    @Binding var nearestApp: AppModel?
    @Binding var mousePos: CGPoint
    private var itemFrames: [AppModel: CGRect] = [:]

    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
        // 你的原有布局尺寸计算逻辑
    }

    func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
        itemFrames.removeAll()
        // 遍历子视图并布局,同时记录每个app对应的全局frame
        for (index, subview) in subviews.enumerated() {
            let app = targets[index]
            let itemFrame = // 计算当前子视图的全局位置和尺寸
            subview.place(at: itemFrame.origin, proposal: proposal)
            itemFrames[app] = itemFrame
        }
        // 布局完成后计算最近的app
        updateNearestApp()
    }

    private func updateNearestApp() {
        var closestApp: AppModel?
        var minDistance: CGFloat = .greatestFiniteMagnitude
        for (app, frame) in itemFrames {
            let distance = hypot(mousePos.x - frame.midX, mousePos.y - frame.midY)
            if distance < minDistance {
                minDistance = distance
                closestApp = app
            }
        }
        nearestApp = closestApp
    }
}

内容的提问来源于stack exchange,提问作者viraptor

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 06:05:14