Audio

作者: BoxDeng | 来源:发表于2019-10-02 12:14 被阅读0次

ColectionView 的装饰视图自定义布局,糊一张带阴影效果的,Swift 5

#### 重点:

一,

装饰视图 Decoration View ,苹果的例子是一个 cell 贴一张背景图。

实际上,一个 section ,贴一张背景图,可以的。

苹果设计的非常灵活,基本上背景图想怎么糊上去,就怎么糊

实践中发现

二,

设置 Decoration View ,手写 UICollectionViewFlowLayout ( 或 UICollectionViewLayout ),是写死的。

布局显示,一般有一个网络请求。数据请求回来前,走自定义的 layout , 到具体的 indexpath, 访问手工设置有,因实际不存在,崩。

因为没有网络请求回数据,实际的 section 数量一般为 0.

需判断一下。

三,

无关 Decoration View 。

做了一个商品首页的需求,UICollectionView 七层楼,每层楼都不一定有,楼层顺序也不一定。

如果写 if else ,就要命。通过字典配置,解决

<hr>

详细介绍:

-

配图说明:

“第一个 cell" , 是第一个 section, 只有 1 个 item

“第二个 cell" , 是第二个 section, 有 5 个 item

![](https://user-gold-cdn.xitu.io/2019/1/6/168226dd016c7b0a?w=750&h=1334&f=png&s=508639)

第一点,一个 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"]];

    }

}

```

<hr>

第二点,怎么处理,看一下 [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 ]];

// 因为这一行,崩

// 数据请求回来前,不存在实际的区间。 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;

    }

``

相关文章

网友评论

      本文标题:Audio

      本文链接:https://www.haomeiwen.com/subject/odalpctx.html