UITableView/UICollectionViewでのFooterローディング

UICollectionView/UITableViewの引っ張り上げ更新の実装では、よく`contentInset`と`contentOffset`を弄くり回して算出してるものが多いのですが、どうにもぱっと見で実装内容が理解できないので(頑張って説明変数使っていても)別の方法を考えていました。
FacebookのiOSアプリ見ているとFooterを使っていそうだったので、真似してみたら上手くいったのでご紹介致します。

R.swiftを使っていたので、適時コードは読み替えて実装お願い致します。

# UICollectionViewの場合
CollectionViewでの実装の時は、セクションフッターを使っての解決を目指します。

# ActivityFooterView
今回自分はStoryBoardを使ったのでUICollectionViewにFooter追加して以下のクラスと紐付けしましたが、使わない派の人はviewDidLoadあたりでregisterお願いします。

```swift:footer.swift
import UIKit

class ActivityFooterView: UICollectionReusableView {
    @IBOutlet weak var activityIndicator: UIActivityIndicatorView!
}
```
# UICollectionViewController(or Delegate)
UICollectionViewDelegateのheader/footerが表示される直前に呼ばれる [`collectionView(_:willDisplaySupplementaryView:forElementKind:at:)`](https://developer.apple.com/documentation/uikit/uicollectionviewdelegate/1618062-collectionview) delegateメソッドでアニメーション再生の関数を叩いていきます。

そこで非同期の関数を呼び出し、クロージャでstopAnimating()やreloadData()を呼び出すことで引っ張り上げ更新は実現されます。
データ取得関数などの結果がdelegateで帰ってくる場合は、`cachedFooterView` のようなfooterViewのキャッシュプロパティを使う必要があります。

```swift:pullup-update.swift
 /* UICollectionViewでの引っ張り上げ更新に対して、Footerを利用するアプローチを選択した。
    理由としては、contentInsetやoffsetをぐちゃぐちゃ弄ると意図しない挙動を作り込みやすいし、分かりにくい処理になるので避けたかったため
    このアイデアの参考にしたのはFacebookアプリで、おそらく同じような実装をしている。試してみてもらうと分かるが全く同じ挙動をする。
    */
    override func collectionView(_ collectionView: UICollectionView,
                                 viewForSupplementaryElementOfKind kind: String,
                                 at indexPath: IndexPath) -> UICollectionReusableView {
        let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind,
                                                                           withReuseIdentifier: R.reuseIdentifier.footer,
                                                                           for: indexPath)!
        return footerView
    }

    override func collectionView(_ collectionView: UICollectionView,
                                 willDisplaySupplementaryView view: UICollectionReusableView,
                                 forElementKind elementKind: String,
                                 at indexPath: IndexPath) {
　　　　　let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: elementKind,
                                                                           withReuseIdentifier: R.reuseIdentifier.footer,
                                                                           for: indexPath)!

        footerView.activityIndicator.startAnimating()
        doSomething() { // closure
            footerView.activityIndicator.stopAnimating()
            collectionView.reloadData()
        }
    }
```

# UITableViewの場合
UITableViewの場合は `tableFooterView` を使って解決を目指します。これはSectionFooterが画面に残り続ける挙動が具合悪いからです。

## Viewの初期化
初期化してVC保持しておきます。

```swift:
private var activityIndicator = UIActivityIndicatorView(style: .gray)
```

## tableFooterに設定
```swift:
override func viewDidLoad() {
    super.viewDidLoad()

    activityIndicator.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: 44)
    tableView.tableFooterView = activityIndicator
}
```

## 最後のセクションの最後のセルのwillDisplayの時にIndicatorを表示
```swift:
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    let lastSectionIndex = tableView.numberOfSections - 1
    let lastRowIndex = tableView.numberOfRows(inSection: lastSectionIndex) - 1

    let showsTableFooterView = indexPath.section ==  lastSectionIndex && indexPath.row == lastRowIndex
    if showsTableFooterView {
        activityIndicator.startAnimating()
        tableView.tableFooterView?.isHidden = false
        // データ読み込みのコード
    }
}
```

データ読み込みが終わったタイミングで、アニメーションをストップさせてください。

```swift:
activityIndicator.stopAnimating()
tableView.tableFooterView?.isHidden = true
```