UITableViewでSwipeを提供する際にはcompletionHandlerを呼ぼう


UICollectionViewベース実装の時代にあれですがTipsです。

tableView(_ tableView:trailingSwipeActionsConfigurationForRowAt: IndexPath) -> UISwipeActionsConfiguration?

func tableView(_ tableView:leadingSwipeActionsConfigurationForRowAt: IndexPath) -> UISwipeActionsConfiguration?

上記の2つは現在スワイプアクションを提供するための唯一の公式APIですが、公式ドキュメントの解説が結構少なくて落とし穴があります。

今回の記事で紹介するのは、その落とし穴の1つと対処法です。

UIContextualActionが最新にならない

以下のような、何らかの条件で表示するアクションが変わるように実装していたとします。ですが、これでは「アクションが最新にならない」という問題に遭遇することがあります。

    func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {

        let action = UIContextualAction(style: .normal,
                                              title: nil) { [weak self] (_, _,_) in
            // Something to do
        }

        guard yourStatement else {
            return .init(actions: [action])
        }

        let secondAction = UIContextualAction(style: .normal,
                                               title: nil) { [weak self] (_, _,_) in
           // Something to do
        }

        return .init(actions: [action, secondAction])
    }

原因

ネット記事などを見ても上記のようなコードはよく見かけますが、UIContextualActionのクロージャーの3番目のパラメータであるUIContextualAction.Handlerを呼んでいません。

このhanderには以下のような説明があり、指定してくれとは書いてありますが、呼ばなかった場合にどうなるかの解説はありません。

completionHandler
アクションを実行した後に実行するためのハンドラブロックです。このブロックは戻り値を持たず、以下のパラメータを取ります。
actionPerformed
アクションを実行したかどうかを示すブール値。アクションを実行した場合は true、何らかの理由でアクションを実行できなかった場合は false を指定します。

いくつかコードを動作されてみた限りの推測ですが、completionHandlerにBool値をつけて呼ぶことでUITableViewはスワイプが完了したことを知り何らかの完了・更新処理を内部的に実行するため、これを呼ばないと不整合が発生してテーブルに意図しない挙動が発生するようです。

対処後のコード

対処後のコードではパラメーターでcompletionHandlerをとり、何らかの処理が終わった後にcompletionHandler(true)として実行しています。

    func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {

        let action = UIContextualAction(style: .normal,
                                              title: nil) { [weak self] (_, _, completionHandler) in
            // Something to do
            completionHandler(true)
        }

        guard yourStatement else {
            return .init(actions: [action])
        }

        let secondAction = UIContextualAction(style: .normal,
                                               title: nil) { [weak self] (_, _, completionHandler) in
            // Something to do
            completionHandler(true)
        }

        return .init(actions: [action, secondAction])
    }

まとめ

たった2行ほどの処理ですが、completionHandler(true or false)の実装有無に気づかないだけで数時間を費やしてしまうことがないように、この記事が助けになれば良いかなと思っております。