lufei's Studio.

你可能不知道的UICollectionView

字数统计: 1.5k阅读时长: 5 min
2020/04/23 Share

前言

最近遇到一个跟 UICollectionView 相关的控件问题,就是如何给 UICollectionView 的不同 Section 设置不同的背景色,进而想到如何给它们设置不同的属性。

为什么想到这个问题,是因为以前遇到这种情况,我往往都是不假思索分成多个 Collection View 来处理的,完全忘记了其实 UICollectionView 是可以分组的,哎,我以前到底绕了多少弯路啊,苦笑~

了解 UICollectionView

UICollectionView 是 iOS6 以后引入 UIKit 的新的视图控件,Api 设计和 UITableView 类似,基础用法也类似。

不过 UICollectionViewUITableView 的基础上做了一些扩展,其中最强大的部分就是完全灵活的布局结构。

UITableViewUICollectionView 都是 data-source 和 delegate 驱动的。它们在显示其子视图集的过程中仅扮演容器角色,且对子视图集真正的内容毫不知情。

UICollectionView 在此之上进行了进一步抽象。它将其子视图的位置,大小和外观的控制权委托给一个单独的布局对象。通过提供一个自定义布局对象,你几乎可以实现任何你能想象到的布局。布局对象继承自 UICollectionViewLayout 抽象基类

iOS6 中以 UICollectionViewFlowLayout 类的形式展现了一个具体的布局实现。

一般情况下,在实际开发中,我们使用 UICollectionViewFlowLayout 就够了

为了适应任意布局,collection view 建立了一个类似、但比 table view 更灵活的视图层级。

table view 一样,你的主要内容显示在 Cell 中,cell 可以被任意分组到 Section 中。Collection view 的 cell 必须是 UICollectionViewCell 的子类。

除了 Cellcollection view 额外管理着两种视图:

  • Supplementary Views (追加视图)
  • Decoration Views (装饰视图)

collection view 中的 Supplementary views 相当于 table viewsection headerfooter views。像 cells 一样,他们的内容都由数据源对象驱动。然而和 table view 中用法不一样,supplementary view 并不一定会作为 headerfooter view,它们的数量和放置的位置完全由 UICollectionViewLayout 控制。

Decoration views 纯粹为一个装饰品。他们完全属于 UICollectionViewLayout,并被布局对象管理,他们并不从 data source 获取内容。当 UICollectionViewLayout 指定需要一个 decoration view 的时候,collection view 会自动创建,并将 UICollectionViewLayout 提供的布局参数应用到上面去。并不需要为 decoration view 准备任何内容。

Supplementary viewsdecoration views 必须是 UICollectionReusableView 的子类。

Supplementary viewsUICollectionViewCell 都需要在 collection view 中注册,这样当 data source 让它们从 reuse pool 中出列时,它们才能够创建新的实例。如果你是使用的 Interface Builder ,则可以通过在可视编辑器中拖拽一个 cell 到 collection view 上完成 cell 在 collection view 中的注册。同样的方法也可以用在 supplementary view 上,前提是你使用了 UICollectionViewFlowLayout。如果没有,你只能通过调用 registerClass: 或者 registerNib: 方法手动注册视图类了。你需要在 viewDidLoad 中做这些操作。

Decoration views 需要使用 UICollectionViewLayout 注册

自定义 UICollectionViewFlowLayout

前面也说了,日常开发中,大部分情况下,UICollectionViewFlowLayout足够使用了,需要自定义的时候,也可以继承 UICollectionViewFlowLayout,进行进一步的定制。

当然,如果实在出现了某种需要自定义 UICollectionViewLayout 的情况,第一步是去仔细阅读 UICollectionViewLayout 文档

我的需求,只需要进一步定制 UICollectionViewFlowLayout 就够了。

大概流程如下:

首先是我们需要自定义一个 UICollectionViewLayoutAttributes的子类,并添加一个 backgroundColor 属性。

然后自定义一个 Decoration view ,显然它继承自 UICollectionReusableView,因为我们需要 UICollectionViewLayoutAttributes 的子类新添加的 backgroundColorUICollectionReusableView 的子类视图生效,所以还需要重写一下 apply 方法。

最后在UICollectionViewFlowLayout 的子类中注册我们自定义的 Decoration view

想要计算 Decoration viewframe,其中一个方法是,获取每一组的第一个 item 和 最后一个 item ,利用 union(_:) 方法来实现。

当然还可以为 UICollectionViewDelegateFlowLayout 添加一个新的关于背景色的代理方法。

实践

参考代码

参考

自定义 Collection View 布局

Collection View Programming Guide for iOS

UIcollectionView 其他可能有用的小知识

也许有时候你会觉得 UICollectionView 的刷新动画有点多余。

我有时候就会这么想,于是想关闭 UICollectionView 刷新时候的隐式动画。

此时,UICollectionViewperformBatchUpdates(_:completion:) 方法,就派上了用场:

1
2
3
4
5
6
7
UIView.setAnimationsEnabled(false)

collectionView.performBatchUpdates({
collectionView.reloadItems(at: indexPaths)
}, completion: { (_) in
UIView.setAnimationsEnabled(true)
})

当然还有更酷的方法,那就是 UIViewperformWithoutAnimation(_:) 方法了:

1
2
3
UIView.performWithoutAnimation {
collectionView.reloadItems(at: indexPaths)
}

当然,设置 UIView 动画时间为0,也可以算第三种办法,此处就不多说了。

但是不知道为什么 reloadData 在这两个个方案中的表现,总是不那么和我美好的想象相符合,尤其是在 performBatchUpdates 里,直接导致 UI 刷新无效了,原因目前还不太清楚,不过 reloadData 的文档里倒是做了这样的说明:

You should not call this method in the middle of animation blocks where items are being inserted or deleted. Insertions and deletions automatically cause the collection’s data to be updated appropriately.

好吧,乖乖听话~~

Auto layout 和 UICollectionView

在大部分情况下,Auto layout 和 UICollectionView 的配合都还不错,只要你的约束写得正确,是很方便的。

只需要在 UICollectionViewFlowLayout 创建的时候,加一句:

1
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize

基本就可以了。

只有 UICollectionViewFlowLayout 可以设置 estimatedItemSize

特殊地,有时候,我们可能需要实现瀑布流,有时候,可能我们的后台也没有把需要展示的图片宽高及时返回,此时如果再使用 auto layout 的话,就有一些需要注意的地方了。

首先,我们可能需要为每一个 item 设置一个临时的宽高。

然后,我们是需要一张一张图片去加载完图片,再一起更新约束的,单独更新约束的话,会导致一些莫名其妙的bug,这一点我还没找到原因。

更新约束的方法,也很简单,在需要更新的时候,使用 invalidateLayout()

CATALOG
  1. 1. 前言
  2. 2. 了解 UICollectionView
  3. 3. 自定义 UICollectionViewFlowLayout
  4. 4. 实践
  5. 5. 参考
  6. 6. UIcollectionView 其他可能有用的小知识
    1. 6.1. 也许有时候你会觉得 UICollectionView 的刷新动画有点多余。
  7. 7. Auto layout 和 UICollectionView