ColectionView 的裝飾視圖自定義布局揍堕,糊一張帶陰影效果的窝剖,Swift 5
#### 重點(diǎn):
一沃饶,
裝飾視圖 Decoration View 棠隐,蘋果的例子是一個(gè) cell 貼一張背景圖石抡。
實(shí)際上,一個(gè) section ,貼一張背景圖助泽,可以的啰扛。
蘋果設(shè)計(jì)的非常靈活嚎京,基本上背景圖想怎么糊上去,就怎么糊
實(shí)踐中發(fā)現(xiàn)
二隐解,
設(shè)置 Decoration View 鞍帝,手寫 UICollectionViewFlowLayout ( 或 UICollectionViewLayout ),是寫死的煞茫。
布局顯示帕涌,一般有一個(gè)網(wǎng)絡(luò)請(qǐng)求。數(shù)據(jù)請(qǐng)求回來前续徽,走自定義的 layout , 到具體的 indexpath, 訪問手工設(shè)置有蚓曼,因?qū)嶋H不存在,崩钦扭。
因?yàn)闆]有網(wǎng)絡(luò)請(qǐng)求回?cái)?shù)據(jù)辟躏,實(shí)際的 section 數(shù)量一般為 0.
需判斷一下。
三土全,
無關(guān) Decoration View 捎琐。
做了一個(gè)商品首頁的需求,UICollectionView 七層樓裹匙,每層樓都不一定有瑞凑,樓層順序也不一定。
如果寫 if else 概页,就要命籽御。通過字典配置,解決
<hr>
詳細(xì)介紹:
-
配圖說明:
“第一個(gè) cell" , 是第一個(gè) section, 只有 1 個(gè) item
“第二個(gè) cell" , 是第二個(gè) section, 有 5 個(gè) item
![](https://user-gold-cdn.xitu.io/2019/1/6/168226dd016c7b0a?w=750&h=1334&f=png&s=508639)
第一點(diǎn)惰匙,一個(gè) section ,貼一張背景圖
設(shè)置背景圖的區(qū)域技掏,糊上去,end
具體烹飪教程如下:
裝飾視圖是 UICollectionViewLayout 的功能项鬼,不是 UICollectionView 的哑梳。
UICollectionView 的方法、代理方法 (delegate, datasource)都不涉及裝飾視圖绘盟。
UICollectionView 對(duì)裝飾視圖一無所知鸠真,UICollectionView 按照 UICollectionViewLayout 設(shè)置的渲染。
要用裝飾視圖龄毡,就要自定制 UICollectionViewLayout吠卷,也就是 UICollectionViewLayout 的子類。這個(gè) UICollectionViewLayout 子類沦零,可以添加屬性祭隔、代理屬性,通過設(shè)置代理協(xié)議方法路操,來自定制裝飾視圖疾渴。
本文 Demo 舉的例子是添加一個(gè)裝飾視圖背景圖片千贯。
(沒有涉及使用代理,設(shè)置協(xié)議方法程奠,進(jìn)一步控制裝飾視圖)
簡(jiǎn)要說來,自定制的 layout 子類祭钉,實(shí)現(xiàn)一個(gè)裝飾視圖瞄沙,五步:
步驟 1,
要有 Decoration View 文件慌核。
先創(chuàng)建一個(gè) UICollectionResuableView 的子類, 這個(gè)就是具體的裝飾視圖
```
@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 中注冊(cè)裝飾視圖垮卓。
有了裝飾視圖垫桂,組裝在一起 (wire it up)
自定制的 layout 子類中,注冊(cè) UICollectionResuableView 的子類粟按,也就是裝飾視圖诬滩。
調(diào)用 `- (void)registerClass:(nullable Class)viewClass forDecorationViewOfKind:(NSString *)elementKind;` 方法。
一般在 `- (void)prepareLayout ` 方法中注冊(cè)灭将。
```
- (void)prepareLayout {
? ? [super prepareLayout];
? ? [self registerClass: FrontDecorationReusableView.class forDecorationViewOfKind: FDRFrontDecorationReusableView];
}
```
步驟 3疼鸟,
設(shè)置裝飾視圖的位置。
`- (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath` 方法庙曙,設(shè)置裝飾視圖 UICollectionResuableView 的位置空镜,因?yàn)樵摲椒ǚ祷亓搜b飾視圖的布局屬性。
`+ (instancetype)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind withIndexPath:(NSIndexPath *)indexPath;` 方法捌朴,構(gòu)建布局屬性吴攒,并作相關(guān)的配置。
先設(shè)置裝飾視圖的具體位置砂蔽,
```
- (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath{
? ? if (elementKind == FDRFrontDecorationReusableView && indexPath.section == 1) {
? ? ? ? DecorationLayoutAttributes * attributes = [DecorationLayoutAttributes layoutAttributesForDecorationViewOfKind: FDRFrontDecorationReusableView withIndexPath: indexPath];
? ? ? ? // 通過屬性洼怔,外部設(shè)置裝飾視圖的實(shí)際圖片 ( 后有介紹 )
? ? ? ? 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` 方法茴厉,
該方法會(huì)返回給定區(qū)域內(nèi),所有視圖 ( 格子視圖什荣、補(bǔ)充視圖(header \ footer)矾缓、裝飾視圖 ) 的布局屬性。
這里要糊上裝飾視圖稻爬,`layoutAttributesForElementsInRect:`返回的布局屬性數(shù)組嗜闻,需含有調(diào)用 `- (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath` 方法中設(shè)置的布局屬性。
這一步比較關(guān)鍵桅锄,collectionView 得到了足夠的信息琉雳,顯示裝飾視圖样眠。
當(dāng) collectionView 調(diào)用 `layoutAttributesForElementsInRect:`,他會(huì)提供每一種裝飾視圖的布局屬性翠肘。
collectionView 對(duì)裝飾視圖是隔離的檐束,一無所知∈叮看到的 collectionView 的裝飾視圖被丧,是自定制 layout 提供的。
步驟 2中绪妹,注冊(cè)了裝飾視圖甥桂,即創(chuàng)建了自定制的裝飾視圖實(shí)例。collectionView 會(huì)根據(jù)布局屬性邮旷,放置好黄选。
把上一步設(shè)置的裝飾視圖布局屬性,交給 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 一個(gè)圖片地址屬性律歼,
```
@interface DecorationFlowLayout : UICollectionViewFlowLayout
@property (nonatomic, copy) NSString * imgUrlString;
@end
```
然后想辦法傳過去懂诗,就好了
collectionView 設(shè)置 layout 的圖片 url ,間接控制裝飾視圖的圖片 url
```
......
self.decorationFlowLayout.imgUrlString = @"https://fscdn.zto.com/GetPublicFile/ztPK4Y-WGgWKiRNfkygd3oYQ/thumbnail_747d31f481044bf6a149c7483cd097a5.jpg";
? ? [self.newMainCollectionView reloadData];
}
```
自定制 layout 與裝飾視圖也是隔離的苗膝。創(chuàng)建自定制布局屬性對(duì)象 UICollectionViewLayoutAttributes 來傳值殃恒,相當(dāng)于找了一個(gè)信使。
使用 UICollectionViewLayoutAttributes 的子類辱揭,添加屬性傳值离唐。
```
@interface DecorationLayoutAttributes: UICollectionViewLayoutAttributes
@property (nonatomic, copy) NSString * imgUrlStr;
@end
```
`layoutAttributesForDecorationViewOfKind:` 中配置,
上有提及问窃,
```
DecorationLayoutAttributes * attributes = [DecorationLayoutAttributes layoutAttributesForDecorationViewOfKind: FDRFrontDecorationReusableView withIndexPath: indexPath];
? ? ? ? // 通過屬性亥鬓,外部設(shè)置裝飾視圖的實(shí)際圖片
? ? ? ? attributes.imgUrlStr = self.imgUrlString;
```
最后一小步,
把自定制 LayoutAttributes 的圖片 url 傳遞給裝飾視圖域庇, 靠 `- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes` 方法嵌戈。
當(dāng) collectionView 配置裝飾視圖的時(shí)候,會(huì)調(diào)用該方法听皿。`layoutAttributes` 作為參數(shù)熟呛,取出 `imgUrlStr` 屬性使用,就可以了
```
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes{
? ? if ( [layoutAttributes valueForKey: @"imgUrlStr"] && [layoutAttributes isMemberOfClass: NSClassFromString(@"DecorationLayoutAttributes")] ) {
? ? ? ? [self.imageView sd_setImageWithURL_str: [layoutAttributes valueForKey: @"imgUrlStr"]];
? ? }
}
```
<hr>
第二點(diǎn)尉姨,怎么處理庵朝,看一下 [CHTCollectionViewWaterfallLayout](https://github.com/chiahsien/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 ]];
// 因?yàn)檫@一行,崩
// 數(shù)據(jù)請(qǐng)求回來前,不存在實(shí)際的區(qū)間九府。 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 數(shù)據(jù)源沒設(shè)置,就先返回
判斷一下情況
```
if (numberOfSections == 0) {
? ? ? ? return rawArr;
? ? }
``