Creating NSCollectionView with datasource programm

2019-07-15 09:21发布

I am trying to create a NSCollectionView programmatically using a NSCollectionViewDataSource.

The code is very simple:

self.collectionView = [[NSCollectionView alloc] init];
// Add collection view to self.view etc. 
self.collectionView.dataSource = self;
[self.collectionView registerClass:[NSCollectionViewItem class] forItemWithIdentifier:@"test"]
self.collectionView.collectionViewLayout = gridLayout;
[self.collectionView reloadData]

This leads to the following methods getting called (if I don't set the collectionViewLayout property explicitly these two don't get called either):

- (NSInteger)numberOfSectionsInCollectionView:(NSCollectionView*)collectionView
- (NSInteger)collectionView:(NSCollectionView*)collectionView numberOfItemsInSection:(NSInteger)section

However, collectionView:itemForRepresentedObjectAtIndexPath: is never called. Is there something else that I need to do in order to make sure that the last data source method is called? I have made sure that the two count calls return > 0, so that's not the problem.

2条回答
Anthone
2楼-- · 2019-07-15 09:36

So it seems that the problem was actually that I wasn't wrapping the NSCollectionView in a NSScrollView. This probably has to do with the layout being done incorrectly (so the items aren't requested from the data source) if it is not wrapped in a scroll view.

查看更多
啃猪蹄的小仙女
3楼-- · 2019-07-15 09:42

I've been working through different scenario's in the past days, and I dare say that using an NSScrollView, or not, makes practically no difference. With or without scrollView, I've ended up with the same errors and limitations.

What does make a huge difference is the choice between "old school" and the new-fangled collectionView. By "old school" I mean setting the itemPrototype and contents properties, something like this:

    NSCollectionView *collectionView = [[NSCollectionView alloc] init];
    collectionView.itemPrototype = [TBCollectionViewItem new];
    collectionView.content = self.collectionItems;

    NSInteger index = 0;
    for (NSString *title in _collectionItems) {
        NSIndexPath *path = [NSIndexPath indexPathForItem:index inSection:0];
        TBCollectionViewItem *item = [collectionView makeItemWithIdentifier:@"Test" forIndexPath:path];
        item.representedObject = title;
        index++;
    }
    // Plays well with constraints

New school, something along these lines:

NSCollectionView *collectionView = [[NSCollectionView alloc] init];
collectionView.identifier = TBCollectionViewIdentifier;

[collectionView registerClass:[TBCollectionViewItem class] forItemWithIdentifier:TBCollectionViewItemIdentifier]; //register before makeItemWithIdentifier:forIndexPath: is called.
TBCollectionViewGridLayout *gridLayout = [TBCollectionViewGridLayout collectionViewGridLayout:NSMakeSize(250, 100)]; //getting the contentSize from the scrollView does not help
collectionView.collectionViewLayout = gridLayout;
collectionView.dataSource = self;

Now, you may have noticed the comment that registerClass: must be called before makeItemWithIdentifier:forIndexPath. In practice, that means calling registerClass: before setting .dataSource, whereas in your code you set .dataSource first. The docs state:

Although you can register new items at any time, you must not call the makeItemWithIdentifier:forIndexPath: method until after you register the corresponding item.

I wish I could say that by switching those two lines, all layout problems will be solved. Unfortunately, I've found that the .collectionViewLayout / .dataSource combination is a recipe for (auto)layout disaster. Whether that can be fixed by switching from NSCollectionViewGridLayout to flowLayout, I'm not yet certain.

查看更多
登录 后发表回答