一, 裝飾視圖 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{
// 最後一層樓,固定的狀況, 簡單點, 仍是用 if 了
if( 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 代碼:
參考了下 casa 大佬寫過的一篇博客
參考資料: