I’m dealing with an issue I can’t work around… I have a table of names, from a DB-array of customers, every customer has a name property among other data members.
I can delete rows within a section successfully, but what I can’t do it's deleting the section (when the last row within that section gets deleted, section must disappear).
I got:
'NSInternalInconsistencyException', reason: 'Invalid update: invalid
number of sections. The number of sections contained in the table
view after the update (3) must be equal to the number of sections
contained in the table view before the update (4), plus or minus the
number of sections inserted or deleted (0 inserted, 0 deleted).
I know the table does some sanity checking behind the scenes of the data and this should match, but I can’t figurate exactly when, before calling deleteRowsAtIndexPaths? after? When should I update my property and/or dictionary? Should I manage numberOfSectionsInTableView data-source method?
I repeat, for rows deleting it’s working alright, the table moves out the row and gets updated properly. Last row on section is the deal...
I guess I'm missing something, that’s why I’m asking… Couldn’t find any help reading around either.
Thank you all very much!
func tableView(tableView: UITableView!, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath!) {
if (editingStyle == UITableViewCellEditingStyle.Delete) {
// handle delete (by removing the data from the array and updating the tableview)
//Check if delete was press
if editingStyle == .Delete {
//Delete row from dataSource
if let tv = tableView
{
customerList.removeAtIndex(returnPositionForThisIndexPath(indexPath, insideThisTable: tableView))
// Deletes the name of the customer from the customer list array, sorted by name
fillArrayOfNames()
//Fill the array of names for the sections-table, creating a dictionary with the name initials
//updated from the customer list array (below)
tv.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade) //Crash in this line
tableView.reloadData()
}
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return dictionaryOfPatientsInitials.count
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var keysFromDictionary = dictionaryOfPatientsInitials.keys.array
keysFromDictionary.sort(<)
let keyByOrder = keysFromDictionary[section]
let arrayInThisSection = dictionaryOfPatientsInitials[keyByOrder]
return arrayInThisSection!.count
}
You're almost there but you are going to need a way of detecting that a section has vanished, and which one has gone at which point you can call deleteSections
Bracket the update section in a beginUpdate / endUpdate call but do NOT call reloadData (See the docs for those methods about it)
/**
remove customer from model layer
:param: index index of customer to remove
:returns: return section that was removed or nil if none was
*/
func removeCustomer(index:Int)->Int? {
var removedSectionOrNil:Int? = nil
//logic to remove customer, rebuild model and detect if section has gone also
return removedSectionOrNil
}
func tableView(tableView: UITableView!, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath!) {
if (editingStyle == UITableViewCellEditingStyle.Delete) {
// handle delete (by removing the data from the array and updating the tableview)
//Check if delete was press
if editingStyle == .Delete {
//Delete row from dataSource
if let tv = tableView
{
tv.beginUpdates()
let position = returnPositionForThisIndexPath(indexPath, insideThisTable: tableView)
let removedSection = removeCustomer(position)
tv.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade) //Crash in this line
if let removedSection = removedSection {
tv.deleteSections(sections:NSIndexSet(index: removedSection) as IndexSet, withRowAnimation: .Automatic)
}
tv.endUpdates()
}
}
}
Without seeing the rest of your code , this should work but tableViews can be tricky when doing vanishing sections.
I am sitting with a similar issue - however I have approached the problem from a non-programming way.
What I have done is to have two tableviews one that simply list all "Customer" key's to their details. (I use a key in the dictionary which is written to the DB, and a second to list "customers" details - each customer is a section.
I have then used Checkmark in the key table as my editing accessory for the cell in the table. Using the delete option for editing will delete the key and then I then simply remove that dictionary item in this code:
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == UITableViewCellEditingStyle.Delete
actDict[keyArray[indexPath.row]] = nil
keyArray.removeAtIndex(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
try! writeFile!.addValuesToUserFile(actDict)
}
}
This works flawlessly for me.
Sorry for the incomplete initial answer
Related
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
}
I have an array which populates a table view - myPosts.
The first row of the table view is not part of the array.
Each row is its own section (with its own custom footer)
I am trying to perform a delete with the following code:
func tableView(profileTableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if (editingStyle == UITableViewCellEditingStyle.Delete) {
myPosts?.removeAtIndex(indexPath.section - 1)
profileTableView.beginUpdates()
let indexSet = NSMutableIndexSet()
indexSet.addIndex(indexPath.section - 1)
profileTableView.deleteSections(indexSet, withRowAnimation: UITableViewRowAnimation.Automatic)
profileTableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
profileTableView.endUpdates()
...
WS Call
...
}
}
And the log is reporting the following:
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), plus or minus the number of rows inserted or deleted from
that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out
of that section (0 moved in, 0 moved out).'
Obviously the issue is related to 0 moved in, 0 moved out but I don't understand why that is? or what the solution would be?
Number of sections in tableView is as follows:
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if self.myPosts == nil
{
return 1
}
return self.myPosts!.count + 1
}
Updated the answer for Swift 4.2 and made a few additional tweaks:
func tableView(_ tableView: UITableView,
commit editingStyle: UITableViewCell.EditingStyle,
forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
myPosts?.removeAtIndex(indexPath.section - 1)
let indexSet = IndexSet(arrayLiteral: indexPath.section)
profileTableView.deleteSections(indexSet, with: .automatic)
// Perform any follow up actions here
}
}
The use of beginUpdates() and endUpdates() is not necessary, since you are only doing one action that contains animation. If you are doing 2 or more, than it is worth combining them to get a fluid effect.
Also, this makes use of the Swift 3 classes, by doing away with the NSMutableIndexSet() call, which would require a conversion now to work with the deleteSections() call.
So the answer is just removing the line that deletes the rows.
So the code is here to delete:
func tableView(profileTableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if (editingStyle == UITableViewCellEditingStyle.Delete) {
myPosts?.removeAtIndex(indexPath.section - 1)
profileTableView.beginUpdates()
let indexSet = NSMutableIndexSet()
indexSet.addIndex(indexPath.section - 1)
profileTableView.deleteSections(indexSet, withRowAnimation: UITableViewRowAnimation.Automatic)
// profileTableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
profileTableView.endUpdates()
...
WS Call
...
}
}
I need to create a action in editActionsForRowAtIndexPath to delete a row in a table. Following some research on the internet I came to this code:
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
let remove = UITableViewRowAction(style: UITableViewRowActionStyle.Destructive, title: "Remover", handler: { (action: UITableViewRowAction, indexPath: NSIndexPath) -> Void in
self.tableData.removeObjectAtIndex(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
})
return [remove]
}
but now I get this error
UITableView internal bug: unable to generate a new section map with
old section count: 1 and new section count: 0
When you delete a row from UITableView, you need to take care of two things.
First you should call tableView.deleteRowsAtIndexPaths only after you remove the object from the data source already. Because it'll check for the count again and ensure that the resultant data source have one less value.
Now the second thing to remember is that the no. of section can't be 0 after you delete the last row. If you are returning 0 to numberOfSectionsInTableView, return 1 at least on empty row count. Another option is to actually delete the index too when you delete the last row.
You don't need to use 'editActionsForRowAtIndexPath' method unless you implement your own custom accessory view but other wise you can use the standard swipe left gesture to delete rows and the delegate called is commitEditingStyle
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
tableData.removeObjectAtIndex(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
else if editingStyle == .Insert {
//edit cell
}
}
//change the default 'Delete' text
override func tableView(tableView: UITableView, titleForDeleteConfirmationButtonForRowAtIndexPath indexPath: NSIndexPath) -> String? {
return "Show this instead of delete"
}
Do deleteRowsAtIndexPaths or reloadData, but not both.
What I have is a table view with different cells that display different messages with other people you're chatting with. Upon swiping the cell to the left, a Delete option shows up. I want to be able to delete both the cell itself as well as the Room associated with it in Parse. This is what I have so far:
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == UITableViewCellEditingStyle.Delete {
//Delete messages here
let query = PFQuery(className: "Message")
query.whereKey("Room", equalTo: ??????)
tableView.beginUpdates()
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
tableView.endUpdates()
}
}
This function displays the Delete option, and when I click it, I want it to delete the cell it was clicked in. Does anyone know what goes inside the query? I have a Message class and I want to delete the Room key that's associated with the cell I delete.
Try this :
var dataToDelete:NSMutableArray = [] // this is a dummy array just to replicate your array
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == UITableViewCellEditingStyle.Delete {
var object = dataToDelete[indexPath.row] as! PFObject
tableView.beginUpdates()
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
tableView.endUpdates()
object.delete()
}
}
I am new to swift And programming. Please Anyone Help me for making call in IOS8 using UITabelViewCell left swipe not to delete A Row but to make a call in iPhone using Swift language
If you want to show a Delete or Insert button, you can use the following default functions of UITableView to accomplish that:
// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return NO if you do not want the specified item to be editable.
return true
}
// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
// Delete the row from the data source
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
Although if you want a different custom button, you should have a look at SWTableViewCell at this link: https://github.com/CEWendel/SWTableViewCell