LoginSignup
15
13

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-05-07

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

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

UICollectionViewの場合

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

ActivityFooterView

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

footer.swift
import UIKit

class ActivityFooterView: UICollectionReusableView {
    @IBOutlet weak var activityIndicator: UIActivityIndicatorView!
}

UICollectionViewController(or Delegate)

UICollectionViewDelegateのheader/footerが表示される直前に呼ばれる collectionView(_:willDisplaySupplementaryView:forElementKind:at:) delegateメソッドでアニメーション再生の関数を叩いていきます。

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

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保持しておきます。

private var activityIndicator = UIActivityIndicatorView(style: .gray)

tableFooterに設定

override func viewDidLoad() {
    super.viewDidLoad()

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

最後のセクションの最後のセルのwillDisplayの時にIndicatorを表示

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
        // データ読み込みのコード
    }
}

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

activityIndicator.stopAnimating()
tableView.tableFooterView?.isHidden = true
15
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
13