Can't delete cells from UITableView, just the data from said cells - swift

I am making an app that stores events and their data, and it was working flawlessly until I tried to add Core Data, since then, I've been having issues with it. One is the fact that using the delete func doesn't really delete the tablecell, just the data inside. If I try to delete the blank cell, it does nothing. Below is the code for the delete function I am using.
Also, my app for some reason starts with one of these blank cell, which can't be deleted.
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
PersistenceService.context.delete(events[indexPath.row])
do {
try PersistenceService.context.save()
eventLog.reloadData()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
}

You have to remove the item from the context and from the data source array and it's highly recommended to use deleteRows rather than reloading the entire table view to take advantage of the animation
PersistenceService.context.delete(events[indexPath.row])
events.remove(at: indexPath.row)
eventLog.deleteRows(at: [indexPath], with: .fade)
... save context

Related

How to initial load UITableView then observe Firebase node for changes to refresh tableview in Swift?

I am loading a UITableView with User data being fetched from Firebase initially using observeSingleEvent which I understand does not attach an observer after the initial read.
I also have an icon in each cell that updates when the user is online/offline. For this, I am attaching the observer to observe any changes to the specific User child node in my willDisplay cell delegate method and removing the observer in the didEndDisplaying method.
This is my first time doing this and is working correctly, but before I proceed I want to know if this is the industry standard way of initially loading data then observing changes so that the view can be updated accordingly for each cell.
cellForRowAt method which dequeues and configures a cell:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "ConversationsListCell", for: indexPath) as? ConversationsListCell else { return UITableViewCell() }
let message = messages[indexPath.row]
DataService.run.getUserInfo(forUserId: message.chatPartnerId()!) { (user) in
cell.configureCell(message: message, user: user)
}
return cell
}//end func
willDisplay method attaches an observer at the specific child node and triggers a tableView.reload() if there are any changes to the node:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let message = messages[indexPath.row]
DataService.run.observeUserNodeChages(forId: message.chatPartnerId()!) { (user) in
self.tableView.reloadData()
}
}//end func
didEndDisplaying method removes the observer attached by the willDisplay method:
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row < messages.count {
let message = messages[indexPath.row]
DataService.run.removeObserveUserNodeChages(forId: message.chatPartnerId()!)
}
}//end func
observeUserNodeChages which listens for any changes at the specific uid child node
func observeUserNodeChages (forId: String, handler: #escaping (Bool) -> ()){
print("inside observeUserNodeChages forId:\(forId)")
conversationListHandle = REF_USERS.child(forId).observe(.childChanged, with: { (snapshot) in
handler(true)
}, withCancel: nil)
}//end func
user node Firebase structure
I want to know if this is the industry standard way
That is not the industry standard way and should be avoided as it will cause flicker and slow response from your UI. However, when it comes to coding, there isn't typically a 'standard' as the code you write to solve a problem will vary depending on the use case and needed result.
Your tableView dataSource should be initially populated from Firebase and then updated when there is a change; that will be initiated by a Firebase event. Once the dataSource has been updated, then refresh the tableView.
That will keep the app responsive and your users happy.
The other thing is it seems you're attaching multiple observers within the same (users) node. That may not be necessary; attach your observers to the /users node and you will be then notified of added, changed or removed events and whichever user was affected will be passed to the firebase closure as a snapshot.

How to delete specific coreData object from TableView

So I'm trying to delete data from a tableView, which It will delete the cell at the row, but it won't delete the information from coreData, causing it to load again when I call a .reloadData(). I'm really new to coredata and I don't know how to select a specific Recipe item that I make.
Here's where I handle the delete:
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == UITableViewCellEditingStyle.delete) {
// handle delete (by removing the data from your array and updating the tableview)
recipes.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: UITableViewRowAnimation.automatic)
}
}
Here's how I am creating the items in coreData, if this helps. Also, I have a Git repository here if anyone's willing to look that deep
#IBAction func createNewRecipeButton(_ sender: Any) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let newRecipe = Recipe(context: context)
newRecipe.title = recipeTitleTextBox.text
newRecipe.instructions = recipeInstructionsTextBox.text
newRecipe.time = recipeTimeTextBox.text
(UIApplication.shared.delegate as! AppDelegate).saveContext()
navigationController!.popToRootViewController(animated: true)
}
Your current removal method merely deletes the recipe from the storage array. You need to tell the context to get rid of it as well…
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == UITableViewCellEditingStyle.delete) {
let recipe = recipes.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: UITableViewRowAnimation.automatic)
guard let moc = recipe.managedObjectContext else { return }
moc.delete(recipe)
moc.processPendingChanges()
}
}
You might also want to look into using a NSFetchedResultsController. There are several tutorials around.

Variable crashes code

Sorry for the bad title, and I feel really dumb for asking this question.
I'm deleting a cell from my table and the first block of code runs just perfect, but when shortening the line by creating a variable the code crashes. Why?
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
emojisByCategories[indexPath.section].remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
This small change will cause a "libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)" error
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
var emojis = emojisByCategories[indexPath.section]
emojis.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
Build on Xcode Version 9.0 (9A235)
You have an array of arrays. Array in Swift is a struct which is a value type. When you assign an array to another variable, you are actually creating a copy of the array.
When you do:
var emojis = emojisByCategories[indexPath.section]
emojis.remove(at: indexPath.row)
you are modifying the copy in emojis. Nothing in emojisByCategories is actually being changed as a result of this code.
So now you tell the table view that a row has been deleted but your data source hasn't actually changed at all so you get the crash telling you about an invalid number of rows in a section.
The line:
emojisByCategories[indexPath.section].remove(at: indexPath.row)
doesn't have the same problem because you are not making a copy of any array and the values in emojisByCategories are being updated as expected.
You can make your second set of code work by adding a third line:
var emojis = emojisByCategories[indexPath.section]
emojis.remove(at: indexPath.row)
emojisByCategories[indexPath.section] = emojis
That 3rd line updates emojisByCategories with the updated emojis array so now your code won't crash.

Swift Saving Deleted Rows To Core Data

Bit of a beginner here so probably shouldn't be trying core data stuff but anyway, I would like to be able to delete a row by swiping. I have done this but it doesn't save the deleted cells and they come back again. I am using a xcdatamodeld file. If anyone can tell me how to save the deleted files to core data that would be great!
Here is my saving data code:
inputAlert.addAction(UIAlertAction(title: "Save", style: .default, handler: { (action:UIAlertAction) in
let taskTextField = inputAlert.textFields?.first
let descTextField = inputAlert.textFields?.last
if taskTextField?.text != "" && descTextField?.text != "" {
taskItem.task = taskTextField?.text
taskItem.desc = descTextField?.text
do {
try self.managedObjectContext.save()
self.loadData()
}catch {
print("Could not save data \(error.localizedDescription)")
}
}
Here is the code I have so far for the deleting:
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCellEditingStyle.delete {
tasks.remove(at: indexPath.row)
tableView.reloadData()
}
}
Don't let the fact that you are a beginner keep you from using this powerful persistent store friend. CoreData is a big topic, books have been written on it, but it is something that works very well once you understand the core concepts of programming with it. It looks like you want to remove data that is populated in a UITableView and then save what you deleted into CoreData. Let's break down the steps and give you some examples to work with in your own project.
1) Remove data from your UITableView's datasource
2) Save NSManagedObject to CoreData
3) Delete row from UITableView
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCellEditingStyle.delete {
// 1)
let task = tasks.remove(at: indexPath.row)
// 2)
saveToCoreData(task: task)
// 3)
tableView.beginUpdates()
tableView.deleteRows(at: [indexPath], with: .fade)
tableView.endUpdates()
}
}
// Assuming your task is of type "`Task`". You should put whatever data type your task object actually is.
func saveToCoreData(task: Task) {
// Insert Into CoreData (very important)
let managedObject = NSEntityDescription.insertNewObject(forEntityName: "RemovedTask", into: self.context)
// assign values
managedObject.value = task.value
// Save CoreData Context (also, very important)
do {
self.context.save()
}
catch {
print("Could not save CoreData!")
}
}

Implement a tablecell fade delete

I'm following this answer to get that nice faded deleted cell. Currently when I swipe to delete, it's sharp, sudden and ugly. Other apps has that nice fade when cell is deleted.
In my app, the cell is not really deleted, the data is removed:
Swift 3, func editingStyle:
let tableSection = sections[sortedSections[indexPath.section]]
let tableItem = tableSection![indexPath.row]
// Removes item from array:
self.incomesDictionary.removeValue(forKey: tableItem.incomeId)
// Reloading the table data:
self.tableView.reloadData()
When I add self.tableView.deleteRows(at: [indexPath], with: .fade) above the ...reloadData() I get an error:
terminating with uncaught exception of type NSException
If I update the function with tableSection?.remove(at: indexPath.row), it still crashes. Any tips to get that smooth effect?
Edit:
I have a programmatically grouped cell and the error I got, after adding the .fade is:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to delete row 0 from section 0, but there are only 0 sections before the update'
Swift 5.0
https://www.hackingwithswift.com/example-code/uikit/how-to-swipe-to-delete-uitableviewcells
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
self.myArray.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
Try this:
self.tableView.beginUpdates()
self.tableView.deleteRows(at: [indexPath], with: .fade)
self.tableView.endUpdates()
This will prepare the tableView for your animation