Mac OS X:在NSView坐标和全局屏幕坐标之间进行转换

20

我在多显示器设置中遇到以下情况:

OSX coordinate conversions

例如,我想要将一个窗口定位到黄色箭头指示的坐标位置。然而我所拥有的仅仅是一个NSView的坐标,它是一个NSScreen的contentView的子视图,而这个NSScreen覆盖整个(更大、上部)次要显示器。

下面是全局坐标空间的定义方式:

  • {0,0} 是我的笔记本屏幕左上角的坐标。(绿色)
  • {-296, -1080} 是第二个屏幕(黑色)左上角的坐标
  • {0, 800} 是左下角的坐标(这里没有箭头)

因此,y从绿色箭头开始向下增加,从绿色箭头开始向上减少。

问题:

如何将黄色箭头所示的点 ({100,100}, NSView inside NSWindow inside this NSScreen) 转换为全局坐标系。(注意:在NSView中,坐标系的{0,0}在左下角,向上增加。)

我相信正确答案是{-196,-980},但是如何在任何屏幕上的窗口中获得此转换的代码呢?

我已经花了太多时间来解决这个问题,所以非常感谢任何帮助。

(不确定是否相关,但底部的屏幕具有视网膜分辨率显示器。)

2个回答

35

Mac OS在不同的地方使用不同的坐标系。视图可以定义它们是否具有朝上或朝下的y轴(isFlipped)。窗口的原点用带有朝上y轴的“屏幕坐标”表示。屏幕使用y指向下的全局坐标系进行排列。

最好不要尝试自己转换所有坐标空间,而是让负责的对象完成工作:

NSView *yellowView; // your view that contains the point with the yellow arrow.
NSPoint yellowPoint = { 100, 100 };

NSPoint pointInWindow = [yellowView convertPoint:yellowPoint toView:nil];
NSPoint pointOnScreen = [[yellowView window] convertRectToScreen:(CGRect){.origin=pointInWindow}];

NSWindow *newWindow = [[NSWindow alloc] initWithContentRect:(CGRect){ pointOnScreen, {32, 32}} styleMask: ...];

1
我认为这行代码: NSPoint pointOnScreen = [[yellowView window] convertRectToScreen:(CGRect){.origin=pointInWindow}]; 应该改为: NSPoint pointOnScreen = [[yellowView window] convertRectToScreen:(CGRect){.origin=pointInWindow}].origin; - TyR

8

被接受的答案的 Swift (4.0) 版本基本上是相同的:

let yellowView: NSView // your view that contains the point with the yellow arrow.
let yellowPoint = NSPoint(x: 100, y: 100)

let pointInWindow = yellowView.convert(yellowPoint, to: nil)
let pointOnScreen = yellowView.window?.convertToScreen(NSRect(origin: pointInWindow, size: .zero)).origin ?? .zero

let contentRect = NSRect(origin: pointOnScreen, size: NSSize(width: 32, height: 32))
let newWindow = NSWindow(contentRect: contentRect, styleMask: ...)

以下是另一种实现方式:
let someView: NSView // Some existing view
var rect: NSRect

rect = NSRect(x: 100, y: 100, width: 0, height: 0)
rect = someView.convert(rect, to: nil)
rect = someView.window?.convertToScreen(rect) ?? rect
rect.size = NSSize(width: 32, height: 32)
let newWindow = NSWindow(contentRect: rect, styleMask: ...)

在这里,我们介绍一种创建矩形的简单方法。对于那些需要详细指导的人,下面是一步步的操作:

1. 创建一个矩形。 在视图坐标系内所需位置初始化一个大小为零的矩形。

let someView: NSView // Some existing view
var rect = NSRect(x: 100, y: 100, width: 0, height: 0)

2. 从视图转换为窗口。 通过将目标 view 指定为 nil,将矩形从视图的坐标系统转换为窗口的坐标系统。

rect = someView.convert(rect, to: nil)

3. 将窗口坐标系转换为屏幕坐标系。 接下来,将矩形从窗口的坐标系统转换为屏幕的坐标系统。

请注意,someView.window 可能为 nil,因此我们使用可选链(即在 window? 中使用 ?)并在这种情况下回退到 rect 的原始值。这可能不是必需的,但这是一个好习惯。

rect = someView.window?.convertToScreen(rect) ?? rect

4. 设置矩形的大小。 使用新窗口所需的大小更新矩形。

rect.size = NSSize(width: 32, height: 32)

5. 创建窗口。 使用转换后的矩形初始化一个新窗口。

let newWindow = NSWindow(contentRect: rect, styleMask: ...)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接