LoginSignup
21
27

More than 5 years have passed since last update.

【Swift】UITableViewにindexPathをinsertした後scrollさせる

Posted at

やりたかっこと

前提として

- 一番下までスクロールしたらAPIを叩いてロードする仕組みを実装している
- APIから読み込んでいるフラグをクラスの変数として保持している
- フラグがtrueの場合はロードしない

というクラスを組んだ場合、フラグをfalseにするタイミングによってはUITableView#scrollToRowAtIndexPath:atScrollPosition:animatedを使った段階でまたAPIをロードしてしまいます。

それを阻止した処理ができたので書いておきます。

ソースコード

DataSource.swift
class DataSource: UITableViewDataSource {
    // UITableViewDataSourceの処理は今回はいらないので省略

    // handlerは追加する分のNSIndexPathの配列を返す
    func reloadData(#handler: ([NSIndexPath] -> Void)) {
        // ここでAPIとごにょごにょ
        var indexPaths:[NSIndexPath] = []
        //
        // NSIndexPathを追加する処理
        //

        handler(indexPaths)
    }

    // handlerは追加する分のNSIndexPathの配列を返す
    func addData(#handler: ([NSIndexPath] -> Void)) {
        // ここでAPIとごにょごにょ
        var indexPaths:[NSIndexPath] = []
        //
        // NSIndexPathを追加する処理
        //

        handler(indexPaths)
    }
}
ViewController.swift
class ViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!
    // これがフラグの変数
    private var isLoading: Bool = false
    // これは独自クラス
    private var dataSource: DataSource = DataSource()

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.delegate = self
        tableView.dataSource = dataSource
    }
}

// MARK: - load data

extension ViewController {

    private func reloadData() {
        isLoading = true
        dataSource.reloadData(handler: {[weak self] indexPaths in
            self?.insertAtIndexPaths(indexPaths)
        })
    }

    private func addData() {
        isLoading = true
        dataSource.reloadData(handler: {[weak self] indexPaths in
            self?.insertAtIndexPaths(indexPaths)
        })
    }

}

extension ViewController {

    // ここの処理が今回やりたかったことです。
    // 今回は追加するindexPathsの頭のNSIndexPathに追加後スクロールするようにしました。
    private func insertAtIndexPaths(indexPaths: [NSIndexPath]) {
        CATransaction.begin()
        CATransaction.setCompletionBlock({
            self?.loading = false
        })
        self?.tableView.beginUpdates()
        self?.tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: .Bottom)
        self?.tableView.endUpdates()
        if let firstIndexPath = indexPaths.first {
            self?.tableView.scrollToRowAtIndexPath(firstIndexPath, atScrollPosition: .Bottom, animated: true)
        }
        CATransaction.commit()
    }

}

extension ViewController: UITableViewDelegate {  // UITableViewDelegateの処理は今回はいらないので省略
    func scrollViewDidScroll(scrollView: UIScrollView) {
        if isLoading || !isViewLoaded() {
            return
        }

        let scrollValue = scrollView.contentSize.height - scrollView.bounds.height
        if scrollValue > scrollView.contentOffset.y {
            return
        }

        // 一番下までスクロールしたというこでデータを追加する
        addData()
    }
}

読んでもらえればだいたいわかるかと思います...。

補足

今回ずっと戦っていたのが以下の処理のところになります。

extension ViewController {

    private func insertAtIndexPaths(indexPaths: [NSIndexPath]) {
        CATransaction.begin()
        CATransaction.setCompletionBlock({
            self?.loading = false
        })
        self?.tableView.beginUpdates()
        self?.tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: .Bottom)
        self?.tableView.endUpdates()
        if let firstIndexPath = indexPaths.first {
            self?.tableView.scrollToRowAtIndexPath(firstIndexPath, atScrollPosition: .Bottom, animated: true)
        }
        CATransaction.commit()
    }

}

ここでUITableView#insertRowsAtIndexPaths:withRowAnimation:が終わった後にUITableView#scrollToRowAtIndexPath:atScrollPosition:animated:を実行して、すぐにisLoadingfalseにするとすぐにaddData()が呼び出されてしまいました。

原因

原因は考えれば簡単なのですが、UITableView#scrollToRowAtIndexPath:atScrollPosition:animated:はアニメーションなので終わるまで時間がかかります。

しかもスクロールアニメーションをしているのでUITableViewDelegate#scrollViewDidScroll:がコールされます。

そこでisLoadingfalseになっているので、addData()が呼び出されてしまいます。

対応

UITableView#scrollToRowAtIndexPath:atScrollPosition:animated:が終了した後にisLoadingfalseにすれば大丈夫ということでCATransactionを使うことにしました。

今回の件で説明するとCATransaction#begin()からCATransaction#commit()の間の処理が終了したらCATransaction#setCompletionBlock:block:(() -> Void)が呼び出される仕組みとなっています。

終わりに

やっと綺麗に思ったとおりのことができて、嬉しくて投稿しました。

21
27
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
21
27