I can remove keys from Firebase and update the table. However once I get to the last one the cell doesn't get removed. I have counted the array inside the table after each delete and it shows there is always one despite the database removing the key. Only until I restart the app then the table is empty. I have looked at this answer: Swift: 'attempt to delete row 0 from section 0 which only contains 0 rows before the update' and refresh tableView after deleting cell but it doesn't solve my issue.
Here is my commit editingStyle:
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let attendee = AttendeeManager.shared.attendees[indexPath.row]
let databaseRef = Database.database().reference(withPath: "users/\(uid)/meetList/\(attendee.key!)")
databaseRef.removeValue(completionBlock: { (_, _) in
print(AttendeeManager.shared.attendees.count)
self.tableView.reloadData()
})
}
}
This is my data structure example:
{
"users": {
"Juoiuf0N6FNDn3rYlM6X6UK62": {
"meetList": {
"DCVBHGVnvjfgvmfkgjgwgkgjffjg": true,
"C076OYmVTzJF22HB3ggjftY2XpED2": true,
"DhkgjrlorTKKjhrgkegfwmdhffwn": true
}
}
}
}
Note: AttendeeManager is a singleton that manages the Attendee object.
Related
I have a dozen table view controllers that all work as expected, then I have this one which crashes with:
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), 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).'
The code doing this has been modified several times with different options but to no effect.
import UIKit
import CoreData
class QTypeVCY: UITableViewController, NSFetchedResultsControllerDelegate
{
let app = UIApplication.shared.delegate as! AppDelegate
override func viewDidLoad()
{
super.viewDidLoad()
}
override func numberOfSections(in tableView: UITableView) -> Int
{
let sections = frc.sections
return sections!.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
guard let sections = self.frc.sections else
{
fatalError("No sections in fetchedResultsController")
}
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "QQQQ", for: indexPath)
let qtype = frc.object(at: indexPath)
cell.textLabel?.text = qtype.title
return cell
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete
{
do
{
let qtype = frc.object(at: indexPath)
let context = self.frc.managedObjectContext
context.delete(qtype)
try context.save()
tableView.deleteRows(at: [indexPath], with: .fade)
}
catch
{
debugPrint(error)
}
} 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
}
}
lazy var frc: NSFetchedResultsController<Qtype> =
{
let context = self.app.persistentContainer.viewContext
let req: NSFetchRequest<Qtype> = Qtype.fetchRequest()
req.fetchBatchSize = 10
let sortDescriptor1 = NSSortDescriptor(key: #keyPath(Qtype.specialty), ascending:true)
let sortDescriptor2 = NSSortDescriptor(key: #keyPath(Qtype.title), ascending:true)
req.sortDescriptors = [sortDescriptor1, sortDescriptor2]
let afrc = NSFetchedResultsController(fetchRequest: req, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
afrc.delegate = self
do
{
try afrc.performFetch()
}
catch
{
print(error.localizedDescription)
fatalError("Abort while fetching Qtype")
}
return afrc
}()
}
The crash occurs on the tableview.deleteRows statement. I have tried surrounding the code with beginUpdate/endUpdates, with and without the performFetch, even tried re-entering the code in case I had a typo that I missed. This same basic code is working fine on other tables/view controllers, just this one.
The entity is just made up of strings. I had it with and without relationships to other entities.
The row is deleted since the next time I run the app it is missing. Also, one other thing about this table is that calling reloadData after adding a new object does not add the row. I need to leave the tableview and reenter it. I'm sure the two are related but can't say why.
Since originally posting this, I included the entire VC code instead of just the offending code. I have also tried swapping the Entity with a different entity where this issue does not occur, but the program still crashes even with a different entity.
You forgot to remove the item also from the data source array
let qtype = qtypes[indexPath.row]
let context = self.frc.managedObjectContext
context.delete(qtype)
try context.save()
qtypes.remove(at: indexPath.row) // <--
tableView.deleteRows(at: [indexPath], with: .fade)
I wish I could offer the actual solution, but the above code now works. I had updated XCode to the 1/24/18 version but I don't think that is the solution. I also stripped out a few things I had in the original code that crashed, but then I put them back and it still works. I suspect I had a typo somewhere, but after spending a half hour trying to locate it, I'm declaring victory and moving on.
I just solloved a problem similar to yours.
when deleted data in CoreData, TableView data array also delete before appDelegate.saveContext()
Code in func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath)
self.tableviewArrayData.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
appDelegate.saveContext()
I have a tableview loaded with messages from Firebase. Storing the data is easy, but trying to delete a specific node with an autoID is troubling me.
I know I have to get a reference to the first node which in this case is Posts, but how do I get the next node as a reference? Which in this case this node is an autoId. Below is the code.
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
print(messages)
print(indexPath.row)
var messageIndexRef = postData[indexPath.row]
print(messageIndexRef)
ref?.child("Posts").child("how do I get this auto ID?").removeValue(completionBlock: { (error, ref) in
if error != nil {
print("error \(error)")
}
})
MessagesTableView.reloadData()
}
}
You will need to keep a dictionary that maps each row/index of the table view to the key of the corresponding item in Firebase.
As far as I can see from the snippet you shared, it would at the same level postData. One holds the key the other the value of each row.
If you build both dictionaries while you're reading the data from Firebase (the same moment you get the value), you can then use the dictionary with keys to determine the key of the row the user clicked on.
I have a many to many relationship of "BillSplitters" to "Items", im trying to. Delete a billsplitter from a tableview of billsplitters, but when i look at my items they still list the billsplitters they are related to and havent been deleted. I have tried changing the delete rule from nullify to cascade on both items and bill splitters but nothing seems to be changing.
Heres the code that i think is relevant:
BillSplitter table view:
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let billSplitter = allBillSplitters[indexPath.row]
let currentSplitters = self.bill.mutableSetValueForKey("billSplitters")
let managedContext = self.bill.managedObjectContext
removeBillSplitter(billSplitter)
currentSplitters.removeObject(billSplitter)
do {
try managedContext!.save()
}
catch let error as NSError {
print("Core Data save failed: \(error)")
}
print(currentSplitters)
tableView.reloadData()
}
}
func removeBillSplitter(billSplitter: BillSplitter) {
if let index = allBillSplitters.indexOf(billSplitter) {
allBillSplitters.removeAtIndex(index)
}
}
Items Tableview:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
fetchBillItems()
let cell: NewBillSplitterItemCell = tableView.dequeueReusableCellWithIdentifier("NewBillSplitterItemCell") as! NewBillSplitterItemCell
let item = allItems[indexPath.row]
let numberOfSplitters = item.billSplitters?.count
if numberOfSplitters == 0 {
cell.currentSplitters.text = "No one is paying for this item yet."
} else {
var splitterList = "Split this item with: "
let itemSplitters = item.billSplitters?.allObjects as! [BillSplitter]
for i in 0...Int((numberOfSplitters)!-1) {
splitterList += "\(itemSplitters[i].name!), "
}
cell.currentSplitters.text = splitterList
}
cell.name.text = item.name
cell.price.text = "£\(item.price!)"
return cell
}
The idea is to select items from the item tableview to assign to a billsplitter, which works fine then redirects to the billsplitter table view, when deleting a billsplitter from this table view it works fine and is no longer listed, but when going back to the items table view it still lists the billSplitters when iterating over the itemSplitters for loop and i dont understand why? Do i have to find each item associated with the bill splitter and delete them from there? I though thats what cascade would do?
So I'm trying to write the delete edit behavior for the rows in a tableview. However, when I hit the delete key after selecting a row, the row is not deleted from the tableView. When I try to do it a second time, I get an error saying an unexpected nil value was found.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete { // Handle the Delete action
// Obtain the name of the genre of movie to be deleted
let genre: String = genres[indexPath.section]
// Obtain the list of movies in the genre as AnyObject
let movies: AnyObject? = applicationDelegate.dict_Genres_dict2[genre]
let movArray: [String] = movies?.allKeys as! [String] //The nil value is unwrapped on this line
// Typecast the AnyObject to Swift array of String objects
var moviesOfGenre: Array<String> = movArray
// Delete the identified movie at row
moviesOfGenre.removeAtIndex(indexPath.row)
if moviesOfGenre.count == 0 {
// If no movie remains in the array after deletion, then we need to also delete the genre
applicationDelegate.dict_Genres_dict2.removeObjectForKey(genre)
// Since the dictionary has been changed, obtain the genre names again
genres = applicationDelegate.dict_Genres_dict2.allKeys as! [String]
// Sort the genre names within itself in alphabetical order
genres.sortInPlace { $0 < $1 }
}
else {
// At least one more movie remains in the array; therefore, the genre stays.
// Update the new list of movie for the genre in the NSMutableDictionary
applicationDelegate.dict_Genres_dict2.setValue(moviesOfGenre, forKey: genre)
}
// Reload the rows and sections of the Table View
tableView.reloadData()
}
}
I have marked which line I am receiving the nil value. Any push in the right direction would be most helpful. Thanks!
After removing the array item. You need remove that item from the table too.When you try to delete the array item again, it shows nil, because that item is not available, but the table didn't remove that item from the view.So you need to remove it from table too...
genres.removeAtIndex(indexPath.row) // or section , delete according to your app
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
why do you take section of the item that you want to delete. You didn't delete the genre item from the table. You only deleted the moviesOfGenre. You need to delete the table view item also( genre ). Remove the genre also from the table.
Also you can use swipe to delete function :-
func tableView(tableView: UITableView!, canEditRowAtIndexPath indexPath: NSIndexPath!) -> Bool {
return true
}
func tableView(tableView: UITableView!, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath!) {
if (editingStyle == .Delete) {
// handle delete (by removing the data from your array and updating the tableview)
self.tableView.beginUpdates()
self.genres.removeObjectAtIndex(indexPath.row) // also remove an array object if exists.
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Left)
self.tableView.endUpdates()
}
i'm trying to delete a cell from UITableView in swift, i follow this tutorial: http://www.ioscreator.com/tutorials/delete-rows-table-view-ios8-swift
the problem is my UITableView has many section, so i can't delete the cell the way like the tutorial.
any one know how to delete cell form table with multiple section?
thanks.
You cannot delete multiple cells at once with the method described in the tutorial. That will only work for single cell. If you select multiple cells and use button, for example, to trigger delete action, your code could look something like this:
if let indexPaths = tableView.indexPathsForSelectedRows() as? [NSIndexPath] {
for indexPath in indexPaths {
// one by one remove items from your datasource
}
tableView.deleteRowsAtIndexPaths(indexPaths, withRowAnimation: .Automatic)
}
Instead of using numbers[row] in the example you can use numbers[section][row]. So the code will look like:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numbers[section].count
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == UITableViewCellEditingStyle.Delete {
numbers[indexPath.section].removeAtIndex(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
}
}
Neither of answers worked for me. Swift Array indexes are updated upon removal hence for-in loop for indexes from .indexPathsForSelectedRows() provided unexpected results i.e. wrong data/tables removed and eventually crash with index outside of array bounds error. Found good (but really outdated) Objective-C iOS Developer Library example. But it utilised NSMutableArray removeObjectsAtIndexes method, not present with Swift Array. Anyway a good deal of useful tricks in there so worth take a look.
The method which work for me is part from that example but instead of removeObjectsAtIndexes do-while is used to remove rows one by one until all selected rows are removed. The method below called by UIAlertAction similar to Apple example.
func deleteSelectedRows() {
// Unwrap indexPaths to check if rows are selected
if let _ = tableView.indexPathsForSelectedRows() {
// Do while all selected rows are deleted
do {
if let indexPath = tableView.indexPathForSelectedRow(){
//remove from table view data source and table view
self.dataSource.removeAtIndex(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
} while tableView.indexPathsForSelectedRows() != nil
}else{
// Delete everything, delete the objects from data model.
self.dataSource.removeAll(keepCapacity: false)
// Tell the tableView that we deleted the objects.
// Because we are deleting all the rows, just reload the current table section
self.tableView.reloadSections(NSIndexSet(index: 0), withRowAnimation: .Automatic)
}
// Exit editing mode after the deletion.
self.tableView.setEditing(false, animated: true)
}
Edit: While do-while did it trick for small example I've been working with (jus starting with swift) It's not efficient. Either extending Array or make data source Equatable and use find() or .filter is preferable.But I'm sure there should be a simpler way. The one I'm using now is described on link below:
http://www.rockhoppertech.com/blog/swift-remove-array-item/
func == (lhs: myDataSource, rhs: myDataSource) -> Bool {
if lhs.data == rhs.data &&
lhs.otherData == rhs.otherData {
return true
}
return false
}
struct myDataSource: Equatable {
let data: String
let otherData: String
}
And then:
if let selectedRows = tableView.indexPathsForSelectedRows(){
var objectsToDelete = [myDataSource]()
for selectedRow in selectedRows {
objectsToDelete.append(myDataSource[selectedRow.row])
}
for object in objectsToDelete {
if let index = find(myDataSource, object){
myDataSource.removeAtIndex(index)
}
}
}
tableView.deleteRowsAtIndexPaths([selectedRows], withRowAnimation: .Automatic)
try to this. this works fine.
But don't forget to this before.
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool
{
return true
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath)
{
if editingStyle == .Delete
{
arrayOfnames.removeAtIndex(indexPath.row)
self.tableViewww.reloadData()
}
}