Home > Software design >  NSCollectionViewAttributes not getting applied correctly with custom NSFlowLayout subclass
NSCollectionViewAttributes not getting applied correctly with custom NSFlowLayout subclass

Time:02-15

I'm currently working on an NSCollectionView with a custom layout. For that purpose, I subclassed NSCollectionViewFlowLayout to top-align my items (which all have a fixed width what made the algorithm pretty easy). The problem I have now is that only the first nine items in my collection view get displayed correctly, that is top-aligned.

These top nine items are at least partially visible when the collection view is initially displayed. The other items that appear when scrolling down the collection view will be drawn without any top-alignment, just vertically-centered-per-row as the default behaviour of NSCollectionViewFlowLayout. By extensive logging I could verify that my Layout only provides NSCollectionViewLayoutAttributes correctly modified for top-alignment.

When I resize the window containing the NSCollectionView, thus resizing the collection view itself, all items in the visible part of it will suddenly be displayed correctly.

What am I missing?

Here comes my custom subclass:

#import "MyCollectionViewLayout.h"

@interface MyCollectionViewLayout ()

@property (nonatomic, strong) NSMutableDictionary<NSIndexPath *, NSCollectionViewLayoutAttributes *> *attributesCache;
@property NSInteger numberOfItemsPerRow;
@property NSInteger numberOfItemsTotal;

@end


@implementation MyCollectionViewLayout

- (void)prepareLayout {
    NSLog(@"Preparing layout");
    [super prepareLayout];
    self.itemSize = CGSizeMake(100, 138);
    self.minimumInteritemSpacing = 5;
    self.minimumLineSpacing = 5;
    self.sectionInset = NSEdgeInsetsMake(10, 10, 10, 10);
    self.attributesCache = @{}.mutableCopy;
    self.numberOfItemsTotal = [self.collectionView.dataSource collectionView:self.collectionView numberOfItemsInSection:0];
    
    NSInteger numberOfItemsPerRow = 1;
    CGFloat computedSize = self.itemSize.width;
    CGFloat usableWidth = self.collectionView.frame.size.width - (self.sectionInset.right   self.sectionInset.left);
    repeat: {
        computedSize  = self.minimumInteritemSpacing   self.itemSize.width;
        if (computedSize < usableWidth) {
            numberOfItemsPerRow  ;
            goto repeat;
        }
    }
    self.numberOfItemsPerRow = numberOfItemsPerRow;
}

#pragma mark NSCollectionViewFlowLayout override

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSLog(@"Getting layout attributes for rect: %f, %f, %f, %f", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
    NSArray *attributesArray = [super layoutAttributesForElementsInRect:rect].mutableCopy;
    for (int i = 0; i < attributesArray.count; i  ) {
        NSCollectionViewLayoutAttributes *attributes = attributesArray[i];
        NSLog(@"Forwarding attribute request for %@", attributes.indexPath);
        attributes = [self layoutAttributesForItemAtIndexPath:attributes.indexPath];
    }
    return attributesArray;
}

- (NSCollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
    if (!self.attributesCache[indexPath]) NSLog(@"");
    NSLog(@"Getting layout attributes for %@", indexPath);
    if (!self.attributesCache[indexPath]) {
        NSLog(@ "Not cached yet, caching full row");
        [self computeAndCacheAttributesForRowContaining:indexPath];
    }
    return self.attributesCache[indexPath];
}

#pragma mark Private instance methods

- (void)computeAndCacheAttributesForRowContaining:(NSIndexPath *)indexPath {
    NSDictionary<NSIndexPath *, NSCollectionViewLayoutAttributes *> *allAttributesInRowByPath = [self getAllAttributesInRowContaining:indexPath];
    CGFloat minY = CGFLOAT_MAX;
    for (NSIndexPath *path in allAttributesInRowByPath) {
        if (allAttributesInRowByPath[path].frame.origin.y < minY) {
            minY = allAttributesInRowByPath[path].frame.origin.y;
        }
    }
    for (NSIndexPath *path in allAttributesInRowByPath) {
        if (indexPath.item == 9) {
            
        }
        NSLog(@"Changing frame for indexPath %@", path);
        NSRect frame = allAttributesInRowByPath[path].frame;
        NSLog(@"Previous y Position: %f", frame.origin.y);
        NSLog(@"New y Position:      %f", minY);
        frame.origin.y = minY;
        allAttributesInRowByPath[path].frame = frame;
    }
    [self.attributesCache addEntriesFromDictionary:allAttributesInRowByPath];
}

- (NSDictionary<NSIndexPath *, NSCollectionViewLayoutAttributes *> *)getAllAttributesInRowContaining:(NSIndexPath *)indexPath {
    NSMutableDictionary<NSIndexPath *, NSCollectionViewLayoutAttributes *> *attributesToReturn = @{}.mutableCopy;
    NSInteger index = indexPath.item;
    NSInteger firstIndex = index - (index % self.numberOfItemsPerRow);
    NSIndexPath *path;
    for (index = firstIndex; (index < firstIndex   self.numberOfItemsPerRow) && (index < self.numberOfItemsTotal); index  ) {
        path = [NSIndexPath indexPathForItem:index inSection:indexPath.section];
        [attributesToReturn setObject:[super layoutAttributesForItemAtIndexPath:path].copy forKey:path];
    }
    return attributesToReturn.copy;
}

@end

CodePudding user response:

You're missing attributesArray[i] = attributes; in layoutAttributesForElementsInRect : and are returning the unmodified attributes.

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSLog(@"Getting layout attributes for rect: %f, %f, %f, %f", rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
    NSMutableArray *attributesArray = [super layoutAttributesForElementsInRect:rect].mutableCopy;
    for (int i = 0; i < attributesArray.count; i  ) {
        NSCollectionViewLayoutAttributes *attributes = attributesArray[i];
        NSLog(@"Forwarding attribute request for %@", attributes.indexPath);
        attributes = [self layoutAttributesForItemAtIndexPath:attributes.indexPath];
        attributesArray[i] = attributes; // <---
    }
    return attributesArray;
}
  • Related