Deleting the wrong row in a tableView - ios

I'm a beginner in Swift and tried to build an app where an array saves the items in a tableView by Core Data. That works. But what won't work is to delete the right row by swiping.
It is deleted the right row first. But when I go back to the app, it is the row above the initial selected row deleted/not shown anymore.
Anybody there who could give an advice?
Here is the code:
import UIKit
import CoreData
var shoppingList: [NSManagedObject] = [ ]
class ShoppingList_1: UIViewController, UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return shoppingList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = shoppingList[indexPath.row]
let cell = Cell.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = item.value(forKeyPath: "itemName") as? String
cell.detailTextLabel?.text = "\(indexPath.row)"
return cell
}
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let itemTmp = shoppingList[sourceIndexPath.row]
shoppingList.remove(at: sourceIndexPath.row)
shoppingList.insert(itemTmp, at: destinationIndexPath.row)
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete
{
shoppingList.remove(at: indexPath.row)
Cell.deleteRows(at: [indexPath], with: UITableViewRowAnimation.automatic)
//Cell.reloadData()
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {return}
let managedContext = appDelegate.persistentContainer.viewContext
managedContext.delete(shoppingList[indexPath.row])
do {
try managedContext.save()
} catch let err as NSError {
print("12345", err)
}
}
}
#IBOutlet weak var AddButton: UIButton!
#IBOutlet weak var AddItem: UITextField!
#IBOutlet weak var Cell: UITableView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {return}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Item")
do {
shoppingList = try managedContext.fetch(fetchRequest)
} catch let err as NSError {
print("Failed to fetch items", err)
}
}
}

Your issue is you are passing the wrong object to managedContext.delete because you access the element by index after you remove the item from the array. In fact, if you tried to delete the last row your app would crash.
You should also only update your local data model and the table if you successfully remove the value from Core Data.
You should update your commit editingStyle method as follows:
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete
{
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {return}
let managedContext = appDelegate.persistentContainer.viewContext
managedContext.delete(shoppingList[indexPath.row])
do {
try managedContext.save()
shoppingList.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: UITableViewRowAnimation.automatic)
} catch let err as NSError {
print("12345", err)
}
}
}

This happens because you first delete the element from the array, then remove the cell and then finally save the array. I would suggest firstly deleting the element, then saving the array and finally removing the cell from the tableView. In order to ensure that your application always follows this order, create a delete() function with a closure. All the database removal should be done in the actual function, and the tableViewCell must be removed in the closure, so that you are sure that it gets removed after everything else is correctly done.
This is the function:
func deleteRows(closure: () -> ()) {
shoppingList.remove(at: indexPath.row)
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {return}
let managedContext = appDelegate.persistentContainer.viewContext
managedContext.delete(shoppingList[indexPath.row])
do {
try managedContext.save()
} catch let err as NSError {
print("12345", err)
return
}
closure()
}
This is you calling it:
deleteRows {
tableView.deleteRows(at: [indexPath], with: UITableViewRowAnimation.automatic)
}

Related

Swipe to delete function insert including Core Data

I am trying to do a Simple Name List app. I have watched this video and copied everything ( https://www.youtube.com/watch?v=tP4OGvIRUC4 )
I now want to add a Swipe to delete function. It works the way I want it to work but when I close and reopen the app it will be like before.
I tried different things but it did not work.
Anybody got any ideas?
Greets from Switzerland
Here is my ViewController:
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var people = [Person]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()
do {
let people = try PersistenceServce.context.fetch(fetchRequest)
self.people = people
self.tableView.reloadData()
}catch{}
}
#IBAction func onPlusTapped() {
let alert = UIAlertController(title: "Add name", message: nil, preferredStyle: .alert)
alert.addTextField { (textField) in
textField.placeholder = "Name"
}
let action = UIAlertAction(title: "Add", style: .default) { (_) in
let name = alert.textFields!.first!.text!
let person = Person(context: PersistenceServce.context)
person.name = name
PersistenceServce.saveContext()
self.people.append(person)
self.tableView.reloadData()
}
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
}
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return people.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
cell.textLabel?.text = people[indexPath.row].name
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
guard editingStyle == UITableViewCell.EditingStyle.delete else { return }
people.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
self.tableView.reloadData()
}
}
You are just removing the item from your local array, you need to persist the change after removing it.
when you reload apps your table take again data from fetch, where your deleted data stay. if you like delete data in fetch look at this topic
Core Data Delete Object
This is how i have done it previously.
override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
// deleteAction, Call the deleteobject function, and then reload the data
let deleteAction = UITableViewRowAction(style: .default, title: DELETE_TITLE) { (rowAction, indexPath) in
_ = deleteObject(name: self.dataSource[indexPath.row].name)
self.tableview.reload()
}
return [deleteAction]
}
func deleteObject(name: String) -> Bool {
let context = getContext()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: ENTITY_NAME)
fetchRequest.predicate = NSPredicate(format: formatStringForPredicate(oldListName: name))
let objects = try! context.fetch(fetchRequest)
for obj in objects {
context.delete(obj as! NSManagedObject)
}
do {
try context.save()
return true
} catch {
return false
}
}
Note you may need to modify deleteObject function.
First of all never call reloadData() right after insertRows(at or deleteRows(at because the insert/delete methods do update the UI.
To make the deletion persistent you have to delete the item in the context and save the context.
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
guard editingStyle == .delete else { return }
let personToDelete = people.remove(at: indexPath.row)
PersistenceServce.context.delete(personToDelete)
tableView.deleteRows(at: [indexPath], with: .automatic)
PersistenceServce.saveContext()
}
In order to do anything with core data, it needs to load all necessary objects to memory, which is going to be accessed by your context, once you load the item you want to delete to the context all you need to do is simply
context.delete(item)
tableViewArray.remove(at: itemIndex in array)
Then call the context.save() to save the changes you made to the persistence store

How to delete from tableview as well as sqlite3 using Swift

I'm new to swift and sqlite3 and I need help on how to delete from tableview and sql db.
I tried to use reloadData() but it doesn't work. I tried to delete using tableView.deleteRows(at: [indexPath], with: .fade) but Im getting an error as I have a sql delete statement running before that. With this code provided below, Im successfully able to remove the item from the database, but it doesn't refresh the tableview. The way I got around to fixing it temporarily is perform a segue to previous screen upon successful removal of an item and when returned to the tableviewcontroller it would be removed.
import UIKit
class TableViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {
let mainDelegate = UIApplication.shared.delegate as! AppDelegate
#IBOutlet var tableView: UITableView!
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let tableCell = tableView.dequeueReusableCell(withIdentifier: "cell") as? SiteCell ?? SiteCell(style: .default, reuseIdentifier: "cell")
let rowNum = indexPath.row
tableCell.primaryLabel.text = mainDelegate.people[rowNum].name
tableCell.secondaryLabel.text = mainDelegate.people[rowNum].email
tableCell.myImageView.image = UIImage(named: mainDelegate.people[rowNum].avatar!)
tableCell.accessoryType = .disclosureIndicator
return tableCell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return mainDelegate.people.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 70
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let rowNum = indexPath.row
let details : String! = "Address: \(mainDelegate.people[rowNum].address!) \nPhone Num: \(mainDelegate.people[rowNum].phonenum!) \nEmail: \(mainDelegate.people[rowNum].email!) \nAge: \(mainDelegate.people[rowNum].age!) \nGender: \(mainDelegate.people[rowNum].gender!) \nDate of birth: \(mainDelegate.people[rowNum].dob!)"
let alertController = UIAlertController(title: mainDelegate.people[rowNum].name, message: details, preferredStyle: .alert
)
let cancelAction = UIAlertAction(title: "ok", style: .cancel, handler: nil)
print("TESTING ROW: \(mainDelegate.people[rowNum].id!)")
alertController.addAction(cancelAction)
present(alertController, animated: true)
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
var rowNum: Int = indexPath.row
if editingStyle == .delete {
print("Testing delete \(mainDelegate.people[rowNum].id!)")
print("\(indexPath.row)")
mainDelegate.removeFromDatabase(id: mainDelegate.people[rowNum].id!)
print("\(indexPath)")
// tableView.deleteRows(at: [indexPath], with: .fade)
DispatchQueue.main.async{
self.tableView.reloadData()
}
// self.performSegue(withIdentifier: "DataToInfo", sender: self)
// let mainDelegate = UIApplication.shared.delegate as! AppDelegate
// mainDelegate.removeFromDatabase(person: mainDelegate.people[indexPath.row])
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
mainDelegate.readDataFromDatabase()
}
removeFromDatabase method
func removeFromDatabase(id : Int){
var db : OpaquePointer? = nil
if sqlite3_open(self.databasePath, &db) == SQLITE_OK{
print("Successfully opened connection to database at \(self.databasePath)")
var deleteStatement : OpaquePointer? = nil
let deleteStatementString : String = "delete from entries where id=\(id)"
if sqlite3_prepare_v2(db, deleteStatementString, -1, &deleteStatement, nil) == SQLITE_OK{
if sqlite3_step(deleteStatement) == SQLITE_DONE{
print("Deleted")
}
else{
print("Failed")
}
}else{
print("Couldn't prepare")
}
sqlite3_finalize(deleteStatement)
sqlite3_close(db)
}
}
Im trying to delete it from tableview as well as database. At one point I was trying to
mainDelegate.people.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
then running the removeFromDatabase, but it was giving me an error.
You should update your datasource. Try to refactor your commitEditing like this:
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
var rowNum: Int = indexPath.row
if editingStyle == .delete {
print("Testing delete \(mainDelegate.people[rowNum].id!)")
print("\(indexPath.row)")
mainDelegate.removeFromDatabase(id: mainDelegate.people[rowNum].id!)
print("\(indexPath)")
mainDelegate.readDataFromDatabase()
tableView.deleteRows(at: [indexPath], with: .fade)
}
}

Can't retrieve data from TableViewCell because didSelectRowAt isn't being called

I have a custom UITableView that contains data in each cell that I want to retrieve and save it using UserDefaults.
I would like for didSelectRowAt to be called when the user taps on a cell so that I can retrieve the data within that particular cell.
The problem is that didSelectRowAt is not being called and I have tried the following methods:
Ensuring there are no gesture recognizers 'eating' the tap on the cell (I never added a gesture recognizer).
Setting the 'Selection' portion of Identity Inspector to 'None' and 'Single Selection'.
Here is a screenshot of how the ViewController with the TableView is set up:
Here is my code:
class blueSide: UIViewController, UITableViewDelegate, UITableViewDataSource {
var items : [SosItem] = []
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
ref.observe(.value, with: {
snapshot in
var newItems : [SosItem] = []
for child in snapshot.children {
if let snapshot = child as? DataSnapshot,
let sosItem = SosItem(snapshot: snapshot) {
newItems.append(sosItem)
}
}
self.items = newItems
print(self.items)
self.tableView.reloadData()
})
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let removedItem = items.remove(at: indexPath.row)
let itemsRef = ref.child(removedItem.key.lowercased())
itemsRef.removeValue()
tableView.reloadData()
}
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 150
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let sosItem = items[indexPath.row]
print(sosItem)
UserDefaults.standard.set(sosItem.clothingDescription, forKey: "clothingDescription")
UserDefaults.standard.set(sosItem.placeName, forKey: "placeName")
UserDefaults.standard.set(sosItem.longitude, forKey: "longitude")
print("Longitude saved!")
UserDefaults.standard.set(sosItem.latitude, forKey: "latitude")
print("Latitude saved!")
print(UserDefaults.standard.value(forKey: "latitude"))
// tableView.deleteRows(at: [indexPath], with: .fade)
// tableView.reloadData()
self.performSegue(withIdentifier: "uberSegue", sender: self)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath) as! CustomTableViewCell
//get cell data from Firebase
let sosItem = items[indexPath.row]
cell.descriptionLabel.text = sosItem.clothingDescription
cell.latitudeLabel.text = String(sosItem.latitude)
cell.longitudeLabel.text = String(sosItem.longitude)
cell.locationNameLabel.text = sosItem.placeName
cell.destinationLabel.text = sosItem.dropoffLocation
return cell
}
The didSelectedRowAt method isn't called when the tableView is in editing mode, isEditing property is set to true, or you invokes canEditRowAt
Try to selecting a row when the editing mode ends, as test!

How to delete objects from Realm using UITableView - Swift

What is the most common way (code structure) to delete objects from Realm through a UITableView?
The following code works fine to show data from Realm in a UITableView but not if I need to delete a row and update Realm since Results does not have a remove method.
Do I need to put my objects into a List and do the deleting through it? If this is the most common method I'm not so sure how to keep the 'List' and the Results from Realm in constant sync.
Model class
import RealmSwift
class Item:Object {
dynamic var productName = ""
}
Main ViewController
let realm = try! Realm()
var items : Results<Item>?
var item:Item?
override func viewDidLoad() {
super.viewDidLoad()
self.items = realm.objects(Item.self)
}
func addNewItem(){
item = Item(value: ["productName": productNameField.text!])
// Save to Realm
try! realm.write {
realm.add(item!)
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reusableCell", for: indexPath)
let data = self.items![indexPath.row]
cell.textLabel?.text = data.productName
return cell
}
Delete Rows
Standard method to delete rows from UITableView which of course does NOT work in this case since I'm using the default Results container from Realm.
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCellEditingStyle.delete{
items!.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: UITableViewRowAnimation.automatic)
}
}
Again, what is the most common way to delete objects from Realm through a UITableView?
Thanks
Some unwrapping and catching logic left out for brevity
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCellEditingStyle.delete{
if let item = items?[indexPath.row] {
try! realm.write {
realm.delete(item)
}
tableView.deleteRows(at: [indexPath], with: UITableViewRowAnimation.automatic)
}
}
}
You could also implement this block:
if let item = items?[indexPath.row] {
do {
try realm.write {
realm.delete(item)
}
} catch {
print("Error deleting item, \(error)")
}
tableView.reloadData()
}

delete core data from a row when swipe to delete is activated

Im working on a project in swift 3.0, and i'm populating data on a table view, which I save on a core data entity from another view controller (from two text fields). I wants to delete data when swipe to delete is activated both from my array and core data. my code on UITableView class as bellow.
import Foundation
import UIKit
import CoreData
class MyExpencesViewController: UIViewController,UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var expensesTableView: UITableView!
var myExpensesArray = [String] ()
var myAmountArray = [String] ()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadData()
self.expensesTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (myExpensesArray.count)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: MyExpensesTableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell") as! MyExpensesTableViewCell
cell.myExpenseName.text = myExpensesArray [indexPath.row]
cell.myExpenseAmount.text = myAmountArray [indexPath.row]
return cell
}
func loadData (){
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let request = NSFetchRequest <NSFetchRequestResult> (entityName: "UserExpenses")
request.returnsObjectsAsFaults = false
do {
let results = try context.fetch(request)
// check data existance
if results.count>0 {
print(results.count)
for resultGot in results as! [NSManagedObject]{
if let expName = resultGot.value(forKey:"expenseName") as? String{
myExpensesArray += [expName]
DispatchQueue.main.async {
[unowned self] in self.expensesTableView.reloadData()
self.expensesTableView.reloadData()
}
print("myExp array is : \(myExpensesArray)")
}
if let amountVal = resultGot.value(forKey:"amount") as? String{
myAmountArray += [amountVal]
DispatchQueue.main.async {
[unowned self] in self.expensesTableView.reloadData()
self.expensesTableView.reloadData()
}
print("myAmount array is : \(myAmountArray)")
}
}
}
}catch{
print("No Data to load in the array")
}
}
}
You have to add 2 more methods for delete row on swipe
//For swipe access allow
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
//For remove row from tableview & object from array.
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == UITableViewCellEditingStyle.delete) {
// delete data and row
<YourArray>.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
let me know if you need more detail.

Resources