假设我在集合视图中有一个项目,该项目将在第一行的集合视图中居中。
并且对于多个项目,所有这些项目将在集合视图中水平分布,它们之间有适当的间距。
如果集合视图的大小发生变化,项目之间的间距将同时更改以适应集合视图的新大小。
的默认行为NSCollectionView
在左侧对齐项目,多个项目之间没有间距。
我应该使用layoutManager
集合视图的图层来布局项目吗?
由于我使用数据绑定来提供项目,因此插入约束似乎并不容易。
假设我在集合视图中有一个项目,该项目将在第一行的集合视图中居中。
并且对于多个项目,所有这些项目将在集合视图中水平分布,它们之间有适当的间距。
如果集合视图的大小发生变化,项目之间的间距将同时更改以适应集合视图的新大小。
的默认行为NSCollectionView
在左侧对齐项目,多个项目之间没有间距。
我应该使用layoutManager
集合视图的图层来布局项目吗?
由于我使用数据绑定来提供项目,因此插入约束似乎并不容易。
最直接的方法是子类化NSCollectionViewFlowLayout
。该布局几乎是您想要的 - 它始终具有您正在寻找的相同数量的行和每行项目:您只希望它们居中。
主要思想是NSCollectionViewFlowLayout
获取每个项目的框架,从总宽度中减去这些宽度,然后更新框架,使它们均匀分布。
作为概述,这些是步骤:
覆盖prepareLayout
以计算当前布局中的列数以及每个元素(和边缘)之间所需的空白。这是在这里完成的,因此我们只需要计算一次值。
覆盖layoutAttributesForElementsInRect
。在这里,获取NSCollectionViewLayoutAttributes
给定矩形中每个项目的 ,并根据项目所在的列和上面计算的网格间距调整原点的 x 位置。返回新属性。
覆盖shouldInvalidateLayoutForBoundsChange
以始终返回YES
,因为我们需要在边界更改时重新计算所有内容。
我有一个工作示例应用程序,在这里演示了这一点:
https://github.com/demitri/CenteringCollectionViewFlowLayout
但这是完整的实现:
//
// CenteredFlowLayout.m
//
// Created by Demitri Muna on 4/10/19.
//
#import "CenteredFlowLayout.h"
#import <math.h>
@interface CenteredFlowLayout()
{
CGFloat itemWidth; // width of item; assuming all items have the same width
NSUInteger nColumns; // number of possible columns based on item width and section insets
CGFloat gridSpacing; // after even distribution, space between each item and edges (if row full)
NSUInteger itemCount;
}
- (NSUInteger)columnForIndexPath:(NSIndexPath*)indexPath;
@end
#pragma mark -
@implementation CenteredFlowLayout
- (void)prepareLayout
{
[super prepareLayout];
id<NSCollectionViewDelegateFlowLayout,NSCollectionViewDataSource> delegate = (id<NSCollectionViewDelegateFlowLayout,NSCollectionViewDataSource>)self.collectionView.delegate;
NSCollectionView *cv = self.collectionView;
if ([delegate collectionView:cv numberOfItemsInSection:0] == 0)
return;
itemCount = [delegate collectionView:cv numberOfItemsInSection:0];
// Determine the maximum number of items per row (i.e. number of columns)
//
// Get width of first item (assuming all are the same)
// Get the attributes returned by NSCollectionViewFlowLayout, not our method override.
NSUInteger indices[] = {0,0};
NSCollectionViewLayoutAttributes *attr = [super layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathWithIndexes:indices length:2]];
itemWidth = attr.size.width;
NSEdgeInsets insets;
if ([delegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)])
insets = [delegate collectionView:cv layout:self insetForSectionAtIndex:0];
else
insets = self.sectionInset;
// calculate the number of columns that can fit excluding minimumInteritemSpacing:
nColumns = floor((cv.frame.size.width - insets.left - insets.right) / itemWidth);
// is there enough space for minimumInteritemSpacing?
while ((cv.frame.size.width
- insets.left - insets.right
- (nColumns*itemWidth)
- (nColumns-1)*self.minimumInteritemSpacing) < 0) {
if (nColumns == 1)
break;
else
nColumns--;
}
if (nColumns > itemCount)
nColumns = itemCount; // account for a very wide window and few items
// Calculate grid spacing
// For a centered layout, all spacing (left inset, right inset, space between items) is equal
// unless a row has fewer items than columns (but they are still aligned with that grid).
//
CGFloat totalWhitespace = cv.bounds.size.width - (nColumns * itemWidth);
gridSpacing = floor(totalWhitespace/(nColumns+1)); // e.g.: | [x] [x] |
}
- (NSUInteger)columnForIndexPath:(NSIndexPath*)indexPath
{
// given an index path in a collection view, return which column in the grid the item appears
NSUInteger index = [indexPath indexAtPosition:1];
NSUInteger row = (NSUInteger)floor(index/nColumns);
return (index - (nColumns * row));
}
- (NSArray<__kindof NSCollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(NSRect)rect
{
// We do not need to modify the number of rows/columns that NSCollectionViewFlowLayout
// determines, we just need to adjust the x position to keep them evenly distributed horizontally.
if (nColumns == 0) // prepareLayout not yet called
return [super layoutAttributesForElementsInRect:rect];
NSArray *attributes = [super layoutAttributesForElementsInRect:rect];
if (attributes.count == 0)
return attributes;
for (NSCollectionViewLayoutAttributes *attr in attributes) {
NSUInteger col = [self columnForIndexPath:attr.indexPath]; // column number
NSRect newFrame = NSMakeRect(floor((col * itemWidth) + gridSpacing * (1 + col)),
attr.frame.origin.y,
attr.frame.size.width,
attr.frame.size.height);
attr.frame = newFrame;
}
return attributes;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(NSRect)newBounds
{
return YES;
}
@end
您可以创建 NSCollectionViewLayout 的子类并相应地实现 layoutAttributes 方法。