検証環境:
Xcode 12.4
Swift 5.3.2
UICollectionView で複数セクションのヘッダーとフッターを表示する方法について。
StoaryBoard 設定
まず StoryBoard で Root View に UICollectionView 及び UICollectionViewCell のビューを設置しておく。
UICollectionView と UICollectionViewDelegateFlowLayout は @IBOutlet でコード上の定義と紐付ける。
ヘッダービュー、フッタービュー(UICollectionReusableView)は StoryBoard 上で設置できるのは各1つのみで複数の設置はできない。(同じビューを使い回すのは可能)
なので複数のヘッダービュー、フッタービューを表示したい場合はコード側でやるしかない(と思われる)。
今回はNibファイル(.xib)で用意し、collectionView#register メソッドで登録する。
ヘッダー、フッターを表示する
ヘッダー、フッターを表示させるには、ビューのサイズを指定する必要がある。
1つは、UICollectionViewFlowLayout のheaderReferenceSize
またはfooterReferenceSize
プロパティでサイズを指定する方法、もう1つは UICollectionViewDelegate のcollectionView(_:layout:referenceSizeForHeaderInSection:)
またはcollectionView(_:layout:referenceSizeForFooterInSection:)
のデリゲートメソッドでサイズを指定する方法のどちらか。
このサイズに 0 を設定するとヘッダー、フッターは表示されなくなる。
コード側で指定しなくても StoryBoard 側で UICollectionView にある以下のチェックを入れるとヘッダー、フッターが自動生成されるので、これでも表示させることができる。
この場合は先にも書いたとおり追加できるのはヘッダー、フッター各1つのみ。
今回は複数のヘッダー、フッターを追加するのが目的なのでここは使わない。
サンプルコード
import UIKit
class ViewController: UIViewController {
enum Section: Int, CaseIterable {
case section1 = 0
case section2
}
var items1: [String] = ["1-1", "1-2", "1-3", "1-4", "1-5", "1-6"]
var items2: [String] = ["2-1", "2-2", "2-3", "2-4", "2-5", "2-6"]
override func viewDidLoad() {
super.viewDidLoad()
}
@IBOutlet weak var collectionView: UICollectionView! {
didSet {
collectionView.register(
UINib(nibName: "Section1HeaderView", bundle: nil),
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: "Section1HeaderView")
collectionView.register(
UINib(nibName: "Section1FooterView", bundle: nil),
forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter,
withReuseIdentifier: "Section1FooterView")
collectionView.register(
UINib(nibName: "Section2HeaderView", bundle: nil),
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: "Section2HeaderView")
collectionView.register(
UINib(nibName: "Section2FooterView", bundle: nil),
forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter,
withReuseIdentifier: "Section2FooterView")
collectionView.dataSource = self
collectionView.delegate = self
collectionView.alwaysBounceVertical = true
}
}
@IBOutlet weak var flowLayout: UICollectionViewFlowLayout! {
didSet {
flowLayout.estimatedItemSize = .zero
flowLayout.scrollDirection = .vertical
flowLayout.sectionHeadersPinToVisibleBounds = true
flowLayout.sectionFootersPinToVisibleBounds = false
}
}
}
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return Section.allCases.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
switch Section(rawValue: section)! {
case .section1:
return items1.count
case .section2:
return items2.count
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
cell.contentView.backgroundColor = (indexPath.item % 2 == 0) ? .systemGray4 : .systemGray6
switch Section(rawValue: indexPath.section)! {
case .section1:
cell.titleLabel.text = items1[indexPath.item]
case .section2:
cell.titleLabel.text = items2[indexPath.item]
}
return cell
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionView.elementKindSectionHeader:
switch Section(rawValue: indexPath.section)! {
case .section1:
return collectionView.dequeueReusableSupplementaryView(
ofKind: kind, withReuseIdentifier: "Section1HeaderView", for: indexPath)
case .section2:
return collectionView.dequeueReusableSupplementaryView(
ofKind: kind, withReuseIdentifier: "Section2HeaderView", for: indexPath)
}
case UICollectionView.elementKindSectionFooter:
switch Section(rawValue: indexPath.section)! {
case .section1:
return collectionView.dequeueReusableSupplementaryView(
ofKind: kind, withReuseIdentifier: "Section1FooterView", for: indexPath)
case .section2:
return collectionView.dequeueReusableSupplementaryView(
ofKind: kind, withReuseIdentifier: "Section2FooterView", for: indexPath)
}
default:
fatalError()
}
}
}
extension ViewController: UICollectionViewDelegate {
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 90, height: 90)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
switch Section(rawValue: section)! {
case .section1:
return CGSize(width: collectionView.frame.width, height: 60)
case .section2:
return CGSize(width: collectionView.frame.width, height: 60)
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
switch Section(rawValue: section)! {
case .section1:
return CGSize(width: collectionView.frame.width, height: 30)
case .section2:
return CGSize(width: collectionView.frame.width, height: 30)
}
}
}
class CollectionViewCell: UICollectionViewCell {
@IBOutlet weak var titleLabel: UILabel!
}
class Section1HeaderView: UICollectionReusableView {
}
class Section1FooterView: UICollectionReusableView {
}
class Section2HeaderView: UICollectionReusableView {
}
class Section2FooterView: UICollectionReusableView {
}