问题描述
在NSView中,设置bounds时,发现bounds失效:
class ScreenshotOverlayView: NSView {
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
layer?.backgroundColor = NSColor.black.withAlphaComponent(0.2) // 设置半透明的黑色背景
let mutablePath = CGMutablePath()
mutablePath.addRect(bounds) // 设置全屏大小的矩形
let shapeLayer = CAShapeLayer()
shapeLayer.path = mutablePath // shapeLayer的路径应该是 bounds 全屏,所以遮罩层是全部,应该半透明的黑色背景
layer?.mask = selectionLayer
}
}
NSView中的bounds表示视图当前大小的区域,而CGMutablePath.addRect()表示,将视图当前大小的矩形路径添加到mutablePath路径中。
经过查询了解到,在NSView的构造方法init()中,使用bounds时视图并没有真正布局完成,bounds值仍然是(0,0,0,0),所以mutablePath路径是空的。
在NSView的生命周期中,init(frame:)是非常早的阶段,bounds默认是(0,0,0,0),底层的CALayer还没完全初始化,视图还没有加入视图层次结构进行布局。

解决方案
把绘图逻辑延迟到视图真正有尺寸后再做。可以放到layout()或viewDidMoveToWindow()中:
1、重写layout:
自动布局时会调用此方法,可重写处理子视图布局。
override func layout() {
layer?.backgroundColor = NSColor.black.withAlphaComponent(0.2) // 设置半透明的黑色背景
let mutablePath = CGMutablePath()
mutablePath.addRect(bounds) // 设置全屏大小的矩形
let shapeLayer = CAShapeLayer()
shapeLayer.path = mutablePath // shapeLayer的路径应该是 bounds 全屏,所以遮罩层是全部,显示半透明的黑色背景
layer?.mask = selectionLayer
}
2、重写viewDidMoveToWindow()
viewDidMoveToWindow方法,在视图添加到 window 时调用。
override func viewDidMoveToWindow() {
layer?.backgroundColor = NSColor.black.withAlphaComponent(0.2) // 设置半透明的黑色背景
let mutablePath = CGMutablePath()
mutablePath.addRect(bounds) // 设置全屏大小的矩形
let shapeLayer = CAShapeLayer()
shapeLayer.path = mutablePath // shapeLayer的路径应该是 bounds 全屏,所以遮罩层是全部,显示半透明的黑色背景
layer?.mask = selectionLayer
}

总结
问题的原因在于,NSView初始化时bounds可能是(0,0,0,0),因此应该考虑在layout或viewDidMoveToWindow生命周期方法中,使用bounds。