自定義CollectionView UICollectionViewLayout實現橫向佈局分頁Emoji

納蘭若水發表於2017-12-22

需求:emoji的橫向顯示,在每頁的最後需要顯示一個刪除按鈕,如下圖所示。

最終視覺效果

當然還有當資料更改移動刪除時需要覆蓋的方法,目前沒有此需求,先不談,具體可以看文件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來使用。 簡單的應用場景,整理記錄一下,後續可能會進行優化?。

相關文章