NSScreen 是 macOS AppKit 框架中表示屏幕显示器(屏幕)的类,用于获取和管理用户连接的所有显示器的相关信息,比如:
每个屏幕的大小、位置、比例(DPI)、颜色空间;
哪一个是主屏幕(primary screen);
多显示器环境中每个屏幕的坐标。

基本用法
class NSScreen : NSObject
不能创建 NSScreen 实例,它由系统提供(屏幕由 macOS 管理)。可以通过 NSScreen.screens 或 NSScreen.main 获取实例,然后读取其属性:
import AppKit
let screens = NSScreen.screens // 所有屏幕(数组)
let mainScreen = NSScreen.main // 主屏幕(可能为 nil)
常用属性
1、screens: [NSScreen]
返回当前连接的所有显示器(包括主屏、副屏)。
for screen in NSScreen.screens {
print(screen.frame) // (0.0, 0.0, 1470.0, 956.0)
}
通常数组第一个是主屏,但不绝对(以 NSScreen.main 为准)。
2、main: NSScreen?
当前应用活动窗口所在的屏幕(并非鼠标所在屏幕)。
if let main = NSScreen.main {
print("当前屏幕尺寸:\(main.frame.size)")
}
3、frame: NSRect
屏幕在全局坐标中的位置和尺寸(单位是“点”而非像素)。
print(screen.frame) // 如:x: 0.0, y: 0.0, width: 1440.0, height: 900.0
原点为主屏幕的左下角。
多屏布局中,副屏可能是负坐标或 Y 向上扩展。
for screen in NSScreen.screens {
print("屏幕尺寸\(screen.frame),屏幕ID:\(screen.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? CGDirectDisplayID ?? 0)")
}
// 输出屏幕尺寸
屏幕尺寸(0.0, 0.0, 1470.0, 956.0),屏幕ID:1
屏幕尺寸(1470.0, -124.0, 1920.0, 1080.0),屏幕ID:2
4、visibleFrame: NSRect
屏幕去掉菜单栏和 Dock 后的可用区域。
print(screen.visibleFrame)
用途:自动布局 App 窗口时,避免被菜单栏遮挡。
5、backingScaleFactor: CGFloat
屏幕的缩放因子(Retina 屏通常为 2.0)
let scale = screen.backingScaleFactor
非 Retina 显示器:1.0,Retina 显示器:2.0。
需要换算成像素大小时使用:
let widthInPixels = screen.frame.width * screen.backingScaleFactor
6、colorSpace: NSColorSpace?
当前屏幕的颜色空间(sRGB、Display P3等)。
print(screen.colorSpace?.localizedName ?? "Unknown")
用途:做色彩敏感处理(图像处理、设计工具等)
7、deviceDescription: [NSDeviceDescriptionKey: Any]
包含屏幕底层硬件 ID 和其他信息。
最常用的是获取 CGDirectDisplayID:
if let screenID = screen.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? CGDirectDisplayID {
print("屏幕ID:\(screenID)")
}
可用于结合 CGDisplay 或 CoreGraphics API 来截图或标识屏幕。
8、localizedName: String (macOS 10.15+)
屏幕的名称,如“内建视网膜显示器”、“DELL U2723QE”等
print(screen.localizedName)
用途:多屏设置界面、用户选择某块屏幕时的名称显示。
常用方法
NSScreen 本身几乎没有方法,只有属性。
但配合它的属性和系统其他 API,可以实现很多高级功能,例如:
1、获取像素尺寸:frame.size × backingScaleFactor;
2、截图:获取 CGDirectDisplayID 然后用 CGDisplayCreateImage(…);
3、窗口自适应:visibleFrame + NSWindow.setFrame(…);
4、判断鼠标在哪块屏幕:NSEvent.mouseLocation + NSMouseInRect;
5、多屏定位:遍历 NSScreen.screens,找坐标对应区域。
使用示例
1、显示所有屏幕信息
for screen in NSScreen.screens {
let name = screen.localizedName
let frame = screen.frame
let visible = screen.visibleFrame
let scale = screen.backingScaleFactor
let color = screen.colorSpace?.localizedName ?? "Unknown"
print("""
屏幕: \(name)
尺寸: \(frame)(可用区域: \(visible))
缩放因子: \(scale)
色彩空间: \(color)
""")
}
2、找出鼠标在哪个屏幕
func screenContainingMouse() -> NSScreen? {
let mouseLocation = NSEvent.mouseLocation
return NSScreen.screens.first(where: { NSMouseInRect(mouseLocation, $0.frame, false) })
}
3、获取 CGDisplayID
for screen in NSScreen.screens {
if let screenID = screen.deviceDescription[.init("NSScreenNumber")] as? CGDirectDisplayID {
print("屏幕 ID: \(screenID), 尺寸: \(screen.frame.size)")
}
}
4、获取主屏幕
let mainDisplayID = CGMainDisplayID() // 主屏幕(主显示器)的唯一 ID
guard let screen = NSScreen.screens.first(where: {
let screenID = $0.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? CGDirectDisplayID
return screenID == mainDisplayID
}) else {
return
}
注意事项
AppKit的坐标系统以主屏幕的左下角为原点。
多个屏幕时,其 frame.origin 会不同,例如副屏可能在主屏上方或左侧。
可以通过 NSScreen.screens 遍历所有屏幕,定位鼠标在哪个屏幕中。
总结
NSScreen可以获取各屏幕的信息,配合实现多屏幕截图、窗口定位和Retina适配。
NSScreen只表示物理显示器,也就是当前连接的:
电脑自身的屏幕(如 MacBook 内建 Retina 显示器);
外接显示器(通过 HDMI、DisplayPort、USB-C 等);
通过 AirPlay 投影的显示器;
通过 Sidecar/iPad 扩展的屏幕。
NSScreen.screens.count // 返回物理屏幕个数
不包含macOS的虚拟桌面/控件(Spaces)。

通过三指滑动切换的 “全屏 App” 或 “Mission Control 添加的多个桌面”,在系统底层是同一个物理屏幕的不同虚拟工作区(Space),不是新的 NSScreen。
扩展知识
NSScreen和CGDirectDisplayID区别
在使用示例中,使用CGDirectDisplayID获取主屏幕:
let mainDisplayID = CGMainDisplayID() // 主屏幕(主显示器)的唯一 ID
guard let screen = NSScreen.screens.first(where: {
let screenID = $0.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? CGDirectDisplayID
return screenID == mainDisplayID
}) else {
return
}
在所有 NSScreen 中,找出与主屏幕 CGDisplay 的 ID(mainDisplayID)一致的那一块 NSScreen。
CGMainDisplayID()是CoreGraphics 提供的函数:返回主屏幕(主显示器)的唯一 ID(CGDirectDisplayID 类型),这个 ID 是一个 UInt32,表示 macOS 系统层面的主屏幕编号。
例如,我有两个屏幕:内建MacBook屏幕和外接屏幕(液晶电视)。

首次连接外接屏幕时,设置为“扩展显示屏”。

打开“设置”-“显示屏”:

可以看到这两个屏幕,点击“用途”按钮,可以切换为“主显示器”或“扩展显示器”。
上图把HDMI液晶电视设置为主显示器,内建MacBook屏幕就变为“扩展显示器”。

CGMainDisplayID()始终返回设置为“主显示器”的CGDirectDisplayID,无论窗口在哪一块屏幕上,永远指向系统设置里“主显示”的那一块屏幕。也就意味着,CGDirectDisplayID只会返回我们设置为主显示器的HDMI液晶电视。
NSScreen.main表示当前App激活窗口所在的屏幕,如果点击HDMI液晶电视屏幕上的应用程序,NSScreen.main就是HDMI液晶电视屏幕。如果点击内建MacBook屏幕的应用程序,NSScreen.main指的就是内建MacBook屏幕。
所以NSScreen.main表示当前活跃窗口的屏幕,不是设置中主显示器的屏幕。
这里也可能通过代码来验证:
// 遍历所有屏幕尺寸和屏幕ID
for screen in NSScreen.screens {
print("屏幕尺寸\(screen.frame),屏幕ID:\(screen.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? CGDirectDisplayID ?? 0)")
}
// 返回 NSScreen.main 的屏幕ID
if let main = NSScreen.main {
print("NSScreen.main的屏幕ID:\(main.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? CGDirectDisplayID ?? 0)")
}
// 返回 CGMainDisplayID() 的屏幕ID
guard let screen = NSScreen.screens.first(where: {
let screenID = $0.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? CGDirectDisplayID
return screenID == CGMainDisplayID()
}) else {
return
}
print("CGDirectDisplayID对应的主屏幕ID:\(screen.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? CGDirectDisplayID ?? 0)")
在代码运行时,我把主屏幕设置为内建MacBook屏幕,HDMI液晶电视设置为扩展屏幕。

当我在内建MacBook屏幕,点击按钮触发这个方法,Xcode输出:
屏幕尺寸(0.0, 0.0, 1470.0, 956.0),屏幕ID:1
屏幕尺寸(1470.0, -124.0, 1920.0, 1080.0),屏幕ID:2
NSScreen.main的屏幕ID:1
CGDirectDisplayID对应的主屏幕ID:1
这表示内建MacBook屏幕是当前活跃窗口的屏幕,ID为1,也是设置中CGDirectDisplayID对应的主屏幕。
我切换到HDMI液晶电视,点击按钮触发这个方法,Xcode输出:
屏幕尺寸(0.0, 0.0, 1470.0, 956.0),屏幕ID:1
屏幕尺寸(1470.0, -124.0, 1920.0, 1080.0),屏幕ID:2
NSScreen.main的屏幕ID:2
CGDirectDisplayID对应的主屏幕ID:1
可以看到,因为我的鼠标移动到HDMI液晶电视,点击按钮触发方法,所以NSScreen.main也变成了HDMI液晶电视。而CGDirectDisplayID只会绑定设置中设置为主屏幕的屏幕。