UICollectionView/UITableViewの引っ張り上げ更新の実装では、よくcontentInset
とcontentOffset
を弄くり回して算出してるものが多いのですが、どうにもぱっと見で実装内容が理解できないので(頑張って説明変数使っていても)別の方法を考えていました。
FacebookのiOSアプリ見ているとFooterを使っていそうだったので、真似してみたら上手くいったのでご紹介致します。
R.swiftを使っていたので、適時コードは読み替えて実装お願い致します。
UICollectionViewの場合
CollectionViewでの実装の時は、セクションフッターを使っての解決を目指します。
ActivityFooterView
今回自分はStoryBoardを使ったのでUICollectionViewにFooter追加して以下のクラスと紐付けしましたが、使わない派の人はviewDidLoadあたりでregisterお願いします。
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のキャッシュプロパティを使う必要があります。
/* 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