使用UICollectionView简单实现多个视图的左对齐和右对齐排版
通常做法
对多个视图进行左对齐或者右对齐的排版,有几种实现方式。
比如可以根据视图容器的宽度,然后逐行计算此行可以放下几个视图,超过换行。这种方式需要复杂的计算,容易出错。
使用UIColletionView的UICollectionViewFlowLayout
下面,介绍一种比较简易的实现方式,便是使用UICollectionView。
具体则是在 UICollectionViewFlowLayout
的基础上,设计子类,然后重写以下方法
- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect; // return an array layout attributes instances for all the views in the given rect
原理
在 layoutAttributesForElementsInRect
方法里,可以拿到UIColletionView的默认排版下的所有视图的数组,此数组存储了 UICollectionViewLayoutAttributes
对象,对应每个项的详情信息,包括已经排版完成的frame,其所在的indexPath等。
拿到数组后,在左对齐的情况下,则顺序遍历,然后重新设置每个项的x轴位置,则完成了排版。
同样,在右对齐的情况下,则倒序遍历数组,重新设置每个项的x轴位置,完成排版。
具体代码
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *originalAttributes = [super layoutAttributesForElementsInRect:rect];
NSMutableArray *updatedAttributes = [NSMutableArray arrayWithArray:originalAttributes];
if (self.rightAligned) {
// right aligned
for (NSInteger index = originalAttributes.count - 1; index >= 0; index--) {
UICollectionViewLayoutAttributes * attributes = originalAttributes[index];
if (!attributes.representedElementKind) {
NSUInteger index = [updatedAttributes indexOfObject:attributes];
updatedAttributes[index] = [self layoutAttributesForItemRightAlignedAtIndexPath:attributes.indexPath itemCount:originalAttributes.count];
}
}
} else {
// left aligned
for (UICollectionViewLayoutAttributes *attributes in originalAttributes) {
if (!attributes.representedElementKind) {
NSUInteger index = [updatedAttributes indexOfObject:attributes];
updatedAttributes[index] = [self layoutAttributesForItemAtIndexPath:attributes.indexPath];
}
}
}
return updatedAttributes;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemRightAlignedAtIndexPath:(NSIndexPath *)indexPath itemCount:(NSInteger)itemCount
{
UICollectionViewLayoutAttributes* currentItemAttributes = [[super layoutAttributesForItemAtIndexPath:indexPath] copy];
UIEdgeInsets sectionInset = [self evaluatedSectionInsetForItemAtIndex:indexPath.section];
BOOL isLastItemInSection = indexPath.item == itemCount - 1;
if (isLastItemInSection) {
[currentItemAttributes rightAlignFrameWithSectionInset:sectionInset collectionViewWidth:self.collectionView.frame.size.width];
return currentItemAttributes;
}
// the total width without left inset and right inset
CGFloat layoutWidth = CGRectGetWidth(self.collectionView.frame) - sectionInset.left - sectionInset.right;
// reversed cycle
NSIndexPath* previousIndexPath = [NSIndexPath indexPathForItem:indexPath.item+1 inSection:indexPath.section];
CGRect previousFrame = [self layoutAttributesForItemRightAlignedAtIndexPath:previousIndexPath itemCount:itemCount].frame;
CGFloat previousFrameLeftPoint = previousFrame.origin.x;
CGRect currentFrame = currentItemAttributes.frame;
CGRect strecthedCurrentFrame = CGRectMake(sectionInset.left,
currentFrame.origin.y,
layoutWidth,
currentFrame.size.height);
// if the current frame, once left aligned to the left and stretched to the full collection view
// widht intersects the previous frame then they are on the same line
BOOL isLastItemInRow = !CGRectIntersectsRect(previousFrame, strecthedCurrentFrame);
if (isLastItemInRow) {
// make sure the last item on a line is right aligned
[currentItemAttributes rightAlignFrameWithSectionInset:self.sectionInset collectionViewWidth:self.collectionView.frame.size.width];
return currentItemAttributes;
}
// reset the frame of current item
CGRect frame = currentItemAttributes.frame;
frame.origin.x = previousFrameLeftPoint - [self evaluatedMinimumInteritemSpacingForSectionAtIndex:indexPath.section] - frame.size.width;
currentItemAttributes.frame = frame;
return currentItemAttributes;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes* currentItemAttributes = [[super layoutAttributesForItemAtIndexPath:indexPath] copy];
UIEdgeInsets sectionInset = [self evaluatedSectionInsetForItemAtIndex:indexPath.section];
BOOL isFirstItemInSection = indexPath.item == 0;
CGFloat layoutWidth = CGRectGetWidth(self.collectionView.frame) - sectionInset.left - sectionInset.right;
if (isFirstItemInSection) {
[currentItemAttributes leftAlignFrameWithSectionInset:sectionInset];
return currentItemAttributes;
}
NSIndexPath* previousIndexPath = [NSIndexPath indexPathForItem:indexPath.item-1 inSection:indexPath.section];
CGRect previousFrame = [self layoutAttributesForItemAtIndexPath:previousIndexPath].frame;
CGFloat previousFrameRightPoint = previousFrame.origin.x + previousFrame.size.width;
CGRect currentFrame = currentItemAttributes.frame;
CGRect strecthedCurrentFrame = CGRectMake(sectionInset.left,
currentFrame.origin.y,
layoutWidth,
currentFrame.size.height);
// if the current frame, once left aligned to the left and stretched to the full collection view
// widht intersects the previous frame then they are on the same line
BOOL isFirstItemInRow = !CGRectIntersectsRect(previousFrame, strecthedCurrentFrame);
if (isFirstItemInRow) {
// make sure the first item on a line is left aligned
[currentItemAttributes leftAlignFrameWithSectionInset:sectionInset];
return currentItemAttributes;
}
CGRect frame = currentItemAttributes.frame;
frame.origin.x = previousFrameRightPoint + [self evaluatedMinimumInteritemSpacingForSectionAtIndex:indexPath.section];
currentItemAttributes.frame = frame;
return currentItemAttributes;
}