代码创建的NSCollectionView已注册但makeItem仍返回nil
我正在开发一款基于macOS 10.10 SDK、使用Xcode 9.3(Swift 4)的macOS应用,全程采用代码创建视图而非xib文件。我想要创建NSCollectionView,已将NSCollectionViewItem的子类通过collectionView.register(:,forItemWithIdentifier)注册到NSCollectionView,但在数据源中调用collectionView.makeItem(withIdentifier:,for:)时,该方法始终返回nil。我已找到类似问题,其解决方案为调用register方法,而我已执行该操作。
附可复现问题的最小示例代码:
import Cocoa @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { @IBOutlet weak var window: NSWindow! func applicationDidFinishLaunching(_ aNotification: Notification) { window.contentViewController = TestViewController() } func applicationWillTerminate(_ aNotification: Notification) { // Insert code here to tear down your application } } let cellIdentifier = NSUserInterfaceItemIdentifier(rawValue: "testIdentifier") class TestViewController: NSViewController, NSCollectionViewDataSource { override func loadView() { self.view = NSView() } override func viewDidLoad() { let scroll = NSScrollView() self.view.addSubview(scroll) scroll.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint(item: scroll, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1, constant: 0).isActive = true NSLayoutConstraint(item: scroll, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1, constant: 0).isActive = true NSLayoutConstraint(item: scroll, attribute: .left, relatedBy: .equal, toItem: self.view, attribute: .left, multiplier: 1, constant: 0).isActive = true NSLayoutConstraint(item: scroll, attribute: .right, relatedBy: .equal, toItem: self.view, attribute: .right, multiplier: 1, constant: 0).isActive = true NSLayoutConstraint(item: scroll, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 500).isActive = true NSLayoutConstraint(item: scroll, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 500).isActive = true let collection = NSCollectionView() scroll.documentView = collection collection.register(TestViewItem.self, forItemWithIdentifier: cellIdentifier) collection.dataSource = self collection.collectionViewLayout = NSCollectionViewFlowLayout() collection.reloadData() } func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { return 10 } func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { let item = collectionView.makeItem(withIdentifier: cellIdentifier, for: indexPath) return item } } class TestViewItem: NSCollectionViewItem { override func loadView() { self.view = NSView() } }
断点显示调用makeItem后返回值始终为nil,请问我哪里操作错误?
嘿,我帮你找到了问题的核心!你的代码里有几个关键的布局设置缺失,导致NSCollectionView无法正确初始化并创建item:
1. NSCollectionView缺少必要的约束和配置
你把collectionView设为scrollView的documentView后,没有给它禁用自动约束转换,也没有建立和scrollView的约束关系。macOS的NSScrollView需要documentView有明确的尺寸约束才能正常工作,否则collectionView的内部布局会失效,进而无法创建注册的item。
2. 流布局未设置itemSize
默认的NSCollectionViewFlowLayout的itemSize是极小的(甚至是0x0),就算item创建成功,你也看不到它;而且这种情况下,collectionView可能会因为没有可显示的内容而跳过item的创建流程。
3. TestViewItem的view无标识性设置
你的TestViewItem的view是空白的NSView,没有背景色或尺寸,就算创建成功也无法直观看到结果。
修复后的完整代码
import Cocoa @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { @IBOutlet weak var window: NSWindow! func applicationDidFinishLaunching(_ aNotification: Notification) { window.contentViewController = TestViewController() } func applicationWillTerminate(_ aNotification: Notification) { // Insert code here to tear down your application } } let cellIdentifier = NSUserInterfaceItemIdentifier(rawValue: "testIdentifier") class TestViewController: NSViewController, NSCollectionViewDataSource { override func loadView() { self.view = NSView() } override func viewDidLoad() { let scroll = NSScrollView() self.view.addSubview(scroll) scroll.translatesAutoresizingMaskIntoConstraints = false // 用数组激活约束,更简洁高效 NSLayoutConstraint.activate([ scroll.bottomAnchor.constraint(equalTo: view.bottomAnchor), scroll.topAnchor.constraint(equalTo: view.topAnchor), scroll.leadingAnchor.constraint(equalTo: view.leadingAnchor), scroll.trailingAnchor.constraint(equalTo: view.trailingAnchor), scroll.widthAnchor.constraint(equalToConstant: 500), scroll.heightAnchor.constraint(equalToConstant: 500) ]) let collection = NSCollectionView() scroll.documentView = collection // 关键:禁用自动约束转换,否则无法添加自定义约束 collection.translatesAutoresizingMaskIntoConstraints = false // 给collectionView添加约束,让它和scrollView的contentView适配 NSLayoutConstraint.activate([ collection.widthAnchor.constraint(equalTo: scroll.contentView.widthAnchor), collection.heightAnchor.constraint(greaterThanOrEqualTo: scroll.contentView.heightAnchor) ]) collection.register(TestViewItem.self, forItemWithIdentifier: cellIdentifier) collection.dataSource = self // 设置流布局的itemSize,确保item有可见尺寸 let flowLayout = NSCollectionViewFlowLayout() flowLayout.itemSize = CGSize(width: 80, height: 80) collection.collectionViewLayout = flowLayout collection.reloadData() } func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { return 10 } func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { // 这里可以加个安全转换,避免意外nil guard let item = collectionView.makeItem(withIdentifier: cellIdentifier, for: indexPath) as? TestViewItem else { fatalError("Failed to dequeue TestViewItem") } return item } } class TestViewItem: NSCollectionViewItem { override func loadView() { self.view = NSView() // 设置背景色,方便直观看到item self.view.wantsLayer = true self.view.layer?.backgroundColor = NSColor.red.cgColor } }
额外注意事项
- 优先使用
NSLayoutConstraint.activate([...])来批量添加约束,比逐个设置isActive更符合现代Swift的写法,也更高效。 - 纯代码创建NSCollectionViewItem时,确保
loadView正确初始化view,并且给view设置可识别的样式(比如背景色),方便调试。 - 始终给纯代码创建的视图设置
translatesAutoresizingMaskIntoConstraints = false,否则系统自动生成的约束会和自定义约束冲突,导致布局异常。
这样修改后,makeItem就能正常返回TestViewItem的实例,你也能看到红色的item在collectionView中显示了。
内容的提问来源于stack exchange,提问作者Marco83

