1

My NSCollectionView is drawing my NSCollection items over each other. UPDATE: I have added a sample project GitHub Sample Project

UPDATE: This has changed somewhat When the app first launches it looks like this

UPDATE My current example has two views which are currently in there own nib files, with dedicated NScollectionViewItem objects they are currently the same for testing. I basically have a NSCollectionViewItem which has as it's child a view with the NSTextField in it. With all of the constraints.

For the Collection View it is setup as a Grid Controller, and ideally, I would like to have 1 column.

In order to load it with Data I made my ViewController the NSCollectionViewDataSource, and implemented the - (NSInteger)collectionView:(NSCollectionView *)collectionView numberOfItemsInSection:(NSInteger)section and - (NSCollectionViewItem *)collectionView:(NSCollectionView *)collectionView itemForRepresentedObjectAtIndexPath:(NSIndexPath *)indexPath

UPDATED CODE Full Code included:

- (void)viewDidLoad {
    [super viewDidLoad];

    // Do any additional setup after loading the view.

    [collectionView registerClass:ItemOne.class forItemWithIdentifier:@"Item1"];
    [collectionView registerClass:ItemTwo.class forItemWithIdentifier:@"Item2"];

    cellArray = [@[@"Item1", @"Item2", @"Item1", @"Item2", @"Item1"] mutableCopy];
}


- (void)setRepresentedObject:(id)representedObject {
    [super setRepresentedObject:representedObject];

    // Update the view, if already loaded.
}

#pragma mark - NSCollectionViewDatasource -
- (NSInteger)collectionView:(NSCollectionView *)collectionView
 numberOfItemsInSection:(NSInteger)section {

    // We are going to fake it a little.  Since there is only one section
    NSLog(@"Section: %ld, count: %ld", (long)section, [cellArray count]);

    return [cellArray count];
}

- (NSCollectionViewItem *)collectionView:(NSCollectionView *)collectionView
 itemForRepresentedObjectAtIndexPath:(NSIndexPath *)indexPath {

    NSLog(@"IndexPath: %@, Requested one: %ld", indexPath, [indexPath item]);
    NSLog(@"Identifier: %@", [cellArray objectAtIndex:[indexPath item]]);

    NSCollectionViewItem *theItem = [collectionView makeItemWithIdentifier:[cellArray objectAtIndex:[indexPath item]] forIndexPath:indexPath];

    return theItem;
}

UPDATE The ItemOne and ItemTwo classes are both empty classes, the nib for each has a NSCollectionViewItem which in turn has a view, with label. The View is connected to the NSCollectionViewItem by the view property in NSCollectionViewItem. There are currently no constraints except for the default ones

The NSCollectionView grid is set up as follows:

Layout: Grid Dimensions: Max Rows: 0 Max Columns: 1 Min Item Size: Width: 250 Height: 150 Max Item Size: Width: 250 Height: 150

This is the code for setting up the whole thing, at this point not tying it to a data source.

It seems that no matter what I change the settings or even changing the CollectionView type to Flow doesn't change anything, it looks the same.

I have been approaching this as an AutoLayout issue because originally there were some auto layout issues, but those have all been resolved.

Any help would be greatly appreciated.

Tempus
  • 13
  • 5
  • Have made some progress! I removed the NSCollectionViewItems from the storyboard and put them in there own nibs with custom classes. I registered the classes, and changed my array to include the identifier. Which loads both views in two separate columns, even though I have the grid set to one column. As soon as I scroll the view, the two NSCollectionViewItems become one again. – Tempus Jun 17 '17 at 20:04
  • I tried your code and it works. What happens when you remove all contraints from the items? How did you set up the grid layout? – Willeke Jun 18 '17 at 08:59
  • It seems that I might have been thinking about this all wrong. So I have a working version in the GitHub Repo, but it works with Flow and not Grid, never got Grid working the way I expected it to work. And am able to create a single column, by making the item size as big as the view size. So it is kind of working. However, I want to see if I can have views with different heights – Tempus Jun 21 '17 at 07:32

2 Answers2

1

The data array should hold data instead of NSCollectionViewItems. In collectionView:itemForRepresentedObjectAtIndexPath: you call makeItemWithIdentifier:forIndexPath:. Call registerClass:forItemWithIdentifier: or registerNib:forItemWithIdentifier: to register your class or nib.

More info in the documentation of NSCollectionView, collectionView:itemForRepresentedObjectAtIndexPath: and makeItemWithIdentifier:forIndexPath:.

EDIT:

There are two ways to provide a NSCollectionViewItem.

registerClass:forItemWithIdentifier:. When the collection view needs a new item, it instatiates this class. NSCollectionViewItem is a subclass of NSViewController and NSViewController looks for a nib with the same name as the class. The NSCollectionViewItem is the owner of the nib.

registerNib:forItemWithIdentifier:. When the collection view needs a new item, it loads this nib. The NSCollectionViewItem is a top level object in the nib.

You mixed registerClass:forItemWithIdentifier: with a xib for use with registerNib:forItemWithIdentifier:. Use registerNib:forItemWithIdentifier: or fix the xib.

Willeke
  • 14,578
  • 4
  • 19
  • 47
  • Thanks for the comments Willeke. I had the same thought. So I changed it up, so that NSCollectionViewItems are in separate nib files with dedicated classes. In the View Controller I register the identifiers, and change the array to be an array of the identifiers. In the `itemForRepresentedObjectAtIndex` I `makeItemWithIdentifier` and when the app first loads all of the items are separated out but as soon as I do a scroll action they combine onto each other. So still not quite there, it seems like the scrollview isn't growing. – Tempus Jun 18 '17 at 00:13
  • Good catch! Even though I got it working the Xib was incorrect which I have fixed. And now I think that it can be used either way. I still swapped the grid type out, because of the added functionality of the flow type – Tempus Jun 22 '17 at 15:52
  • I updated the repo with your suggestions, and I fixed the xib files so now either one should work. – Tempus Jun 22 '17 at 16:04
  • It appears that this behavior has changed in Xcode 13.3 or after macOS 12.3: I have been using the first way for years, editing in Xcode 10.1 and building later versions up to 13.3. No issues. Now, suddenly, in 13.3 on 12.6.2, I get an exc in `makeItemWithIdentifier:forIndexPath:`, namely "invalid nib for identifier (…) - nib must contain exactly one top level NSCollectionViewItem instance". I was now forced to switch to the second way to make it work again. Still, a previously built version, also with 13.3, runs fine in 12.6.2. Very weird. – Thomas Tempelmann Dec 29 '22 at 21:00
0

I have it figured out.

And have made a github repo with a working version Working Version of Collection View Sample Application

First thing. Thanks to Willeke's catch of the way the original xib was setup I was able to get the Grid type working. But in the end the Grow view is a better type of view if you can make it do what you want, because it support sections, and distances between views etc. So eventhough I started out wanting to use the Grid type I am going to implement the Grow type in my app.

So I accomplished a single column view using the Grow type.

My Criteria for success are:

  • That it can support non-uniform view heights (Each custom view can have it's own height)
  • That there is a single column, and each custom view expands if the view size expands.

Onto the source code:

@interface ViewController ()
@property NSMutableArray *cellArray;
@property (weak) IBOutlet NSCollectionView *collectionView;

@end

@implementation ViewController

@synthesize cellArray;
@synthesize collectionView;

- (void)viewDidLoad {
    [super viewDidLoad];

    // Do any additional setup after loading the view.

    [collectionView registerClass:ItemOne.class forItemWithIdentifier:@"Item1"];
    [collectionView registerClass:ItemTwo.class forItemWithIdentifier:@"Item2"];

    cellArray = [@[@"Item1", @"Item2", @"Item1", @"Item2", @"Item1"] mutableCopy];
}


- (void)setRepresentedObject:(id)representedObject {
    [super setRepresentedObject:representedObject];

    // Update the view, if already loaded.
}

#pragma mark - NSCollectionViewDatasource -

- (NSInteger)numberOfSectionsInCollectionView:(NSCollectionView *)collectionView {
    return 1;
}

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

    // We are going to fake it a little.  Since there is only one section
    NSLog(@"Section: %ld, count: %ld", (long)section, [cellArray count]);

    return [cellArray count];
}

- (NSCollectionViewItem *)collectionView:(NSCollectionView *)collectionView
 itemForRepresentedObjectAtIndexPath:(NSIndexPath *)indexPath {

    NSLog(@"IndexPath: %@, Requested one: %ld", indexPath, [indexPath item]);
    NSLog(@"Identifier: %@", [cellArray objectAtIndex:[indexPath item]]);

    NSCollectionViewItem *theItem = [collectionView makeItemWithIdentifier:[cellArray objectAtIndex:[indexPath item]] forIndexPath:indexPath];

    theItem.representedObject = [cellArray objectAtIndex:[indexPath item]];

    return theItem;
}

#pragma mark - NSCollectionViewDelegate -
- (NSSize)collectionView:(NSCollectionView *)collectionView
              layout:(NSCollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    NSLog(@"%@", indexPath);

    NSSize size = NSMakeSize(438, 150);
    NSInteger width = 0;
    NSInteger height = 0;
    NSString *label = [cellArray objectAtIndex:[indexPath item]];

    NSRect collectionFrame = [collectionView frame];

    width = collectionFrame.size.width;

    // TODO: This needs to be based on the actual value of the view instead of hardcoding a number in.
    if ([label isEqualToString:@"Item1"]) {
        height = 114;
    } else if ([label isEqualToString:@"Item2"]) {
        height = 84;
    }

    size = NSMakeSize(width, height);

    return size;    
}

@end

And there you have it. The implementation wasn't too bad. Each of the Custom views that show up in the NSCollectionView are defined in there own NSCollectionViewItem and .xib file, so they are easily modifiable.

The only part that is brittle is where I am calculating the height of each view, and it is only brittle because I am being lazy in my implementation in the sample application. In the actual implementation I will dynamically grab them from the actual views, so that they aren't tied to a static number.

Tempus
  • 13
  • 5