Как реализовать NSCollectionView с центрированными элементами с самоизменяющимся размером полей?
Предположим, что у меня есть один элемент в представлении коллекции, элемент будет центрирован в представлении коллекции в первой строке.
А с несколькими элементами все эти элементы будут распределены горизонтально в виде коллекции с соответствующим интервалом между ними.
Если размер представления коллекции изменился, интервал между элементами будет изменен одновременно, чтобы соответствовать новому размеру представления коллекции.
Поведение по умолчанию NSCollectionView
выравнивает элементы слева, без промежутков между несколькими элементами.
Должен ли я использовать layoutManager
слоя представления коллекции для размещения элементов?
Поскольку я использую привязку данных для предоставления элементов, кажется, что вставить ограничения нелегко.
1 ответ
Самый простой способ - это создать подкласс NSCollectionViewFlowLayout
, Этот макет - почти то, что вам нужно - он всегда будет иметь одинаковое количество строк и элементов в строке, которое вы ищете: вы просто хотите, чтобы они были отцентрированы.
Основная идея состоит в том, чтобы взять кадры, которые NSCollectionViewFlowLayout
подходит для каждого элемента, вычтите эти ширины из общей ширины, а затем обновите кадры, чтобы они распределялись равномерно.
В качестве обзора, эти шаги:
Override
prepareLayout
рассчитать количество столбцов в текущем макете и пробел, необходимый между каждым элементом (и ребрами). Это сделано здесь, так что нам нужно рассчитать значения только один раз.Override
layoutAttributesForElementsInRect
, Здесь, получитьNSCollectionViewLayoutAttributes
для каждого элемента в данном прямоугольнике и отрегулируйте положение x начала координат на основе столбца, в котором находится элемент, и интервал сетки, рассчитанный выше. Вернуть новые атрибуты.Override
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; // wdith 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;
//CGFloat inset = self.sectionInset.left;
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.