UICollectionView: 糊一張裝飾視圖 Decoration View 的一點經驗

重點:

一, 裝飾視圖 Decoration View ,蘋果的例子是一個 cell 貼一張背景圖。git

實際上,一個 section ,貼一張背景圖,能夠的。github

蘋果設計的很是靈活,基本上背景圖想怎麼糊上去,就怎麼糊數組

實踐中發現bash

二, 設置 Decoration View ,手寫 UICollectionViewFlowLayout ( 或 UICollectionViewLayout ),是寫死的。網絡

佈局顯示,通常有一個網絡請求。數據請求回來前,走自定義的 layout , 到具體的 indexpath, 訪問手工設置有,因實際不存在,崩。app

由於沒有網絡請求回數據,實際的 section 數量通常爲 0.佈局

需判斷一下。ui

三, 無關 Decoration View 。atom

作了一個商品首頁的需求,UICollectionView 七層樓,每層樓都不必定有,樓層順序也不必定。url

若是寫 if else ,就要命。經過字典配置,解決


詳細介紹:

配圖說明:

「第一個 cell" , 是第一個 section, 只有 1 個 item

「第二個 cell" , 是第二個 section, 有 5 個 item

第一點,一個 section ,貼一張背景圖

設置背景圖的區域,糊上去,end

具體烹飪教程以下:

裝飾視圖是 UICollectionViewLayout 的功能,不是 UICollectionView 的。

UICollectionView 的方法、代理方法 (delegate, datasource)都不涉及裝飾視圖。

UICollectionView 對裝飾視圖一無所知,UICollectionView 按照 UICollectionViewLayout 設置的渲染。

要用裝飾視圖,就要自定製 UICollectionViewLayout,也就是 UICollectionViewLayout 的子類。這個 UICollectionViewLayout 子類,能夠添加屬性、代理屬性,經過設置代理協議方法,來自定製裝飾視圖。

本文 Demo 舉的例子是添加一個裝飾視圖背景圖片。

(沒有涉及使用代理,設置協議方法,進一步控制裝飾視圖)

簡要說來,自定製的 layout 子類,實現一個裝飾視圖,五步:

步驟 1,

要有 Decoration View 文件。

先建立一個 UICollectionResuableView 的子類, 這個就是具體的裝飾視圖

@interface FrontDecorationReusableView()
// 裝飾視圖,裏面就一張圖片
@property (nonatomic, strong) UIImageView * imageView;

@end

@implementation FrontDecorationReusableView
- (instancetype)initWithFrame:(CGRect)frame{

    if (self = [super initWithFrame:frame]){ 
        self.backgroundColor = UIColor.whiteColor;
        _imageView = [[UIImageView alloc] init];
        [self addSubview: _imageView];
        // 使用了 masonry 佈局
        [_imageView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.edges.mas_equalTo(self);
        }];
    }
    return self;
}
複製代碼

步驟 2,

layout 中註冊裝飾視圖。

有了裝飾視圖,組裝在一塊兒 (wire it up)

自定製的 layout 子類中,註冊 UICollectionResuableView 的子類,也就是裝飾視圖。

調用 - (void)registerClass:(nullable Class)viewClass forDecorationViewOfKind:(NSString *)elementKind; 方法。

通常在 - (void)prepareLayout 方法中註冊。

- (void)prepareLayout {
    [super prepareLayout];
    [self registerClass: FrontDecorationReusableView.class forDecorationViewOfKind: FDRFrontDecorationReusableView];
}
複製代碼

步驟 3,

設置裝飾視圖的位置。

- (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath 方法,設置裝飾視圖 UICollectionResuableView 的位置,由於該方法返回了裝飾視圖的佈局屬性。

+ (instancetype)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind withIndexPath:(NSIndexPath *)indexPath; 方法,構建佈局屬性,並做相關的配置。

先設置裝飾視圖的具體位置,

- (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath{
    
    if (elementKind == FDRFrontDecorationReusableView && indexPath.section == 1) {
        DecorationLayoutAttributes * attributes = [DecorationLayoutAttributes layoutAttributesForDecorationViewOfKind: FDRFrontDecorationReusableView withIndexPath: indexPath];
        // 經過屬性,外部設置裝飾視圖的實際圖片 ( 後有介紹 )
        attributes.imgUrlStr = self.imgUrlString;
       // 這裏,裝飾視圖的位置是固定的
        CGFloat heightOffset = 16;
        attributes.frame = CGRectMake(0, KScreenWidth * 0.5 - heightOffset, KScreenWidth, 102 + heightOffset);
        attributes.zIndex -= 1;
        return attributes;
    }
    return nil;
}
複製代碼

步驟 4,

重寫 - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect 方法, 該方法會返回給定區域內,全部視圖 ( 格子視圖、補充視圖(header \ footer)、裝飾視圖 ) 的佈局屬性。

這裏要糊上裝飾視圖,layoutAttributesForElementsInRect:返回的佈局屬性數組,需含有調用 - (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath 方法中設置的佈局屬性。

這一步比較關鍵,collectionView 獲得了足夠的信息,顯示裝飾視圖。 當 collectionView 調用 layoutAttributesForElementsInRect:,他會提供每一種裝飾視圖的佈局屬性。 collectionView 對裝飾視圖是隔離的,一無所知。看到的 collectionView 的裝飾視圖,是自定製 layout 提供的。

步驟 2中,註冊了裝飾視圖,即建立了自定製的裝飾視圖實例。collectionView 會根據佈局屬性,放置好。

把上一步設置的裝飾視圖佈局屬性,交給 collectionView 使用

- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
    NSArray<UICollectionViewLayoutAttributes *> * rawArr = [super layoutAttributesForElementsInRect: rect];
    NSMutableArray<UICollectionViewLayoutAttributes *> * array = [[NSMutableArray alloc] initWithArray: rawArr];
// 避免崩潰 ( 後有介紹 )
    NSInteger numberOfSections = [self.collectionView numberOfSections];
    if (numberOfSections == 0) {
        return rawArr;
    }
    UICollectionViewLayoutAttributes * decorationAttrs = [self layoutAttributesForDecorationViewOfKind: FDRFrontDecorationReusableView atIndexPath: [NSIndexPath indexPathForItem: 0 inSection: 1 ]];
    if (decorationAttrs && CGRectIntersectsRect(rect, decorationAttrs.frame)) {
        [array addObject: decorationAttrs];
    }
    return [array copy];
}


複製代碼

步驟 5,

怎麼給裝飾視圖傳值?

三步走:

CollcetionView -> layout -> layoutAttributes -> decorationView 裝飾視圖

本文 demo ,是配置具體的裝飾圖片。

先給自定製的 layout 一個圖片地址屬性,

@interface DecorationFlowLayout : UICollectionViewFlowLayout
@property (nonatomic, copy) NSString * imgUrlString;
@end
複製代碼

而後想辦法傳過去,就行了

collectionView 設置 layout 的圖片 url ,間接控制裝飾視圖的圖片 url

......
self.decorationFlowLayout.imgUrlString = @"https://fscdn.zto.com/GetPublicFile/ztPK4Y-WGgWKiRNfkygd3oYQ/thumbnail_747d31f481044bf6a149c7483cd097a5.jpg";
    [self.newMainCollectionView reloadData];
}
複製代碼

自定製 layout 與裝飾視圖也是隔離的。建立自定製佈局屬性對象 UICollectionViewLayoutAttributes 來傳值,至關於找了一個信使。

使用 UICollectionViewLayoutAttributes 的子類,添加屬性傳值。

@interface DecorationLayoutAttributes: UICollectionViewLayoutAttributes

@property (nonatomic, copy) NSString * imgUrlStr;
@end

複製代碼

layoutAttributesForDecorationViewOfKind: 中配置, 上有說起,

DecorationLayoutAttributes * attributes = [DecorationLayoutAttributes layoutAttributesForDecorationViewOfKind: FDRFrontDecorationReusableView withIndexPath: indexPath];
        // 經過屬性,外部設置裝飾視圖的實際圖片
        attributes.imgUrlStr = self.imgUrlString;
複製代碼

最後一小步,

把自定製 LayoutAttributes 的圖片 url 傳遞給裝飾視圖, 靠 - (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes 方法。

當 collectionView 配置裝飾視圖的時候,會調用該方法。layoutAttributes 做爲參數,取出 imgUrlStr 屬性使用,就能夠了

- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes{
    if ( [layoutAttributes valueForKey: @"imgUrlStr"] && [layoutAttributes isMemberOfClass: NSClassFromString(@"DecorationLayoutAttributes")] ) {
        [self.imageView sd_setImageWithURL_str: [layoutAttributes valueForKey: @"imgUrlStr"]];
    }
}

複製代碼

第二點,怎麼處理,看了一下大神寫的 CHTCollectionViewWaterfallLayout

- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
    NSArray<UICollectionViewLayoutAttributes *> * rawArr = [super layoutAttributesForElementsInRect: rect];
    NSMutableArray<UICollectionViewLayoutAttributes *> * array = [[NSMutableArray alloc] initWithArray: rawArr];
    NSInteger numberOfSections = [self.collectionView numberOfSections];
//    if (numberOfSections == 0) {
//        return rawArr;
//    }
UICollectionViewLayoutAttributes * decorationAttrs = [self layoutAttributesForDecorationViewOfKind: FDRFrontDecorationReusableView atIndexPath: [NSIndexPath indexPathForItem: 0 inSection: 1 ]];

// 由於這一行,崩
// 數據請求回來前,不存在實際的區間。 indexPath 也沒有。
複製代碼

2019-01-05 16:54:59.230718+0800 Improved[31532:238435] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'request for layout attributes for decoration view of kind FrontDecorationReusableView in section 1 when there are only 0 sections in the collection view'

datasource 數據源沒設置,就先返回

判斷一下狀況

if (numberOfSections == 0) {
        return rawArr;
    }
複製代碼

第三點,

if 直接判斷條件,有一個隨機的語義。

字典就是哈希表,知道鍵,直接取值。也有一個隨機的語義。

正適合這種隨機配置的狀況。

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
// 最後一層樓,固定的狀況, 簡單點, 仍是用 ifif( indexPath.section == self.floorDataLists.count ){
        HotSalesCell * hotSaleCollectionViewCell = [collectionView dequeueReusableCellWithReuseIdentifier: kHotSaleCollectionViewCell forIndexPath: indexPath];
        MyProduct * myProduct = self.hotSaleProducts[indexPath.row];
       hotSaleCollectionViewCell.hotSalesProduct = myProduct;
       return hotSaleCollectionViewCell;
   }
   UICollectionViewCell * cell = nil;
   
   FloorDataList * floorDataList = self.floorDataLists[indexPath.section];
   NSString * keyStr = floorDataList.floorTypeName;
// 先找出,關鍵的樓層配置信息, 做爲鍵
   NSNumber * newSectionIndex = self.mapOne_sequence[keyStr];
// 先使用字典,化無序的配置,爲有限的集合狀況
// 使用 switch ... case  , 對每一種狀況,針對性處理就行了
   FloorDataModel * floorDataModel = floorDataList.floorData[indexPath.item];
   switch (newSectionIndex.unsignedIntegerValue){
       case 1:
       {
           BannerReusableView * bannerReusableView = [collectionView dequeueReusableCellWithReuseIdentifier:  kBannerReusableView forIndexPath: indexPath];
               NSMutableArray * imgLinksArray = [NSMutableArray array];
               for (FloorDataModel * floorDataModel in floorDataList.floorData){
                   [imgLinksArray addObject: floorDataModel.uploadImage];
               }
               [bannerReusableView parseBannerPics: imgLinksArray  andSection: indexPath.section];
           }
           / / Cell A 
           cell = bannerReusableView;
       }
           break;
       case 2:
           {
               cell = // ... Cell B; 
           }
           break;
       case 3:
           {
                cell = // ... Cell C; 
           }
           break;
       case 4:
       {
            cell = // ... Cell D; 
       }
           break;
       default:
           break;
   }
   return cell;
}


- (NSDictionary *)mapOne_sequence{
   if (!_mapOne_sequence) {
       _mapOne_sequence = @{kFloorTypeNameFiveIcon: @(2),
                   kFloorTypeNameBanner: @(1),
                   kTenGoods: @(3),
                   kAcrossColumn: @(4)
                   };
   }
   return _mapOne_sequence;
}


複製代碼

先找出,關鍵的樓層配置信息, 做爲鍵

使用字典,化無序的配置,爲有限的集合狀況

使用 switch ... case , 對每一種狀況,針對性處理就行了

(暫未想出更好的辦法)

更多見 Demo 代碼:

dev.tencent.com/u/dengjiang…

參考了下 casa 大佬寫過的一篇博客


參考資料:

stackoverflow.com/questions/1…

相關文章
相關標籤/搜索