默认的流式布局会自动换行,以便使区段中的条目能够使用集合视图的长度或宽度,但这样做出来的视图只能在一个方向上滚动。如果愿意多做一些数学运算,就可以编写自定义的布局子类,从而实现不会换行的双向滚动视图。实现该功能所需要的运算律比较大,而且不是特别容易。
完全定制了UICollectionViewFlowLayout的子类,覆写了collectionViewContentSize及layoutAttributesForItemAtIndexPath:方法,以便手工摆放每个条目。这种实现方式完全考虑到所有与间隔有关的请求以及回调方法。相比之下,普通的流式布局只会在满足多个最小值的前提下,试着把条目合适摆放到屏幕上。
typedef NS_ENUM(NSUInteger, GridRowAlignment) {
GridRowAlignmentNone,
GridRowAlignmentTop,
GridRowAlignmentCenter,
GridRowAlignmentBottom,
};
@interface GridLayout : UICollectionViewFlowLayout
@property (nonatomic,assign) GridRowAlignment aligment;
@end
@implementation GridLayout
- (BOOL)usesIndividualItemSizing
{
return [self.collectionView.delegate respondsToSelector:@selector(collectionView:layout:sizeForItemAtIndexPath:)];
}
- (CGSize)sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
CGSize itemSize = self.itemSize;
if ([self usesIndividualItemSizing]) {
itemSize = [(id )self.collectionView.delegate collectionView:self.collectionView layout:self sizeForItemAtIndexPath:indexPath];
}
return itemSize;
}
- (BOOL)usesIndividualInsets
{
return [self.collectionView.delegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)];
}
- (UIEdgeInsets)insetsForSection:(NSInteger)section
{
UIEdgeInsets insets = self.sectionInset;
if ([self usesIndividualInsets]) {
insets = [(id)self.collectionView.delegate collectionView:self.collectionView layout:self insetForSectionAtIndex:section];
}
return insets;
}
- (BOOL)usesIndividualItemSpacing
{
return [self.collectionView.delegate respondsToSelector:@selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:)];
}
- (CGFloat)itemSpacingForSection:(NSInteger)section
{
CGFloat spacing = self.minimumInteritemSpacing;
if ([self usesIndividualItemSpacing]) {
spacing = [(id )self.collectionView.delegate collectionView:self.collectionView layout:self minimumInteritemSpacingForSectionAtIndex:section];
}
return spacing;
}
- (CGFloat)maxItemHeightForSection:(NSInteger)section
{
CGFloat maxHeight = 0;
NSInteger numberOfItems = [self.collectionView numberOfItemsInSection:section];
for (int i = 0; i< numberOfItems; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:section];
CGSize itemSize = [self sizeForItemAtIndexPath:indexPath];
maxHeight = MAX(maxHeight, itemSize.height);
}
return maxHeight;
}
- (CGFloat)fullWidthForSection:(NSInteger)section
{
UIEdgeInsets insets = [self insetsForSection:section];
CGFloat horizontalInsetExtent = insets.left + insets.right;
CGFloat collectiveWith = horizontalInsetExtent;
NSInteger numberOfItems = [self.collectionView numberOfItemsInSection:section];
for (int i = 0; i < numberOfItems; i ++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:section];
CGSize itemSize = [self sizeForItemAtIndexPath:indexPath];
collectiveWith += itemSize.width;
collectiveWith += [self itemSpacingForSection:section];
}
collectiveWith -= [self itemSpacingForSection:section];
return collectiveWith;
}
- (CGSize)fullSizeForSection:(NSInteger)section
{
CGFloat headerExtent = (self.scrollDirection == UICollectionViewScrollDirectionHorizontal) ? self.headerReferenceSize.width : self.headerReferenceSize.height;
CGFloat footerExtent = (self.scrollDirection == UICollectionViewScrollDirectionHorizontal) ? self.footerReferenceSize.width : self.footerReferenceSize.height;
UIEdgeInsets insets = [self insetsForSection:section];
CGFloat verticalInsetExtent = insets.top + insets.bottom;
CGFloat maxHeight = [self maxItemHeightForSection:section];
CGFloat fullHeight = headerExtent + footerExtent + verticalInsetExtent + maxHeight;
CGFloat fullWith = [self fullWidthForSection:section];
return CGSizeMake(fullWith, fullHeight);
}
- (CGFloat)horizontalInsetForItemAtIndexPath:(NSIndexPath *)indexPath
{
UIEdgeInsets insets = [self insetsForSection:indexPath.section];
CGFloat horizontalOffset = insets.left;
for (int i = 0; i < indexPath.item; i ++) {
NSIndexPath *indexPath1 = [NSIndexPath indexPathForItem:i inSection:indexPath.section];
CGSize itemSize = [self sizeForItemAtIndexPath:indexPath1];
horizontalOffset += (itemSize.width + [self itemSpacingForSection:indexPath.section]);
}
return horizontalOffset;
}
- (CGFloat)verticalInsetForItemAtIndexPath:(NSIndexPath *)indexPath
{
CGSize thisItemSize = [self sizeForItemAtIndexPath:indexPath];
CGFloat verticlOffset = 0;
for (int i = 0; i < indexPath.section; i ++) {
verticlOffset += [self fullSizeForSection:i].height;
}
CGFloat headerExtent = (self.scrollDirection == UICollectionViewScrollDirectionHorizontal) ? self.headerReferenceSize.width : self.headerReferenceSize.height;
verticlOffset +=headerExtent;
UIEdgeInsets insets = [self insetsForSection:indexPath.section];
verticlOffset += insets.top;
CGFloat maxHeigth = [self maxItemHeightForSection:indexPath.section];
CGFloat fullHeight = (maxHeigth - thisItemSize.height);
CGFloat midHeight = fullHeight / 2;
switch (self.aligment) {
case GridRowAlignmentNone:
case GridRowAlignmentTop:
break;
case GridRowAlignmentCenter:
verticlOffset += midHeight;
break;
case GridRowAlignmentBottom:
verticlOffset += fullHeight;
break;
default:
break;
}
return verticlOffset;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
CGSize thisItemSize = [self sizeForItemAtIndexPath:indexPath];
CGFloat verticalOffset = [self verticalInsetForItemAtIndexPath:indexPath];
CGFloat horizontalOffset = [self horizontalInsetForItemAtIndexPath:indexPath];
if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
attributes.frame = CGRectMake(horizontalOffset, verticalOffset, thisItemSize.width, thisItemSize.height);
}else{
attributes.frame = CGRectMake(verticalOffset, horizontalOffset, thisItemSize.width, thisItemSize.height);
}
return attributes;
}
- (CGSize)collectionViewContentSize
{
NSInteger sections = self.collectionView.numberOfSections;
CGFloat maxWith = 0;
CGFloat collectiveHeight = 0;
for (int i=0; i < sections; i++) {
CGSize sectionSize = [self fullSizeForSection:i];
collectiveHeight += sectionSize.height;
maxWith = MAX(maxWith, sectionSize.width);
}
if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
return CGSizeMake(maxWith, collectiveHeight);
}else
{
return CGSizeMake(collectiveHeight, maxWith);
}
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray *attributes = [NSMutableArray array];
for (NSInteger section = 0; section < self.collectionView.numberOfSections; section++) {
for (NSInteger item = 0; item < [self.collectionView numberOfItemsInSection:section]; item ++ ) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section];
UICollectionViewLayoutAttributes * layout = [self layoutAttributesForItemAtIndexPath:indexPath];
[attributes addObject:layout];
}
}
return attributes;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
@end
布局对象要把每个元素的排布位置都计算出来。但是它不会使用行间距属性,因为该属性只在自动换行的前提下才有意义。我们制作的网格状布局,绝不会自动换行,所以会彻底胡洛这个属性。
还有一个名为alignment属性。这个新的属性决定了表格中的每一行是顶端对齐、底端对齐,还是居中对齐。它会查询一整行的总体高度,然后可以会把比这个高度矮的条目移动一段距离。