文章按照顺序写的,之前文章写过的很多逻辑都会略过,建议顺序阅读,并下载源码结合阅读。
目录
项目下载地址: CollectionView-Note
UICollectionView 01 - 基础布局篇
UICollectionView 02 - 布局和代理篇
UICollectionView 03 - 自定义布局原理篇
UICollectionView 04 - 卡片布局
UICollectionView 05 - 可伸缩Header
UICollectionView 06 - 瀑布流布局
UICollectionView 07 - 标签布局
上一篇 通过 UICollectionViewFlowLayout
,然后对其cell实时监控根据距离中心位置进行缩放实现了卡片布局 , 这篇继续继承 UICollectionViewFlowLayout
对其SupplementaryView
做一些小处理 ,即可实现可伸缩头部。
继续在 Storyboard
中拖出一个 ViewController
, 然后放上一个 UICollectionView
. 依旧使用之前篇幅的色块作为cell,这里就放几个横条cell (不关键,本篇主要针对 SupplementaryView)。 然后新建一个继承自UICollectionReusableView
的 ImageHeaderView
. 这边使用xib创建的 也可以直接再 Storyboard
中或者纯代码 。 ImageView
是靠四个边的。
class ImageHeaderView: UICollectionReusableView {
static let reuseID = "ImageHeaderView"
@IBOutlet weak var imageView: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
}
}
整个 ViewController
大概就这样
class StretchyHeaderViewController: UIViewController {
@IBOutlet weak var collectionView: UICollectionView!
var layout: UICollectionViewFlowLayout? {
return collectionView.collectionViewLayout as? UICollectionViewFlowLayout
}
var colors: [UIColor] = []
override func viewDidLoad() {
super.viewDidLoad()
colors = DataManager.shared.generalColors(3)
collectionView.dataSource = self
collectionView.register(UINib(nibName: "ImageHeaderView", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: ImageHeaderView.reuseID)
collectionView.register(UINib(nibName: "BasicsHeaderView", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: BasicsHeaderView.reuseID)
collectionView.alwaysBounceVertical = true
let width = view.bounds.width
layout?.itemSize = CGSize(width: width, height: 50)
layout?.minimumLineSpacing = 2
layout?.headerReferenceSize = CGSize(width: width, height: 150)
}
}
// MARK: - UICollectionViewDataSource
extension StretchyHeaderViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return colors.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: BasicsCell.reuseID, for: indexPath) as! BasicsCell
cell.backgroundColor = colors[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionView.elementKindSectionHeader:
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: ImageHeaderView.reuseID, for: indexPath) as! ImageHeaderView
return view
default:
fatalError("No such kind")
}
}
}
一个很普通的header + cell的布局, 如下,拖动的时候没有任何效果。
下面我们自定义一个继承自 UICollectionViewFlowLayout
的 StretchyLayout
.
跟上篇一样重写override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
方法。
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// 1
guard let collectionView = self.collectionView else { return nil }
guard let attributes = super.layoutAttributesForElements(in: rect) else { return nil }
// 2
let insets = collectionView.contentInset
let offset = collectionView.contentOffset
let minY = -insets.top
// 3
if offset.y < minY {
// 4
let headerSize = self.headerReferenceSize
let deltalY = abs(offset.y - minY)
for attibute in attributes {
// 5
if attibute.representedElementKind == UICollectionView.elementKindSectionHeader {
// 6
var headerRect = attibute.frame
headerRect.size.height = headerSize.height + deltalY
headerRect.origin.y = headerRect.origin.y - deltalY
attibute.frame = headerRect
}
}
}
return attributes
}
这里解释下
- 获取
collectionView
和 父类返回的attributes
- 我们的拉伸是从顶部开始拉伸,所以这里获取到当前的 offset 和顶部的 inset
- 如果滚动的y坐标比顶部最小的inset还小,就需要拉伸了
- 获取到headersize,以及我们滚动超过顶部的距离
deltalY
- 遍历
attributes
,我们只针对Section Header
进行处理 - 获取到header的frame,根据滚动的距离 ,调整header的大小和坐标。
最后 跟上一篇一样,当bounds改变时重新计算。
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
ok,其实没啥复杂的东西,然后再storyboard
中将layout设置为我们自定义的layout (不会设置的看上一篇)。 ViewController
中获取到的也是我们的 StretchyLayout
。
var layout: StretchyLayout? {
return collectionView.collectionViewLayout as? StretchyLayout
}
再次运行。