18
votes

I have the following situation in my multi-monitor setup:

OSX coordinate conversions

In this example I want to position a window exactly at the coordinates depicted with the yellow arrow. All I do have however, are the coordinates of an NSView that is a subview of the contentView of an NSWindow that spans the entire (bigger,upper) secondary monitor.

Here's how the global coordinate space is defined:

  • {0,0} is the coordinate of the upper left corner of my laptop screen. (green)
  • {-296, -1080} is the coordinate of the upper left corner of my second screen (black)
  • {0, 800} is the coordinate of the lower left corner (no arrow here)

Thus y is increasing going down from green arrow, decreasing going up from green arrow.

Problem:

How do I convert the point depicted by the yellow arrow ({100,100}, NSView inside NSWindow inside this NSScreen) into that global coordinate system. (Note: In an NSView the coordinate system has {0,0} in the bottom left corner, increasing upwards.)

I believe the correct answer is {-196, -980}, but whats the code to get this conversion for any window on any screen?

I have spent too much time on this problem already, so any help is very appreciated.

(Not sure if relevant, but the bottom screen has a retina resolution display.)

2

2 Answers

31
votes

Mac OS uses different coordinate systems in various places. Views can define if they have an upwards or downwards pointing y-axis (isFlipped). A window's origin is expressed in "screen coordinates" with an upwards y-axis. Screens are arranged using the global coordinate system with y pointing down.

It's better not to try to do the conversion of all the coordinate spaces yourself but let the responsible objects do the job:

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: ...];
7
votes

The Swift (4.0) version of the accepted answer is basically the same:

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: ...)

The following is an alternate way to do it:

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: ...)

The latter way simply sets the rect up ahead of time. Here's a play-by-play for those who like walkthroughs:

1. Create a rectangle. Initialize a zero-sized rectangle at the desired location within the view's coordinate system.

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

2. Convert from view to window. Convert the rectangle from the view's coordinate system to the window's coordinate system by specifying nil for the destination view.

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

3. Convert from window to screen. Next, convert the rectangle from the window's coordinate system to the screen's coordinate system.

Note that someView.window may be nil, so we use optional chaining (i.e. the ? in window?) and fallback to the original value of rect if that's the case. This probably isn't necessary, but it's a good habit to get into.

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

4. Set the rectangle's size. Update the rectangle with the desired size of the new window.

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

5. Create the window. Initialize a new window with the converted rectangle.

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