需求:emoji的横向显示,在每页的最后需要显示一个删除按钮,如下图所示。
- 可以先阅读下苹果的文档 必须需要覆盖的方法 collectionViewContentSize layoutAttributesForElements(in:) layoutAttributesForItem(at:) layoutAttributesForSupplementaryView(ofKind:at:) (如果您的布局支持补充视图) layoutAttributesForDecorationView(ofKind:at:) (如果您的布局支持装饰视图)
当然还有当数据更改移动删除时需要覆盖的方法,目前没有此需求,先不谈,具体可以看文档O(∩_∩)O~~ ####prepareLayout 在collectionview显示或更新时总会先调用此方法,该方法的默认实现什么都不做。子类可以覆盖它,并使用它来设置数据结构或执行以后执行布局所需的任何初始计算。 ####清楚了流程,代码很简单
// BIEmojiCollectionViewLayout.h
#import <UIKit/UIKit.h>
@interface BICollectionViewFixedSizeLayout : UICollectionViewLayout
@property (nonatomic) CGSize itemSize;//item的size
@property (nonatomic) NSUInteger numberOfLinesPerPage;//每页的行数
@property (nonatomic) UIEdgeInsets contentInsets;//列表的内边距
@property (nonatomic) CGFloat minimalColumnSpacing;//item之间的间隙
@property (nonatomic) BOOL anchorLastItemPerPage;//是否每页最后一项是特殊的item,也就是需求中的删除按钮
- (BOOL)isLastItemPerPageAtIndexPath:(NSIndexPath *)indexPath;//获取当前indexPath是否是特殊的item
@end
//BIEmojiCollectionViewLayout.m
#import "BICollectionViewFixedSizeLayout.h"
#import "BICollectionViewFixedSizeLayout.h"
@implementation BICollectionViewFixedSizeLayout {
NSMutableDictionary<NSIndexPath *,UICollectionViewLayoutAttributes *> *_layoutAttributes;
CGSize _contentSize;
NSUInteger _numberOfColumnsPerPage;
}
#pragma mark - life cycle methods
- (instancetype)init {
self = [super init];
if (self) {
[self commonInit];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self commonInit];
}
return self;
}
#pragma mark - public methods
- (BOOL)isLastItemPerPageAtIndexPath:(NSIndexPath *)indexPath {
if (self.anchorLastItemPerPage) {
if ((indexPath.row + 1) % (_numberOfColumnsPerPage * _numberOfLinesPerPage) == 0 ||
indexPath.row == [self.collectionView numberOfItemsInSection:0] - 1) {
return YES;
}
}
return NO;
}
#pragma mark - private methods
- (void)commonInit {
_itemSize = CGSizeMake(40, 40);
_numberOfLinesPerPage = 3;
_anchorLastItemPerPage = NO;
_minimalColumnSpacing = 1.0;
_contentInsets = UIEdgeInsetsMake(5, 5, 5, 5);
_layoutAttributes = [NSMutableDictionary dictionary];
}
#pragma mark - custom UICollectionViewLayout methods
- (void)prepareLayout {
[super prepareLayout];
//clean up
[_layoutAttributes removeAllObjects];
_contentSize = CGSizeZero;
//caculate attibutes
CGFloat collectionViewWidth = CGRectGetWidth(self.collectionView.bounds);
CGFloat collectionViewHeight = CGRectGetHeight(self.collectionView.bounds);
CGFloat itemWidth = _itemSize.width;
CGFloat itemHeight = _itemSize.height;
_numberOfColumnsPerPage = (collectionViewWidth - _contentInsets.left - _contentInsets.right + _minimalColumnSpacing) / (_itemSize.width + _minimalColumnSpacing);
NSAssert([self.collectionView numberOfSections] == 1, @"number of sections should equal to 1.");
NSUInteger numberOfItemsPerPage = _numberOfColumnsPerPage * _numberOfLinesPerPage;
CGFloat columnSpacing = _numberOfColumnsPerPage == 1 ? 0.0 : (collectionViewWidth - _contentInsets.left - _contentInsets.right - _numberOfColumnsPerPage * itemWidth) / (_numberOfColumnsPerPage - 1);
CGFloat lineSpacing = _numberOfLinesPerPage == 1 ? 0.0 : (collectionViewHeight - _contentInsets.top - _contentInsets.bottom - _numberOfLinesPerPage * itemHeight) / (_numberOfLinesPerPage - 1);
NSUInteger numberOfRows = [self.collectionView numberOfItemsInSection:0];
for (NSUInteger row = 0; row < numberOfRows; row++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:0];
NSUInteger currentPage = floor(row / numberOfItemsPerPage);
NSUInteger currentItemOnPageIndex = (row % numberOfItemsPerPage);
//
CGFloat originX = 0.0;
CGFloat originY = 0.0;
if (_anchorLastItemPerPage && (row == numberOfRows - 1)) {
originX = (currentPage + 1) * collectionViewWidth - _contentInsets.right - itemWidth;
originY = collectionViewHeight - _contentInsets.bottom - itemHeight;
} else {
originX = _contentInsets.left + (row % _numberOfColumnsPerPage) * (columnSpacing + itemWidth) + currentPage * collectionViewWidth;
originY = _contentInsets.top + (currentItemOnPageIndex / _numberOfColumnsPerPage) * (lineSpacing + itemHeight);
}
// all attributes
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attributes.size = CGSizeMake(itemWidth, itemHeight);
attributes.frame = CGRectMake(originX, originY, itemWidth, itemHeight);
_layoutAttributes[indexPath] = attributes;
}
// content size
NSUInteger pages = ceil(numberOfRows / (1.0 * numberOfItemsPerPage));
_contentSize = CGSizeMake(collectionViewWidth * pages, collectionViewHeight);
}
- (CGSize)collectionViewContentSize {
return _contentSize;
}
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *attributes = [_layoutAttributes.allValues filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(UICollectionViewLayoutAttributes * _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
if (CGRectIntersectsRect(rect, evaluatedObject.frame)) {
return YES;
}
return NO;
}]];
return attributes;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
return _layoutAttributes[indexPath];
}
@end
复制代码
###参考使用示例
BICollectionViewFixedSizeLayout *_collectionViewFixedLayout = [[BICollectionViewFixedSizeLayout alloc] init];
_collectionViewFixedLayout.contentInsets = UIEdgeInsetsMake(10, 10, 10, 10);
_collectionViewFixedLayout.itemSize = CGSizeMake(36, 36);
_collectionViewFixedLayout.numberOfLinesPerPage = 3;
_collectionViewFixedLayout.minimalColumnSpacing = 10;
_collectionViewFixedLayout.anchorLastItemPerPage = YES;
[self.collectionView setCollectionViewLayout:_collectionViewFixedLayout animated:NO];
复制代码
到此,layout的代码已经写完,需要注意的是在使用anchorLastItemPerPage 时 numberOfItemsInSection需要在数据源的count基础上增加page页数,在使用时也要注意获取到正确的数据index来使用。 简单的应用场景,整理记录一下,后续可能会进行优化?。