iOS and macOS are different. NSRect
and CGRect
can be too, apparently.
NSScreen
describes the attributes of a computer’s monitor. NSScreen.screens
returns an array of all monitors connected. NSScreen.frame
is an NSRect
defined as
This is the full screen rectangle at the current resolution. This rectangle includes any space currently occupied by the menu bar and dock.
https://developer.apple.com/documentation/appkit/nsscreen/1388387-frame
NSRect
is typedef’d to CGRect
. Of course, CGRect
is defined in Core Graphics as
In the default Core Graphics coordinate space, the origin is located in the lower-left corner of the rectangle and the rectangle extends towards the upper-right corner. If the context has a flipped-coordinate space—often the case on iOS—the origin is in the upper-left corner and the rectangle extends towards the lower-right corner.
https://developer.apple.com/documentation/coregraphics/cgrect
So, there’s the difference between iOS and macOS. One initializer is CGRect.init(origin: CGPoint, size: CGSize)
where CGPoint
(CGPoint.x
and CGPoint.y
) are the coordinates of the lower-left corner of the rectangle of size CGSize
with width and height (CGSize.width
and CGSize.height
, respectively)
Using a MacBook Pro, NSScreen.frame
returns (0.0, 0.0, 1920.0, 1200.0). The first two values is a CGPoint
while the last two are the width and height of CGSize
. The combination is a CGRect
.
Now, I connect another monitor and align them in System Preferences…>Displays>Arrangement such that the new monitor is above the original monitor. Leaving the menu bar on the original monitor, NSRect
is (0.0, 1200.0, 2560.0, 1080.0) for the new monitor and unchanged for the laptop’s screen.
Moving the menu bar to the new monitor, both NSRect
change: (0.0, 0.0, 2560.0, 1080.0) and (0.0, -1200.0, 1920.0, 1200.0) for the new and original monitors, respectively. This is consistent with the documentation if the origin is defined to be the lower-left corner of the monitor which has the menu bar, and CGPoint
is the lower right corner of the rectangle defined by CGSize
. I won’t show, but it’s true also for NSWindow.frame
, etc. Fantastic!
In Core Graphics, you can ask about what windows are on screen. For example let windows = CGWindowListCopyWindow( .optionOnScreenOnly, kCGNullWindowID)
will return an array of dictionaries, each dictionary (i.e. element of the array) corresponding to a window on the screen (front to back order). One of the required keys in each directory is “kCGWindowBounds
“. Apple provides an initializer for CGRect
: CGRect.init(dictionaryRepresentation: windows[0]["kCGWindowBounds"] as! CFDictionary)!
using this key. The documentation also says
The coordinates of the rectangle are specified in screen space, where the origin is in the upper-left corner of the main display.
https://developer.apple.com/documentation/coregraphics/kcgwindowbounds
So, now we’re back to iOS coordinates? This Core Graphic CGRect
has it’s vertical axis flipped and offset from either NSRect
or CGRECT
we have seen earlier with NSScreen
(or NSWindow
).frame
. Hmmm.
Here, CGPoint.zero
is NS
*’s height of the monitor (with the menu bar), call it h0. That’s the offset. Therefore, to translate this new CGRect
into the former NSRect
, only the vertical point of the CGPoint
needs to change. We first need to increase the original vertical component by CGSize.height
and subtract the result from h0. Now we can compare this new CGRect
to NS*.frame
to see if they are on the same area of the screen.
In code
// height of monitor that has menu bar let h0 = NSHeight((NSScreen.screens.filter({$0.frame.origin == CGPoint.zero}).first?.frame)!) // it must exist! // array of on screen windows (front to back) over all monitors let windows = CGWindowListCopyWindowInfo(.optionOnScreenOnly, kCGNullWindowID) as! [[ String : Any]] // get rectNS in CG coordinates (not NS* coordinates) for the first on screen window let rectNS = CGRect.init(dictionaryRepresentation: windows[0]["kCGWindowBounds"] as! CFDictionary)! // translate that CGRect to NS* coordinates let rectNS.origin.y = h0 - rectNS.origin.y - rectNS.height
Now rectNS
has the same coordinate space as NS*.frame
and can be compared directly (==
).
Add a Comment