NSCollectionViewAttributes 未正确应用于自定义 NSFlowLayout 子类
NSCollectionViewAttributes not getting applied correctly with custom NSFlowLayout subclass
我目前正在开发具有自定义布局的 NSCollectionView
。为此,我将 NSCollectionViewFlowLayout
子类化为 top-align 我的项目(它们都有固定的宽度,这使得算法非常简单)。我现在遇到的问题是 collection 视图中只有前九个项目可以正确显示,即 top-aligned.
最初显示 collection 视图时,这些前九个项目至少部分可见。向下滚动 collection 视图时出现的其他项目将在没有任何 top-align 的情况下绘制,只是 vertically-centered-per-row 作为 NSCollectionViewFlowLayout
的默认行为。通过广泛的日志记录,我可以验证我的布局仅提供 NSCollectionViewLayoutAttributes
正确修改为 top-alignment.
当我调整包含 NSCollectionView
的 window 的大小时,从而调整 collection 视图本身的大小时,它可见部分的所有项目都会突然正确显示。
我错过了什么?
这是我的自定义子类:
#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
您在 layoutAttributesForElementsInRect :
中缺少 attributesArray[i] = 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;
}
我目前正在开发具有自定义布局的 NSCollectionView
。为此,我将 NSCollectionViewFlowLayout
子类化为 top-align 我的项目(它们都有固定的宽度,这使得算法非常简单)。我现在遇到的问题是 collection 视图中只有前九个项目可以正确显示,即 top-aligned.
最初显示 collection 视图时,这些前九个项目至少部分可见。向下滚动 collection 视图时出现的其他项目将在没有任何 top-align 的情况下绘制,只是 vertically-centered-per-row 作为 NSCollectionViewFlowLayout
的默认行为。通过广泛的日志记录,我可以验证我的布局仅提供 NSCollectionViewLayoutAttributes
正确修改为 top-alignment.
当我调整包含 NSCollectionView
的 window 的大小时,从而调整 collection 视图本身的大小时,它可见部分的所有项目都会突然正确显示。
我错过了什么?
这是我的自定义子类:
#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
您在 layoutAttributesForElementsInRect :
中缺少 attributesArray[i] = 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;
}