Swift:如何在ScreenSaverView中加载ScreenView.xib的自定义视图
从XIB加载自定义视图到ScreenSaverView子类的正确实现
我看你已经在ScreenView.xib里创建了自定义视图,想要把它加载到ScreenSaverView的子类中,你的代码已经有了雏形,不过这里有几个容易踩坑的地方,我帮你完善并解释一下关键点:
首先,完整的实现代码应该是这样的:
class SwiftScreensaverView: ScreenSaverView { override init?(frame: NSRect, isPreview: Bool) { super.init(frame: frame, isPreview: isPreview) setupCustomView() } required init?(coder: NSCoder) { super.init(coder: coder) setupCustomView() } private func setupCustomView() { self.wantsLayer = true let customView = loadNib() // 让自定义视图铺满整个ScreenSaverView customView.frame = self.bounds // 设置自动调整尺寸,适配父视图的大小变化 customView.autoresizingMask = [.width, .height] self.addSubview(customView) } private func loadNib() -> NSView { var topLevelObjects: NSArray? // 重点:屏幕保护程序的Bundle不能用Bundle.main,要获取当前类所在的Bundle let bundle = Bundle(for: type(of: self)) guard bundle.loadNibNamed(NSNib.Name("ScreenView"), owner: self, topLevelObjects: &topLevelObjects) else { fatalError("Failed to load ScreenView.xib - 检查XIB文件名是否正确,或者是否加入到目标中") } // 从XIB的顶级对象中筛选出我们需要的NSView guard let customView = topLevelObjects?.first(where: { $0 is NSView }) as? NSView else { fatalError("ScreenView.xib中没有找到NSView类型的顶级视图") } return customView } }
几个关键注意点:
- 必须实现两个初始化方法:
init?(frame:isPreview:)和required init?(coder:),因为屏幕保护程序在不同场景下会调用不同的初始化方法,漏掉任何一个都可能导致加载失败。 - Bundle的正确获取方式:普通App用
Bundle.main没问题,但屏幕保护程序是作为插件存在的,Bundle.main指向的是系统偏好设置App的Bundle,所以必须用Bundle(for: type(of: self))来获取当前类所在的插件Bundle,才能找到你的ScreenView.xib。 - 视图尺寸适配:一定要设置自定义视图的
frame和autoresizingMask,否则你的自定义视图可能只会显示原来XIB里的尺寸,不会随屏幕保护的窗口大小变化。 - XIB加载后的校验:加载XIB后会返回所有顶级对象,我们需要从中筛选出NSView类型的对象,避免拿到File's Owner或者其他辅助对象。
内容的提问来源于stack exchange,提问作者triandicAnt
相关产品推荐
相关产品推荐

