BLACK FRIDAY SALE: Save big on all my Swift books and bundles! >>

SOLVED: UITableViewCell - Can't swipe cell that had an action performed on it until another cell swiped

Forums > Swift

More specifcally, it seems to be repeating the action every time the cell is tapped until I swipe another cell, then it behaves normally.

I've got a UITableView that lists members of a study list, and I've got swipe actions from both sides. On the leading side I've got Focus/Unfocus (depending on the current state of the item, and on the trailing side I've got Ignore/Unignore.

Here's the basics of how I've got things set up:

class StudyListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
  private var swipedRow: Int? // Tracks the last row that was swiped so we can reset it before the view disappears

  var listdata: [Any] = [] // Data store.  Is a custom data type; this is just a dummy so it's represented in the sample code

  override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)

        if let row = swipedRow { // The navigation view is one tab in a tab controller, so we want to reset the swiped row before switching tabs
            let indexPath = IndexPath(row: row, section: 0)
            swipedRow = nil // Unset the value.  Otherwise if it resets a row and we come back later without swiping, it will try to reset the row a second time, causing visual issues.

            tableView.reloadRows(at: [indexPath], with: .automatic) // Reset the row
        }
    }

  func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        swipedRow = indexPath.row

        let focus = UIContextualAction(style: .normal, title: "Focus") { [weak self, indexPath] (action, view, boolValue) in
            if let self = self {
        let listItem = listdata[indexPath.row]

                self.swipeFocus(listItem) // Performs list magic, adds to focused items.  Does not interact with table view.

                self.swipedRow = nil
                self.tableView.reloadRows(at: [indexPath], with: .automatic) // Reset the row
            }
        }

        focus.backgroundColor = .systemGreen

        let unfocus = UIContextualAction(style: .normal, title: "Unfocus") { [weak self, indexPath] (action, view, boolValue) in
            if let self = self {
                let listItem = listdata[indexPath.row]

                self.swipeUnfocus(listItem) // Performs list magic, adds to focused items.  Does not interact with table view.

                self.swipedRow = nil
                self.tableView.reloadRows(at: [indexPath], with: .automatic) // Reset the row
            }
        }

        unfocus.backgroundColor = .systemOrange

        let swipeActions = UISwipeActionsConfiguration(actions: [focus, unfocus])

        return swipeActions
    }

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

        let exclude = UIContextualAction(style: .normal, title: "Exclude") { [weak self, indexPath] (action, view, boolValue) in
            if let self = self {
                let listItem = listdata[indexPath.row]

                self.swipeExclude(listItem) // Performs list magic, adds to focused items.  Does not interact with table view.

                self.swipedRow = nil
                self.tableView.reloadRows(at: [indexPath], with: .automatic) // Reset the row
            }
        }

        exclude.backgroundColor = .systemOrange

        let unexclude = UIContextualAction(style: .normal, title: "Unexclude") { [weak self, indexPath] (action, view, boolValue) in
            if let self = self {
                let listItem = listdata[indexPath.row]

                self.swipeUnexclude(listItem) // Performs list magic, adds to focused items.  Does not interact with table view.

                self.swipedRow = nil
                self.tableView.reloadRows(at: [indexPath], with: .automatic) // Reset the row
            }
        }

        unexclude.backgroundColor = .systemGreen

        let swipeActions = UISwipeActionsConfiguration(actions: [unexclude, exclude])

        return swipeActions
    }
}

As mentioned, swiping reveals the options appropriately, and swiping on another row closes the first and opens the second. Swiping and tapping a button performs the action as it is supposed to.

Once an action has been performed, however, the button appears to be reset, but remain in an active state—every time I try to swipe on the row or tap on it it repeats the action of the button that was tapped (have verified this via print statements in the console).

The only way to clear this behavior is to swipe on another row, after which it will start behaving properly.

Am I using the right commands to reset the rows, or is there something else I should be using instead?

1      

Hi, everyone!

Figured out what I was doing wrong, so I wanted to share the answer here in case someone else comes across the same issue down the line.

Since it's very simple to demonstrate, I'm just going to pull one of the actions out; all affected actions should be updated in a similar manner. For clarity's sake, I've changed the name of the boolValue argument passed to the action to completionHandler.

let focus = UIContextualAction(style: .normal, title: "Focus") { [weak self, indexPath] (action, view, completionHandler) in
  if let self = self {
    let listItem = listdata[indexPath.row]

    self.swipeFocus(listItem) // Performs list magic, adds to focused items.  Does not interact with table view.

    self.swipedRow = nil
    self.tableView.reloadRows(at: [indexPath], with: .automatic) // Reset the row

    completionHandler(true) // New Line
   }

   completionHandler(false) // New Line
}

Long story short is that the boolValue that I had included initially because it was in the sample code I was learning from is a reporting value that tells the system you've finished the action (it shows as (Bool) -> Void if you focus the inspector on it). Doing this runs a couple of actions that tells the system you're done with the cell, and prepares it to receive another action straight away.

1      

Hacking with Swift is sponsored by RevenueCat

SPONSORED In-app subscriptions are a pain to implement, hard to test, and full of edge cases. RevenueCat makes it straightforward and reliable so you can get back to building your app. Oh, and it's free if your app makes less than $10k/mo.

Learn more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.