Mat
Mat

Reputation: 6324

fatal error: Index out of range on second cell deletion

In my custom cell I have a timer. When the count down reach 0, I call my delegate method and the cell is automatically deleted. The problem is that when the second cell reach 0, my app crashes with the error fatal error: Index out of range.

In my custom cell I setup my data:

protocol MyDelegateName {
func removeOfferExpired(offerId: String, indexPath: IndexPath)
}

class MyCustomCell: UITableViewCell {
  var offer:Offers?
  var cellIndexPath:IndexPath?
  var delegate:MyDelegateName?


  func setupData(offer:Offers, indexPath:IndexPath){
    self.offer = offer
    self.cellIndexPath = indexPath 
    //...other code not relevant 
  }

//When the time reach zero I call the following method 

func updateTime() {
    if timeLeft > 0 {
        timeLeft = endTime.timeIntervalSinceNow
        offerExpiresLabel.textColor = UIColor.white
        offerExpiresLabel.text = timeLeft.hmmss
    }else {
        offerExpiresLabel.textColor = UIColor.red
        offerExpiresLabel.text = "Offer Expired"
        timer.invalidate()
        self.delegate?.removeOfferExpired(offerId: (self.offer?.offer_id!)!, indexPath: self.cellIndexPath!)
    }
}

In my ViewController I setup my cell data inside cellForRowAt:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let offer = offers[indexPath.row]
    let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! MyCustomCell
    cell.setupData(offer: offer, indexPath:  indexPath)
    cell.delegate = self
    return cell
}

Then inside func removeOfferExpired(offerId: String, indexPath: IndexPath) I have tried to use:

1. self.offers.remove(at: indexPath.row)
   self.tableView.reloadData()

2. self.offers.remove(at: indexPath.row)
   self.tableView.deleteRows(at: [indexPath], with: .automatic)
   self.tableView.reloadData()

3. //and even try to "wrap" it inside begin/end updates
   tableView.beginUpdates()
   self.offers.remove(at: indexPath.row)
   self.tableView.deleteRows(at: [indexPath], with: .automatic)
   tableView.endUpdates()

it always crashes the second times. I understand that the indexPath I assign to the cell in setupData is not the same after the first cell is deleted but I thought reloadData was the way to go to update the indexPath in the remaining cells.

Upvotes: 1

Views: 158

Answers (2)

Mat
Mat

Reputation: 6324

As rmaddy said, what I was doing it was completely wrong. This is what I did based on his answer:

func updateTime() {
    if timeLeft > 0 {
        timeLeft = endTime.timeIntervalSinceNow
        offerExpiresLabel.textColor = UIColor.white
        offerExpiresLabel.text = timeLeft.hmmss
    }else {
        offerExpiresLabel.textColor = UIColor.red
        offerExpiresLabel.text = "Offer Expired"
        timer.invalidate()
        // when the time reach zero I passed self to the delegate instead of the indexPath
        self.delegate?.removeOfferExpired(offerId: (self.offer?.offer_id!)!, cell: self as UITableViewCell)
    }
}

protocol MyDelegateName {
  func removeOfferExpired(offerId: String, cell: UITableViewCell) // delegate method now passes the cell instead of the index
}

func removeOfferExpired(offerId: String, cell: UITableViewCell) {
    // and then I get the index path from the cell 
    let indexPath = tableView.indexPath(for: cell)
    self.offers.remove(at: (indexPath?.row)!)
    self.tableView.deleteRows(at: [indexPath!], with: .automatic)
}

Upvotes: 0

rmaddy
rmaddy

Reputation: 318824

Your primary issue is that fact that you tell a cell its index path and your cell then passes that index path to its delegate. But a cell's index path isn't stable. It changes as other rows are added, removed, or moved.

The method of your cell protocol should pass itself (the cell) as a parameter, not an index path. Then the delegate can query the table view to find the cell's up-to-date index path and perform the row deletion based on that up-to-date index path.

Upvotes: 3

Related Questions