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

Mac Catalyst及Cocoa中光标位置与绘制点不匹配问题求助

解决方案:Mac平台鼠标点击与绘图位置偏移问题

核心原因分析

你遇到的偏移问题主要来自三个方面:

  • Cocoa项目中直接使用窗口坐标而非视图本地坐标的转换错误
  • 高DPI(Retina)屏幕下逻辑坐标与物理像素未对齐
  • 系统默认十字光标本身存在的微小位图偏移

一、修复Cocoa原生项目的坐标转换错误

你的Cocoa代码中直接用event.locationInWindow作为绘图坐标,但该坐标属于窗口坐标系,必须转换为当前视图的本地坐标:

override func mouseDown(with event: NSEvent) {
    // 替换原错误代码,转换为视图本地坐标
    point = convert(event.locationInWindow, from: nil)
    setNeedsDisplay(bounds)
    NSCursor.crosshair.set()
}

二、处理高DPI屏幕的像素对齐

无论是Mac Catalyst(UIKit)还是原生Cocoa,高DPI屏幕下逻辑坐标需要转换为物理像素对齐的位置,避免偏移或模糊。

1. Mac Catalyst(UIKit)项目修改

在CanvasView的draw(_:)方法中,基于视图的contentScaleFactor对齐像素:

override func draw(_ rect: CGRect) {
    guard let context = UIGraphicsGetCurrentContext() else { return }
    let scale = contentScaleFactor
    
    // 将逻辑坐标转换为像素对齐的位置
    let alignedX = round(drawPoint.x * scale) / scale
    let alignedY = round(drawPoint.y * scale) / scale
    
    let circleRect = CGRect(
        x: alignedX - radius,
        y: alignedY - radius,
        width: radius * 2,
        height: radius * 2
    )
    // 可选:关闭抗锯齿,避免边缘模糊导致的视觉偏移
    context.setShouldAntialias(false)
    context.fillEllipse(in: circleRect)
}

同时确保视图的contentScaleFactor与屏幕一致:

class CanvasView: UIView {
    override init(frame: CGRect) {
        super.init(frame: frame)
        contentScaleFactor = UIScreen.main.scale
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        contentScaleFactor = UIScreen.main.scale
    }
    
    // ... 原有的其他代码
}

2. Cocoa原生项目修改

在View的draw(_:)方法中,使用backingScaleFactor对齐像素:

override func draw(_ dirtyRect: NSRect) {
    super.draw(dirtyRect)
    NSColor.blue.setStroke()
    let cross = NSBezierPath()
    let v = radius * 2
    
    let scale = backingScaleFactor
    // 对齐到物理像素
    let alignedX = round(point.x * scale) / scale
    let alignedY = round(point.y * scale) / scale
    
    cross.move(to: CGPoint(x: alignedX - v, y: alignedY))
    cross.line(to: CGPoint(x: alignedX + v, y: alignedY))
    cross.move(to: CGPoint(x: alignedX, y: alignedY - v))
    cross.line(to: CGPoint(x: alignedX, y: alignedY + v))
    
    cross.lineWidth = 0.8
    // 可选:设置方形线帽,避免抗锯齿导致的端点模糊
    cross.lineCapStyle = .square
    cross.stroke()
}

三、解决系统光标偏移问题

系统默认的十字准星光标是位图,本身存在1像素左右的偏移。可以自定义光标实现完全对齐:

// Cocoa自定义精准十字光标
func createPreciseCrosshairCursor() -> NSCursor {
    let size = NSSize(width: 21, height: 21)
    let image = NSImage(size: size, flipped: false) { rect in
        let path = NSBezierPath()
        // 绘制中心十字线,确保热点在正中心
        path.move(to: NSPoint(x: rect.midX, y: rect.minY))
        path.line(to: NSPoint(x: rect.midX, y: rect.maxY))
        path.move(to: NSPoint(x: rect.minX, y: rect.midY))
        path.line(to: NSPoint(x: rect.maxX, y: rect.midY))
        NSColor.black.setStroke()
        path.lineWidth = 1
        path.stroke()
        return true
    }
    image.isTemplate = true
    // 设置热点为图像正中心
    return NSCursor(image: image, hotSpot: NSPoint(x: size.width/2, y: size.height/2))
}

使用时替换原光标设置:

override func mouseDown(with event: NSEvent) {
    point = convert(event.locationInWindow, from: nil)
    setNeedsDisplay(bounds)
    // 使用自定义光标
    createPreciseCrosshairCursor().set()
}

内容的提问来源于stack exchange,提问作者Nguyễn Vũ Khang

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 00:20:44