文章按照順序寫的,之前文章寫過的很多邏輯都會略過,建議順序閱讀,并下載源碼結合閱讀。
目錄
項目下載地址: 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
}
再次運行。