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

基于macOS的SwiftUI与Xcode:获取CGImage上的鼠标图像坐标

解决SwiftUI中鼠标悬停获取图像原始坐标的问题

需求说明

在SwiftUI的CGImageView中显示已缩放为UInt8格式的240×320灰度图像,需要在鼠标悬停时获取相对于图像原点的坐标,以此读取原始UInt16数据对应位置的值。目前已实现鼠标悬停切换光标,但无法获取目标坐标。

解决步骤

  • 利用onContinuousHover的Phase参数获取鼠标相对当前View的位置
  • 通过嵌套GeometryReader获取图像实际显示的区域(处理.aspectRatio(contentMode: .fit)带来的留白偏移)
  • 计算缩放比例,将鼠标相对坐标转换为图像原始坐标系下的坐标

修改后的完整代码

struct CGImageView: View {
    @Binding var filename: String?
    @Binding var minMax: [UInt16]
    
    @State private var opacity = 0.0
    // 存储转换后的图像原始坐标
    @State private var imageCoordinate: CGPoint?
    
    var body: some View {
        // 建议将数据处理移到异步任务或ViewModel中,避免body重复计算
        let data = importAndScaleImageData(filename!)
        let grayCGImageRef = createCGImageFromUint8(data, height: 240, width: 320)
        let originalSize = CGSize(width: grayCGImageRef.width, height: grayCGImageRef.height)
        
        HStack {
            GeometryReader { containerGeo in
                Image(decorative: grayCGImageRef, scale: 1.0)
                    .aspectRatio(contentMode: .fit)
                    .border(Color.white)
                    .overlay(
                        // 嵌套GeometryReader获取图像实际显示的frame
                        GeometryReader { imageGeo in
                            Color.clear
                                .onContinuousHover { phase in
                                    switch phase {
                                    case .active(let location):
                                        // 计算图像在容器中的偏移量与缩放比例
                                        let imageFrame = imageGeo.frame(in: .parent)
                                        let scale = min(imageFrame.width / originalSize.width, imageFrame.height / originalSize.height)
                                        let adjustedX = (location.x - imageFrame.minX) / scale
                                        let adjustedY = (location.y - imageFrame.minY) / scale
                                        
                                        // 过滤图像范围外的坐标
                                        if adjustedX >= 0 && adjustedX < originalSize.width && adjustedY >= 0 && adjustedY < originalSize.height {
                                            imageCoordinate = CGPoint(x: adjustedX, y: adjustedY)
                                            // 此处可通过imageCoordinate读取原始UInt16数据
                                            // let originalValue = originalUInt16Data[Int(adjustedY) * 320 + Int(adjustedX)]
                                        } else {
                                            imageCoordinate = nil
                                        }
                                    case .ended:
                                        imageCoordinate = nil
                                        NSCursor.pop()
                                    }
                                }
                                .onHover { isHovering in
                                    if isHovering {
                                        NSCursor.crosshair.push()
                                    } else {
                                        NSCursor.pop()
                                    }
                                }
                        }
                    )
            }
            .frame(width: CGFloat(grayCGImageRef.width), height: CGFloat(grayCGImageRef.height))
            .padding(10)
        }
        // 可选:显示当前坐标或原始数据值
        .overlay(
            Text(imageCoordinate != nil ? "坐标: (\(Int(imageCoordinate!.x)), \(Int(imageCoordinate!.y)))" : "")
                .foregroundColor(.white)
                .padding(4)
                .background(Color.black.opacity(0.7))
                .cornerRadius(4),
            alignment: .bottomTrailing
        )
    }
}

关键细节

  • 坐标转换逻辑:先通过内部GeometryReader获取图像实际显示的imageFrame,计算fit模式下的缩放比例(取宽高缩放比例的较小值),再将鼠标位置减去图像偏移量后除以缩放比例,得到原始坐标。
  • 边界判断:添加坐标范围检查,确保只有鼠标在图像实际区域内时才记录有效坐标。
  • 性能优化:原代码中importAndScaleImageData和createCGImageFromUint8在body中执行会导致视图刷新时重复计算,建议移至ViewModel或异步任务中处理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 20:14:59