Deleting UITableViewCell causing crash - ios

I know there's a number of posts on this subject and I've been through most of them but I still can't solve an issue I'm having when deleting a tableViewCell causing my app to crash with the follow error:
Assertion failure in -[UITableViewRowData rectForRow:inSection:heightCanBeGuessed:]
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'request for rect at invalid index path (<NSIndexPath: 0xc000000000000016> {length = 2, path = 0 - 0})'
I explicitly set the height of my rows using
tableView.rowHeight = 140
My code for the delete is as follows
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let deleteAction = UITableViewRowAction(style: .destructive, title: "Delete") { (action, indexPath) in
let object = self.datastore[indexPath.row]
do {
try self.realm?.write {
self.realm?.delete(object)
self.datastore.remove(at: indexPath.row)
self.tableView.deleteRows(at: [indexPath], with: .none)
}
} catch let error {
}
}
deleteAction.backgroundColor = .red
return [deleteAction]
}
This does delete the item correctly from the dataStore. I've tried a number of different solutions here such as calling the delete on the mainThread, reloading the whole dataSource etc. but it still crashes.
My numberOfRowsInSection method gets called and is return the correct number of rows after the deletion so that doesn't appear to be the issue.
Adding a general exception breakpoint though I think it's crashing here
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? Cell {
cell.image.kf.cancelDownloadTask()
}
}
kf here is the kingfisher library as I want to cancel an image download if the cell is removed
What I have noticed is that if I use
`tableView.dequeueReusableCell(withIdentifier: "Cell")`
instead of
tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
This doesn't crash, so that might be my solution, but I'm eager to understand what's wrong here. I understand that the 'forIndexPath' method should always return a cell but in this instance is crashing because of a nil.
I have the same deletion code in another viewController that doesn't rely on the didEndDisplaying:Cell method and this doesn't crash

You need to change this line in didEndDisplaying. you need to get existing cell.
let cell = tableView.cellForRowAtIndexPath(indexPath)

I had a very similar issue.
Make sure that when you're deleting your cell, your goal is not to remove the entire section. My tableView is built on a varying length of sections instead of rows.
I found that by replacing
tableView.deleteRows(at: [indexPath], with: .automatic
with:
tableView.deleteSections(IndexSet(integer: indexPath.section), with: .automatic)
I was able to get fix the bug. Make sure you want to delete the entire section however before you implement this change!
Hope this helps, cheers :)

Related

How do you make prepareForReuse() work right?

I have a table view with ten cells. Each of them contains an image view. When scrolling, sometimes images start jumping from one cell to another - I guess reuse does not work. I tried to reset the image to nil in prepareForReuse(), but the problem persists.
catImageView.image = nil
I also reset indexPath in cellForRowAt, but that didn't work either.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: SearchCell.identifier, for: indexPath) as? SearchCell else { return UITableViewCell() }
let cat = cats[indexPath.row]
cell.setup(cat)
cell.catImageView.image = nil
return cell
}
In rxSwift for this I cleaned disposedBag. What can be done in this case?

Is there a way to know when a UITableViewCell is removed from UITableView?

I am showing a UITableView which is driven by RxRealmDataSources.
I need to perform some actions when a row in the table gets deleted.
Is there a way such that whenever a row gets deleted from the table, a function gets called with the indexpath of the deleted row?
Edit -
The UI of a cell of the UITableView in my app depends on 2 things -
A data object that is fetched from the realm db ( info )
The index position of the row
Whenever, a cell gets deleted, I need to update the UI of its next cell.
If the only way the db ever got updated was by the direct action of the user, then I could have used func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) method to get the indexPath of the cell that should be deleted and update the UI of the next cell.
However, the db is synced to cloud and the db is binded to the table view so that I do not have control on when cells gets added or deleted. It is for this reason, I wanted to know if there is a way to know when a cell is removed from UITableView
Due to the reusability of cells in UITableView, cells are not actually deleted until the table itself is deallocated.
I might assume that by 'deleting' cell you mean cell disappearing from the screen. In this case the following function of UITableViewDelegate might help you (called when the cell is not visible any more):
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath)
In a comment you said:
By 'deleting', I mean when the cell is removed from the tableview like when we swipe the cell to the left to remove it.
Since you tagged RxSwift, the solution is to use itemDeleted as in:
tableView.rx.itemDeleted
.subscribe(onNext: { print("delete item at index path \($0) from your model.")})
.disposed(by: bag)
If you aren't looking for an Rx solution, then your question is a dup of:
Add swipe to delete UITableViewCell
I was able to solve this by subclassing the UITableView class and overriding the func deleteRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation) method.
You have to implement 1 delegate method of UITableView.
trailingSwipeActionsConfigurationForRowAt
It's easy to implement. This delegate method will be called twice, one when you swipe and again when you press to delete a row.
`enter code here`
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let config = UISwipeActionsConfiguration(actions: [makeDeleteContextualAction(forRowAt: indexPath)])
config.performsFirstActionWithFullSwipe = true
return config
}
private func makeDeleteContextualAction(forRowAt indexpath:IndexPath) -> UIContextualAction {
let deleteAction = UIContextualAction(style: .destructive, title: LocalizableConstants.constLS_global_delete()) { (action, swipeButtonView, completion) in
let product = self.products[indexpath.row]
if let quantity = product.vo_quantity(), let amount = product.vo_priceFide() {
self.totalProducts -= Int(truncating: quantity)
self.totalAmount -= amount.doubleValue * quantity.doubleValue
}
DispatchQueue.main.async {
self.lbBasketNumber.text = String(self.totalProducts)
self.lbTotalAmount.text = String(self.totalAmount)
}
self.products.remove(at: indexpath.row)
self.tableView.deleteRows(at: [indexpath], with: .fade)
if #available(iOS 13.0, *) {
action.image = ImagesConstants.constIMG_XCA_mini_icon_red_trash()
action.image?.withTintColor(ConstantsColor.const_COLOR_RED())
action.backgroundColor = ConstantsColor.const_COLOR_WHITE()
} else {
action.title = LocalizableConstants.constLS_global_delete()
}
completion(true)
}
return deleteAction
}

Swipe to delete animation overlaps with other tableview cells

I have installed a swipe-to-delete function in my tableview. Nothing special there. However, when performed, the red delete button (row action) overlaps while retracting to the right with the cell below even though the deleted cell has already disappeared.
In the editActionsForRowAt function I first remove the value from the array and then from a reference in Firebase. Here is the code:
func tableView(_ tableView: UITableView, editActionsForRowAt: IndexPath) -> [UITableViewRowAction]? {
let delete = UITableViewRowAction(style: .normal, title: "Verwijder") { action, index in
tableView.beginUpdates()
let item = self.joinedArrayFiltered[editActionsForRowAt.section][editActionsForRowAt.row]
let indexPath: IndexPath = IndexPath(row: editActionsForRowAt.row, section: editActionsForRowAt.section)
self.joinedArrayFiltered[editActionsForRowAt.section].remove(at: editActionsForRowAt.row)
self.ref.child("productsObserver").child(item.productId).child("wishlistUsers").child(self.userID).removeValue()
tableView.deleteRows(at: [indexPath], with: UITableViewRowAnimation.bottom)
tableView.endUpdates()
}
delete.backgroundColor = .red
return [delete]
}
To help visualize the issue, please see the attached GIF below.
Any suggestions would be helpful, thanks.

iOS: Cannot use Activity Indicator inside UITableViewCell

I want to have ActivityIndicatorView inside each UITableViewCell and start animation after tap (to show that action is in progress). But I cannot start the animation effect.
I added ActivityIndicatorView to my cell prototype and connected it via #IBOutlet. This is my code to start animation after user selects table row:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedAction = actions[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "actionCell", for: indexPath) as! ActionTableViewCell
cell.activityInProgressIndicator.startAnimating()
}
I also tried adding new instance of ActivityIndicatorView as cell.accessoryView. Without any effect.
I also tried to update the cell either via tableView.reloadData() (which I would like to avoid) and tableView.beginUpdates() and tableView.endUpdates()
Ideally this spinning indicator should be hidden but setting .isHidden = false in didSelectRowAt also does not work.
Just replace
let cell = tableView.dequeueReusableCell(withIdentifier: "actionCell",
for: indexPath) as! ActionTableViewCell with below line
let cell = tableView.cellForRow(at: indexPath) as! ActionTableViewCell.
Hope it will work!!
The problem is that you're creating new instance of ActionTableViewCell when you use tableView.dequeueReusableCell inside didSelectRowAt, which is wrong.
You need to use something like this:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let cell = tableView.cellForRow(at: indexPath) as? ActionTableViewCell else { return }
cell.activityInProgressIndicator.startAnimating()
}
This will fix your issue.

Delete cell from tableview

I am using core data to populate a table view cell. I am trying to implement a function to delete the cell however I keep getting this error
Assertion failure in -[UITableView _endCellAnimationsWithContext:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3600.6.21/UITableView.m:1610
2017-07-15 22:07:51.077 Annoying Alarm- Alarm can't be snoozed or stopped![7608:793341] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (1),
My code for view controller is as follows
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let cell = tableView.cellForRow(at: indexPath) as! AlarmCell
let identifier = cell.time.text
for object in self.controller.fetchedObjects!{
if object.timeTitle == identifier {
print("HELLO!")
print("deep inside if")
context.delete(object)
ad.saveContext()
tableView.deleteRows(at: [indexPath], with: .fade)
//tableView.reloadData()
}
}
}
}
The core date part works just fine and when I reopen the app the cell does disappear. My problem is when I actually press delete the
tableView.deleteRows(at: [indexPath], with: .fade)
function throws the error displayed above. When I comment it out, no crash occurs but the cell is not deleted unless I go to another view then come back to this one.
error indicates that after deleting the row you should remove it from your array and update your array. then you should reload your tableview so your tableviews numberofrows get updated.

Resources