仿UITableView 對(duì)UIColletionView進(jìn)行改造 - 流式布局

寫(xiě)這篇文章的目的 一個(gè)原因是想把在開(kāi)發(fā)中遇到的一些常見(jiàn)的問(wèn)題通過(guò)文章的形式記錄和保留 以后可以沒(méi)事上來(lái)看看 一個(gè)原因是因?yàn)閷?duì)TableView不能任意隨我定制“深?lèi)和唇^” 特意對(duì)CollectionView進(jìn)行改造讓他能被我為所欲為 甚至替代UITableView
代碼地址 https://github.com/evernoteHW/UICollectionViewLayoutDemo/tree/master/UICollectionViewLayoutDemo
簡(jiǎn)單描述下我的思路 方便剁手黨 特意貼出代碼

1 利用Runtime機(jī)制擴(kuò)展屬性

創(chuàng)建UICollectionView 類(lèi)目 Category 添加兩個(gè)屬性headerView和footerView

- (UIView *)hw_collectionHeaderView
{
    return objc_getAssociatedObject(self, _cmd);
}
- (void)setHw_collectionHeaderView:(UIView *)hw_collectionHeaderView
{
    [self.hw_collectionHeaderView removeFromSuperview];
    objc_setAssociatedObject(self, @selector(hw_collectionHeaderView), hw_collectionHeaderView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    [self addSubview:hw_collectionHeaderView];
    [self.collectionViewLayout invalidateLayout];
}
- (UIView *)hw_collectionFooterView
{
    return objc_getAssociatedObject(self, _cmd);
}
- (void)setHw_collectionFooterView:(UIView *)hw_collectionFooterView
{
    [self.hw_collectionFooterView removeFromSuperview];
    objc_setAssociatedObject(self, @selector(hw_collectionFooterView), hw_collectionFooterView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    [self addSubview:hw_collectionFooterView];
    [self.collectionViewLayout invalidateLayout];
}

2 子類(lèi)化 SubClass UICollectionViewLayout

第一步 改寫(xiě)prepareLayout </br>

第一小步 設(shè)定Item大小

for (NSInteger item = 0; item < itemCount; item++) {
            
    NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section];
    UICollectionViewLayoutAttributes *itemAttributes =
    [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    
    CGFloat item_x = self.itemInsets.left;
    CGFloat item_y = top + itemHeight + self.itemInsets.top;
    CGFloat item_width = self.itemSize.width - self.itemInsets.left - self.itemInsets.right;
    CGFloat item_height = self.itemSize.height - self.itemInsets.top - self.itemInsets.bottom;
    
    itemAttributes.frame = CGRectMake(item_x,item_y, item_width, item_height);
    cellLayoutInfo[indexPath] = itemAttributes;
    
    itemHeight += (self.itemSize.height);
}
        

第二小步 設(shè)定Header大小

    NSIndexPath *headerIndexPath = [NSIndexPath indexPathForItem:0 inSection:section];
    UICollectionViewLayoutAttributes *headerAttributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:HWCollectionElementKindSectionHeader withIndexPath:headerIndexPath];
    CGFloat headerHeight = self.headerHeight;
    if ([self.layoutDelegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)]) {
        headerHeight = [self.layoutDelegate collectionView:self.collectionView layout:self referenceSizeForHeaderInSection:section];
    }
        
    headerAttributes.frame = CGRectMake(0, top, self.itemSize.width, headerHeight);
    headerLayoutInfo[headerIndexPath] = headerAttributes;
    top += headerHeight;
        

第三小步 設(shè)定Footer大小

NSIndexPath *footerIndexPath = [NSIndexPath indexPathForItem:itemCount - 1 inSection:section];
UICollectionViewLayoutAttributes *footerAttributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:HWCollectionElementKindSectionFooter withIndexPath:footerIndexPath];
    
CGFloat footerHeight = self.footerHeight;
if ([self.layoutDelegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)]) {
    footerHeight = [self.layoutDelegate collectionView:self.collectionView layout:self referenceSizeForFooterInSection:section];
}
    
footerAttributes.frame = CGRectMake(0, top, self.itemSize.width, footerHeight);
footerLayoutInfo[footerIndexPath] = footerAttributes;
    
top += footerHeight;

第四小步 設(shè)定 裝飾視圖(用處:設(shè)定組與組的背景) 這個(gè)比較特殊或者分割線(xiàn) 比如類(lèi)似這樣的 組的背景可能是個(gè)圖片可能組頭視圖和組所有的cell公用一個(gè)背景圖 這個(gè)比較特殊

NSIndexPath *decorationIndexPath = [NSIndexPath indexPathForItem:0 inSection:section];
UICollectionViewLayoutAttributes *decorationAttributes = [UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:HWCollectionElementKindSectionDecoration withIndexPath:decorationIndexPath];
CGFloat decorationHeight = CGRectGetMaxY(footerAttributes.frame) - CGRectGetMinY(headerAttributes.frame);
decorationAttributes.frame = CGRectMake(0, CGRectGetMinY(headerAttributes.frame), self.itemSize.width, decorationHeight);
decorationAttributes.zIndex = -1;
decorationLayoutInfo[decorationIndexPath] = decorationAttributes;
        

這里需要說(shuō)明的是 zIndex = -1 總是處于最底端

第五小步 確定collectionHeaderView和collectionFooterView 位置
和普通的組頭部不同 這個(gè)可以類(lèi)似對(duì)比UITableView的tableHeaderView和tableFooterView 也很簡(jiǎn)單 如下
collectionHeaderView

CGFloat top = 0.0;   
    if (self.collectionView.hw_collectionHeaderView) {
    top += self.collectionView.hw_collectionHeaderView.bounds.size.height;
}

CollectionFooterView

if (self.collectionView.hw_collectionFooterView) {
    self.collectionView.hw_collectionFooterView.frame = CGRectMake(self.collectionView.hw_collectionFooterView.frame.origin.x, top , self.collectionView.hw_collectionFooterView.frame.size.width, self.collectionView.hw_collectionFooterView.frame.size.height)
    ;
    top += self.collectionView.hw_collectionFooterView.bounds.size.height;
}
    

第六小步 將設(shè)定的大小全部存儲(chǔ)起來(lái)
我和別人的比較特殊很多使用二位數(shù)組的方式 我是用字典 字典這樣的存儲(chǔ)方式的好處是我可以任意添加一個(gè)Kind類(lèi)型 然后設(shè)置對(duì)于的Attribute大小 在任何地方加入都可以 最好存起來(lái) 還有一個(gè)好處是會(huì)在下面的 layoutAttributesForElementsInRect中體現(xiàn)出來(lái)

 newLayoutInfo[HWLayoutItemCellKind] = cellLayoutInfo;
 newLayoutInfo[HWLayoutHeaderKind] = headerLayoutInfo;
 newLayoutInfo[HWLayoutFooterKind] = footerLayoutInfo;
 newLayoutInfo[HWLayoutDecorationKind] = decorationLayoutInfo;   
 self.layoutInfo = newLayoutInfo;

第二步 確定在Rect范圍內(nèi)返回的Attribute數(shù)組 </br>

第二個(gè)好處是 這個(gè)方法是通用的不管你PrePareLayout方法怎么干 這些都一成不變的 這個(gè)方法的含義是在Rect范圍內(nèi) 意思是說(shuō) 只是返回Recf范圍內(nèi)的Attribute數(shù)組 不是所有的 這個(gè)注意一下

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSMutableArray *allAttributes = [NSMutableArray arrayWithCapacity:self.layoutInfo.count];
    
    [self.layoutInfo enumerateKeysAndObjectsUsingBlock:^(NSString *elementIdentifier,
                                                         NSDictionary *elementsInfo,
                                                         BOOL *stop) {
        [elementsInfo enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath,
                                                          UICollectionViewLayoutAttributes *attributes,
                                                          BOOL *innerStop) {
            if (CGRectIntersectsRect(rect, attributes.frame)) {
                [allAttributes addObject:attributes];
            }
        }];
    }];
    return allAttributes;
}

第三步 確定 collectionViewContentSize </br>

之前設(shè)定的 第三個(gè)好處是 我再取得 collectionViewContentSize 取得最后一個(gè)Attribute 拿到 CGRectGetMaxY 如果有我之前通過(guò)類(lèi)目方法擴(kuò)展的collectionHeaderView 做一個(gè)判斷即可


- (CGSize)collectionViewContentSize
{
    NSInteger sectionCount = [self.collectionView numberOfSections];
    if (sectionCount <= 0) return [super collectionViewContentSize];
    NSInteger numberOfItemsInSection = [self.collectionView numberOfItemsInSection:sectionCount - 1];
    
    UICollectionViewLayoutAttributes *footAttributes = nil;
    NSDictionary *dic = self.layoutInfo[HWLayoutFooterKind];
    if (dic.count > 0) {
        footAttributes = self.layoutInfo[HWLayoutFooterKind][[NSIndexPath indexPathForRow:numberOfItemsInSection - 1 inSection:sectionCount - 1]];
    }else{
        footAttributes = self.layoutInfo[HWLayoutItemCellKind][[NSIndexPath indexPathForRow:numberOfItemsInSection - 1 inSection:sectionCount - 1]];
    
    }
    CGFloat height = CGRectGetMaxY(footAttributes.frame);
    if (self.collectionView.hw_collectionFooterView) {
        height += CGRectGetHeight(self.collectionView.hw_collectionFooterView.frame);
    }
    
    return CGSizeMake(self.collectionView.bounds.size.width, height);
}

第四步 設(shè)定Layout代理 改裝外部可以控制 </br>

設(shè)定代理 代理方法如下 當(dāng)如你如果是比如固定大小的可以通過(guò)設(shè)定諸如


@property (nonatomic) UIEdgeInsets itemInsets;
@property (nonatomic) CGSize itemSize;
@property (nonatomic) CGFloat interItemSpacingY;
@property (nonatomic) NSInteger numberOfColumns;
@property (nonatomic, assign) CGFloat headerHeight;
@property (nonatomic, assign) CGFloat footerHeight;


也可以通過(guò)設(shè)定代理隨意定制

@protocol HWCollectionViewWaterfallLayoutDelegate <NSObject>
@optional
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(HWCollectionViewWaterfallLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(HWCollectionViewWaterfallLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;

- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(HWCollectionViewWaterfallLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section;
//組與組的最小距離
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(HWCollectionViewWaterfallLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section;
//組頭高度
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(HWCollectionViewWaterfallLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section;
//組尾高度
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(HWCollectionViewWaterfallLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section;
@end

End ----

項(xiàng)目效果圖

image.png

至此所有的工作都做完了
注意: 我這個(gè)只是針對(duì)TableView的仿造還沒(méi)有對(duì)流式布局進(jìn)行進(jìn)一步處理 下一篇文章說(shuō)明 如何定制 比如蘋(píng)果自帶瀏覽器圖片等功能 有什么問(wèn)題可以私信我或者在在下面評(píng)論 如果還不錯(cuò)給個(gè)Star吧

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 翻譯自“Collection View Programming Guide for iOS” 0 關(guān)于iOS集合視...
    lakerszhy閱讀 4,082評(píng)論 1 22
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂(lè)視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,873評(píng)論 11 349
  • { 24、Sqlite數(shù)據(jù)庫(kù) 1、存儲(chǔ)大數(shù)據(jù)量,增刪改查,常見(jiàn)管理系統(tǒng):Oracle、MSSQLServer、DB...
    CYC666閱讀 1,055評(píng)論 0 1
  • 用心對(duì)生 過(guò)往煙云,如風(fēng)消散 恍世浮華,一生雙影 蕓蕓眾生,尋尋覓覓 只為回頭,燈火闌珊。
    羋密閱讀 168評(píng)論 0 0
  • 連續(xù)幾天都做夢(mèng)了,早上醒來(lái),躺在床上夢(mèng)清清楚楚的,很想把夢(mèng)境記下來(lái),但往往一通忙乎之后,拿起手機(jī)要記錄的時(shí)候,腦海...
    牧田麻麻閱讀 243評(píng)論 0 0

友情鏈接更多精彩內(nèi)容