How to create NSCollectionView programatically?

I'm attempting to create an NSCollectionView (wrapped in a SwiftUI container) programmatically, however I repeatedly get a crash when dequeuing a reusable item (or supplementary view if the snapshot is empty). I used the Modern Collection Views sample code as reference, but don't see what I'm doing incorrectly.

The code for the view



And the crash messages

Code Block
could not dequeue an item of kind: NSCollectionElementKindItem with identifier Cell - must register a nib or a class for the identifier, or name a nib or class to match the identifier


Code Block
could not dequeue a view of kind: ViewHeader with identifier Header - must register a nib or a class for the identifier, or name a nib or class to match the identifier


which are particularly unhelpful as those are exactly the identifiers used during registration. I've also tried using nibs during registration, with the same results. Any insights would be greatly appreciated!
Could you show how you registered, either nib or class, in the ViewController ?
That's done in the attached source in makeNSView

Code Block
view.register(HostingCellView.self, forItemWithIdentifier: Cell.reuseIdentifier)
view.register(HostingSupplementaryView.self, forSupplementaryViewOfKind: Self.headerIdentifier, withIdentifier: Header.reuseIdentifier)
view.register(HostingSupplementaryView.self, forSupplementaryViewOfKind: Self.footerIdentifier, withIdentifier: Footer.reuseIdentifier)


I copy code for convenience, not easy to read in the original post.

On which line does the crash occurs ?

Code Block
import SwiftUI
import AppKit
final class HostingCellView: NSCollectionViewItem {
func setView<Content>(_ newValue: Content) where Content: View {
for view in self.view.subviews {
view.removeFromSuperview()
}
let view = NSHostingView(rootView: newValue)
view.autoresizingMask = [.width, .height]
self.view.addSubview(view)
}
}
struct Cell: View {
static let reuseIdentifier = NSUserInterfaceItemIdentifier("Cell")
var body: some View {
Text("Cell")
}
}
final class HostingSupplementaryView: NSView, NSCollectionViewElement {
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
}
@objc required dynamic init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setView<Content>(_ newValue: Content) where Content: View {
for view in self.subviews {
view.removeFromSuperview()
}
let view = NSHostingView(rootView: newValue)
view.autoresizingMask = [.width, .height]
self.addSubview(view)
}
}
struct Header: View {
static let reuseIdentifier = NSUserInterfaceItemIdentifier("Header")
var body: some View {
Text("Header")
}
}
struct Footer: View {
static let reuseIdentifier = NSUserInterfaceItemIdentifier("Footer")
var body: some View {
Text("Footer")
}
}
struct Collection: NSViewRepresentable {
static let headerIdentifier = "ViewHeader"
static let footerIdentifier = "ViewFooter"
typealias NSViewType = NSCollectionView
func makeNSView(context: Context) -> NSCollectionView {
let view = NSCollectionView()
view.delegate = context.coordinator
view.register(HostingCellView.self, forItemWithIdentifier: Cell.reuseIdentifier)
view.register(HostingSupplementaryView.self, forSupplementaryViewOfKind: Self.headerIdentifier, withIdentifier: Header.reuseIdentifier)
view.register(HostingSupplementaryView.self, forSupplementaryViewOfKind: Self.footerIdentifier, withIdentifier: Footer.reuseIdentifier)
view.collectionViewLayout = NSCollectionViewCompositionalLayout { (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection in
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let group = NSCollectionLayoutGroup.vertical(layoutSize: itemSize, subitem: item, count: 1)
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .continuous
let supplementarySize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44))
let header = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: supplementarySize, elementKind: Self.headerIdentifier, alignment: .topLeading)
let footer = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: supplementarySize, elementKind: Self.footerIdentifier, alignment: .bottomTrailing)
section.boundarySupplementaryItems = [header, footer]
return section
}
let dataSource = NSCollectionViewDiffableDataSource<Int, Int>(collectionView: view) { (view, indexPath, sectionIndex) -> NSCollectionViewItem? in
guard let item = view.makeItem(withIdentifier: Cell.reuseIdentifier, for: indexPath) as? HostingCellView else {
fatalError()
}
item.setView(Cell())
return item
}
dataSource.supplementaryViewProvider = { (view: NSCollectionView, kind: String, indexPath: IndexPath) -> (NSView & NSCollectionViewElement)? in
switch kind {
case Self.headerIdentifier:
guard let supplementary = view.makeSupplementaryView(ofKind: kind, withIdentifier: Header.reuseIdentifier, for: indexPath) as? HostingSupplementaryView else {
fatalError()
}
supplementary.setView(Header())
return supplementary
case Self.footerIdentifier:
guard let supplementary = view.makeSupplementaryView(ofKind: kind, withIdentifier: Footer.reuseIdentifier, for: indexPath) as? HostingSupplementaryView else {
fatalError()
}
supplementary.setView(Footer())
return supplementary
default:
return nil
}
}
context.coordinator.dataSource = dataSource
return view
}
func updateNSView(_ nsView: NSCollectionView, context: Context) {
var snapshot = NSDiffableDataSourceSnapshot<Int, Int>()
snapshot.appendSections([0])
// snapshot.appendItems(Array<Int>(0..<10), toSection: 0)
context.coordinator.dataSource?.apply(snapshot, animatingDifferences: true)
}
func makeCoordinator() -> Coordinator {
return Coordinator(self)
}
class Coordinator: NSObject {
let view: Collection
var dataSource: NSCollectionViewDiffableDataSource<Int, Int>? = nil
init(_ view: Collection) {
self.view = view
}
}
}
extension Collection.Coordinator: NSCollectionViewDelegate {}

It crashes on lines 90 (or 99 or 105) when calling makeItem or makeSupplementaryView.

(The attached source was because the original posting interface wouldn't let me submit that long a post with code inline.)
I'm not sure of this.

In UIKit, I register in viewDidload.
Maybe your collection is not yet registered when you call makeItem.
Hi, @mbarriault 

Is this problem solved?

You can fix the crash by adding the cell xib.

Were you able to solve this problem? I'm encountering it as well and I hit the runtime error whether I register an itemClass or a nib. This is a big problem if I try to implement an NSCollectionView in a framework since AppKit seems to try to load the nib from the app bundle and not the framework bundle, and there's no way to configure it to use the framework bundle. My workaround was to add the nib to the target project instead of defining it in the target framework.

I am an AppKit noob with a UIKit background who just stumbled on the same issue. However, this unrelated StackOverflow question gives me some insight: you can override loadView of a NSCollectionViewItem and simply create any view programmatically:

class TestViewItem: NSCollectionViewItem {
    override func loadView() {
        self.view = NSView()
    }
}

I suppose this is the place where the default implementation from AppKit looks for NIB files.

Seeing the same behavior. Not sure what's going on, but I was able to work around it by making the reuse identifier the same as the nib name. E.g.:

class ListItem: NSCollectionViewItem {

    static let reuseIdentifier = NSUserInterfaceItemIdentifier("ListItem") // was "list-item-reuse-identifier"

...

I suspect I'm now picking up some fallback behavior.

-Steve

Setting collectionViewLayout somehow clear the cell class registrations, causing makeItem(withIdentifier:for:) to dequeue the cell from nib unexpectedly.

This seems like a bug to me, so I fire a feedback FB14769347.

As a workaround, set collectionViewLayout BEFORE calling your register(_:for:) methods.

How to create NSCollectionView programatically?
 
 
Q