UICollectionViewのHeaderカスタマイズいろいろ

CollectionViewのHeaderを作る上でカスタマイズできる点についてまとめました。Swift4.0で書いています。

一応GitHubにコードを置いています。
https://github.com/akatsuki174/CollectionViewHeaderCustomizeSample

# 前提：Headerを表示するまで

`UICollectionViewController`を使って作る場合の一例がこちらになります。

## storyboard

<img width="977" alt="スクリーンショット 2017-11-12 0.03.14.png" src="https://qiita-image-store.s3.amazonaws.com/0/100736/4a9f4f0d-82cc-c9ba-331d-d915a9f47a80.png">

cellはUICollectionViewControllerを用意した時にデフォルトでついていたものを使っています。headerは`UICollectionReusableView`をObject Libraryから引っ張ってきて置いています。このheaderには`SectionHeader`というidentifierを振っています。

## headerクラス

ラベル置いているだけなのでこれだけしか書いていません。

```swift:SectionHeader.swift
import UIKit

final class SectionHeader: UICollectionReusableView {
    @IBOutlet weak var sectionLabel: UILabel!
}
```

## ViewControllerクラス

```swift:ViewController.swift
import UIKit

final class ViewController: UICollectionViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

}

// MARK: datasorce, delegate

extension ViewController {
    override func collectionView(_ collectionView: UICollectionView,
                        cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{

        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
        return cell
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 10
    }

    override func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 3
    }

    override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {

        guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "SectionHeader", for: indexPath) as? SectionHeader else {
            fatalError("Could not find proper header")
        }

        if kind == UICollectionElementKindSectionHeader {
            header.sectionLabel.text = "section \(indexPath.section)"
            return header
        }

        return UICollectionReusableView()
    }

}
```

## この時点でのUI

<img width="365" alt="スクリーンショット 2017-11-12 0.14.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/100736/e96bf6a6-e4e0-3fe6-8f8c-3d313630aae2.png">

# 高さのカスタマイズ

全てのセクションヘッダを同じ高さにするのであればviewDidLoadを以下のようにすれば設定することができます。

```swift:ViewController.swift
override func viewDidLoad() {
    super.viewDidLoad()
    guard let fl = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout else { return }
    fl.headerReferenceSize = CGSize(width: self.view.bounds.width, height: 30)
}
```

<img width="365" alt="スクリーンショット 2017-11-12 0.32.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/100736/fc5ea7ba-48e9-475a-eace-4f8965074748.png">

セクションによってヘッダの高さを変えたい場合は`UICollectionViewDelegateFlowLayout`プロトコルを適用させた上で次のメソッドを追加すると高さを調節することができます。

```swift:ViewController.swift
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
    if section == 1 {
        return CGSize(width: self.view.bounds.width, height: 50)
    } else {
        return CGSize(width: self.view.bounds.width, height: 30)
    }
}
```

# ヘッダを表示しないセクションを作る

セクションによってセクションを出したい時、出したくない時があると思います。その時はさっき使ったメソッドを使って以下のように(0,0)を返します。

```swift:ViewController.swift
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
    if section == 1 {
        return CGSize.zero
    } else {
        return CGSize(width: self.view.bounds.width, height: 30)
    }
}
```

<img width="365" alt="スクリーンショット 2017-11-12 0.45.25.png" src="https://qiita-image-store.s3.amazonaws.com/0/100736/9fdc4fbe-1659-d80c-0e33-487d11f4d61a.png">

# ヘッダを画面上部に固定する

上にスクロールさせた時にセクションヘッダを画面上部に固定する場合は以下のようにviewDidLoadにコードを追加します（これを書かずに今まで通りのコードを書いていれば画面上部に固定されません）。

```swift:ViewController.swift
override func viewDidLoad() {
    super.viewDidLoad()
    guard let fl = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout else { return }
    fl.sectionHeadersPinToVisibleBounds = true
}
```

<img width="365" alt="スクリーンショット 2017-11-12 1.00.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/100736/2b5e1e46-c9fb-12c4-7af3-35694a759fb4.png">

# xibで作ったヘッダを設定する

ヘッダはこんなふうに作りました。

<img width="887" alt="スクリーンショット 2017-11-12 22.15.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/100736/ff882d0b-0df8-ec91-8c95-f4e4feacddd7.png">

```swift:XibHeader.swift
final class XibHeader: UICollectionReusableView {

    @IBOutlet weak var sectionLabel: UILabel!

    static let identifier: String = "XibHeader"

    static func nib() -> UINib {
        return UINib(nibName: XibHeader.identifier, bundle: nil)
    }
}
```

上記ヘッダをセットするために、`ViewController.swift`に以下のコードを足しています。

```swift:ViewController.swift
override func viewDidLoad() {
    super.viewDidLoad()

    // 今まで書いた他の要素は省略

    collectionView?.register(XibHeader.nib(), forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: XibHeader.identifier)
}
```

```swift
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {

    if kind == UICollectionElementKindSectionHeader {
        if indexPath.section == 0 || indexPath.section == 4 {
            guard let xibHeader = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: XibHeader.identifier, for: indexPath) as? XibHeader else {
                fatalError("Could not find proper header")
            }
            xibHeader.sectionLabel.text = "section \(indexPath.section)"
            return xibHeader
        } else {
            guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "SectionHeader", for: indexPath) as? SectionHeader else {
                fatalError("Could not find proper header")
            }
            header.sectionLabel.text = "section \(indexPath.section)"
            return header
        }
    }

    return UICollectionReusableView()
}
```

<img width="365" alt="スクリーンショット 2017-11-12 22.13.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/100736/c90dee11-39a1-076b-1b2c-c2c90556422b.png">