swiftmacosappkitnscollectionviewnscollectionviewitem

NSCollectionView renders all NSCollectionViewItems at the same point


I am trying to create a simple NSCollectionView programmatically. I am setting it up in the NSViewController:

class ViewController: NSViewController {
    var scrollView: NSScrollView!
    var collectionView: NSCollectionView!
    override func loadView() {
        self.view = NSView(frame: NSRect(x: 0, y: 0, width: 600, height: 400))
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        collectionView = NSCollectionView(frame: NSZeroRect)
        collectionView.backgroundView?.wantsLayer = true
        collectionView.backgroundColors = [NSColor.black]
        collectionView.collectionViewLayout = NSCollectionViewFlowLayout()
        collectionView.dataSource = self
        collectionView.delegate = self
        collectionView.register(CollectionViewItem.self, forItemWithIdentifier: NSUserInterfaceItemIdentifier("CollectionViewItem"))
        scrollView = NSScrollView(frame: view.frame)
        scrollView.documentView = collectionView
        view.addSubview(scrollView)
        self.scrollView.translatesAutoresizingMaskIntoConstraints = false
        self.scrollView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
        self.scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
        self.scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
        self.scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
    }
}

extension ViewController: NSCollectionViewDataSource {
    func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
        return 5
    }
    func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
        let item = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier("CollectionViewItem"), for: indexPath)
        item.representedObject = indexPath.item
        return item
    }
}

extension ViewController: NSCollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> NSSize {
        return NSSize(width: 25, height: 25)
    }
}

Here's how I am setting up the NSCollectionViewItem:

class CollectionViewItem: NSCollectionViewItem {
    var collectionViewItemView: CollectionViewItemView?
    override func loadView() {
        if (self.representedObject != nil) {
            self.collectionViewItemView = CollectionViewItemView(frame: NSZeroRect, number: self.representedObject as! Int)
            self.view = collectionViewItemView!
        } else {
            self.view = CollectionViewItemView(frame: NSZeroRect)
        }
    }
    override var representedObject: Any? {
        didSet {
            loadView()
        }
    }
}

And here's how I'm creating a custom NSView to work inside the NSCollectionViewItem:

class CollectionViewItemView: NSView {
    var number: Int?
    override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)
    }
    convenience init(frame frameRect: NSRect, number: Int) {
        self.init(frame: frameRect)
        self.number = number
        layoutSubviews()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
    func layoutSubviews() {
        let label = NSTextField(frame: NSMakeRect(0, 0, 60, 30))
        label.stringValue = "\(self.number!)"
        addSubview(label)
    }
}

Now, despite the fact that I am using an NSCollectionViewFlowLayout, conforming to the NSCollectionViewDelegateFlowLayout and providing a sizeForItemAt all indices, the resultant CollectionViewItemView is always sized 0, as shown below:

NSCollectionView

In sample projects online, the authors always feed an NSZeroRect to the frame of the NSCollectionViewItem. If I give it a custom NSRect, it always renders them on top of each other (since the origin of the frame is always the same).

What is the proper way of doing this? I'm at a loss. Why is the NSCollectionView not responding to the NSCollectionViewDelegateFlowLayout ?


Solution

  • I've never seen this problem, but here's a guess:

    In CollectionViewItem you are overriding loadView() but it never calls super.loadView(). CollectionViewItem is a subclass of NSViewController and if you override loadView() you must first invoke super.loadView() so the view controller can initialize its internal state.

    If you're going to be targeting macOS 10.10 and later, you probably want to override viewDidLoad() instead.