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()
}
Related
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.
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?
I have a table view within a VC. It is populated from a core data object using the following code:
// Variables
var allFruits: NSArray
var managedObjectContext: NSManagedObjectContext
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
func loadFruits(){
// Try to load all of the Fruits from the core data.
let fetchRequest = NSFetchRequest()
let entityDescription = NSEntityDescription.entityForName("Fruit", inManagedObjectContext: self.managedObjectContext)
fetchRequest.entity = entityDescription
do{
self.allFruits = try self.managedObjectContext.executeFetchRequest(fetchRequest)
if self.allFruits.count == 0{
print("No saved Fruits")
}
}
catch
{
let fetchError = error as NSError
print(fetchError)
}
}
Then the table view is populated with this specific fruit data. I have got this method for deletion of the Fruits from the table
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if (editingStyle == UITableViewCellEditingStyle.Delete) {
// handle delete (by removing the data from your array and updating the tableview)
managedObjectContext.deleteObject(allFruits[indexPath.row] as! NSManagedObject)
// Attempt to save the object
do{
try appDelegate.managedObjectContext.save()
}
catch let error{
print("Could not save Deletion \(error)")
}
// Remove the deleted item from the table view
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
// Reload the fruits
self.loadFruits()
// Reload the table view
tableView.reloadData()
}
}
This instead just crashes the app every time I try to delete the Fruits.
'NSInternalInconsistencyException', reason: 'Invalid update: invalid
number of rows in section 0. The number of rows contained in an
existing section after the update (5) must be equal to the number of
rows contained in that section before the update (5), 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).'
I am suspecting that there is some issue with the fact that I am using an NSArray as opposed to an NSMutableArray.
How can I fix this issue?
Thanks in advance.
First of all use a Swift array rather than a Foundation array
var allFruits = [NSManagedObject]()
This avoids a lot of type casting.
To keep Core Data and the table view in sync you have to delete the object in Core Data and in the model. The solution to completely reload both the model and the view is very expensive and not needed at all.
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let objectToDelete = allFruits[indexPath.row]
allFruits.removeAtIndex(indexPath.row)
managedObjectContext.deleteObject(objectToDelete)
//Attempt to save the object
do{
try appDelegate.managedObjectContext.save()
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
catch let error{
print("Could not save Deletion \(error)")
}
}
}
deleteRowsAtIndexPaths includes updating the UI so reloading the table view is not needed.
If the model of the table view is NSManagedObject it's recommended to use NSFetchedResultsController
You should update data source before delete. Like this:
//Reload the fruits
self.loadFruits()
//Remove the deleted item from the table view
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
And if you're sure there's no other changes,it's no need to reload data because this costs a lot.
I fixed my code by simply changing this method:
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if (editingStyle == UITableViewCellEditingStyle.Delete) {
// handle delete (by removing the data from your array and updating the tableview)
managedObjectContext.deleteObject(allFruits[indexPath.row] as! NSManagedObject)
//Attempt to save the object
do{
try appDelegate.managedObjectContext.save()
}
catch let error{
print("Could not save Deletion \(error)")
}
//Reload the fruits
self.loadFruits()
//Reload the table view
tableView.reloadData()
}
}
This successfully deletes the object from the core data as well as updating the table view.
Updated for Swift 3 Xcode 8 iOS 10
The following code snippet adds a 'swipe to delete' to your Table Row, and also saves the changes to CoreData.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
if editingStyle == .delete {
context.delete(tasks[indexPath.row] as NSManagedObject)
//objects.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
//Save the object
do{
try (UIApplication.shared.delegate as! AppDelegate).saveContext()
}
catch let error{
print("Cannot Save: Reason: \(error)")
}
//Reload your Table Data
getData()
//Reload the table view
tableView.reloadData()
} 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.
}
}
The premise: I have a UITableViewController that conforms to NSFetchedResultsControllerDelegate. I also have a fetched results controller and managed object context as variables in the controller. My tableView displays a table with one section of core data objects from the fetched results controller.
What I'm trying to implement is swipe to delete. The object selected for deletion is actually deleted, however the wrong indexPath is being animated to delete and I don't know why. I currently have the following methods that I believe are relevant:
// This method is being called in viewDidLoad, adding all of the CoreData objects to an array called fetchedResults.
func performFetch() {
do { try fetchedResultsController?.performFetch()
fetchedResults = fetchedResultsController?.fetchedObjects as! [Date]
} catch let error as NSError {
print(error.localizedDescription)
}
}
// tableViewDataSource methods
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let objectToDelete = fetchedResults[indexPath.row]
fetchedResultsController?.managedObjectContext.deleteObject(objectToDelete)
print("commitEditingStyle-indexPath = \(indexPath)")
do { try managedContext.save()
} catch let error as NSError {
print(error.localizedDescription)
}
}
}
// NSFetchedResultsControllerDelegate methods
func controllerWillChangeContent(controller: NSFetchedResultsController) {
self.tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeObject object: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Delete:
if let indexPath = indexPath {
print("didChangeObject indexPath = \(indexPath)")
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
default:
return
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.tableView.endUpdates()
}
As you can see, I print the indexPath for the tableView:commitEditingStyle method as well as the controller:didChangeObject method. Here are the 2 print statements:
commitEditingStyle-indexPath = {length = 2, path = 0 - 3}
didChangeObject-indexPath = {length = 2, path = 0 - 0}
Why is the didChangeObject method picking up the wrong indexPath? When I swipe to delete the object, the object is deleted at the proper indexPath (in this case 3...) but the table view cell that animates deletion is indexPath 0 (the first cell in my table view). What gives?
Remove all usage of fetchedResults from your code. You are caching the initial set of objects that the FRC knows about, but you aren't tracking additions or removals in that cache. The cache is also a waste of memory because you can always get exactly what you want from the FRC and it also tracks changes.
So, what you're seeing should be what appears to be a random difference and is due to indexing differences between the cache array and the FRC. They should match initially, and if you only ever delete the last item it should be ok, but any other deletion would cause them to fall out of sync.
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()
}
}