Swipe to delete function insert including Core Data - ios

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

Related

Table Cell Not Swiping

I'm developing a todo list app that links up with a Realm database, however when I'm trying to use the 'editingStyle' method which allows users to swipe on the cell to delete the data from the UI & the Realm database the cell doesn't swipe, the app has 2 screens, this method works fine on one the first one but it does not work on the other screen, the cell works fine it just won't swipe.
My code:
import UIKit
import RealmSwift
class CategoryViewController: UITableViewController {
var categories: Results<Category>?
let realm = try! Realm()
override func viewDidLoad() {
super.viewDidLoad()
loadCategories()
tableView.rowHeight = 60.0
}
//MARK: - Creating the table view cell
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return categories?.count ?? 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CategoryCell", for: indexPath)
cell.textLabel?.text = categories?[indexPath.row].name ?? "No Categories Added Yet"
return cell
}
//MARK: - This will remove a category from the UI & the Realm database, this is a built in swift method
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
if let deleteAction = categories?[indexPath.row] {
do {
try realm.write({
realm.delete(deleteAction)
})
} catch {
print("Error deleting the cell \(error)")
}
}
}
tableView.deleteRows(at: [indexPath], with: .fade)
}
//MARK: - TableView Delegate Methods
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "goToItems", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! TodoListViewController
if let indexPath = tableView.indexPathForSelectedRow {
destinationVC.selectedCategory = categories?[indexPath.row]
}
}
//MARK: - Add New Categories
#IBAction func addButtonPressed(_ sender: UIBarButtonItem) {
var textField = UITextField()
let alert = UIAlertController(title: "Add New Category", message: "", preferredStyle: .alert)
let action = UIAlertAction(title: "Add", style: .default) { (action) in
// What happens when user clicks add button
let newCategory = Category()
newCategory.name = textField.text!
self.saveCategories(category: newCategory)
}
alert.addAction(action)
alert.addTextField { (field) in
textField = field
textField.placeholder = "Add a new category"
}
present(alert, animated: true, completion: nil)
}
//MARK: - Data Manipulation Methods
func saveCategories(category: Category) {
do {
try realm.write({
realm.add(category)
})
} catch {
print("Error saving category \(error)")
}
tableView.reloadData()
}
func loadCategories() {
categories = realm.objects(Category.self)
tableView.reloadData()
}
}
Add this:
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
Your code seems correct. I think there's something wrong with realm.delete(_:) method which possibly throws an error which executes the catch block instead of deleting the row. Try to see if there is something wrong within the try block with a few print statements. And if all fails, try making the following changes
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
if let deleteAction = categories?[indexPath.row] {
do {
try realm.write({
realm.delete(deleteAction)
})
// New snippet
tableView.deleteRows(at: indexPath, with: .fade)
self.tableView.reloadData()
} catch {
print("Error deleting the cell \(error)")
}
}
}
}

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)
}
}

Deleting the wrong row in a tableView

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)
}

Using UserDefaults on an UITableView

I'm making an app which uses the Blogger API. In the first tab, I can search for posts and display their contents. Also, I can add posts to the "Favorites" section in the second tab. All is working, until I close the app. After re-launching, the Favorites section is gone. I tried to implement UserDefaults so that the Favorites section does not become empty after killing the app, but it does not work.
This is the code for the button which adds the post to Favorites:
vc.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addTapped))
func addTapped() {
offlineTitles.append(cellText)
titlesArray.append(cellText)
subtitlesArray.append(cellSubtitle)
let defaults = UserDefaults.standard
defaults.set(titlesArray, forKey: "title")
defaults.set(subtitlesArray, forKey: "subtitle")
NotificationCenter.default.post(name: .reload, object: nil)
let ac = UIAlertController(title: "Added!", message: "Post added to favorites", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "Great!", style: .default))
present(ac, animated: true)
}
and this for the FavoritesViewController.swift :
import UIKit
var offlineTitles = [String]()
var titlesArray = [String]()
var subtitlesArray = [String]()
extension Notification.Name {
static let reload = Notification.Name("reload")
}
class OfflineViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(reloadTableData(_:)), name: .reload, object: nil)
self.tableView.allowsMultipleSelectionDuringEditing = false
}
func reloadTableData(_ notification: Notification) {
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return titlesArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "OfflineCell", for: indexPath)
let defaults = UserDefaults.standard
let userDefaultsTitleArray = defaults.array(forKey: "title") as? [String] ?? [String]()
let userDefaultsSubtitleArray = defaults.array(forKey: "subtitle") as? [String] ?? [String]()
let title = userDefaultsTitleArray[indexPath.row]
let subtitle = userDefaultsSubtitleArray[indexPath.row]
cell.textLabel?.text = title
cell.detailTextLabel?.text = subtitle
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = FavouritesViewController()
navigationController?.pushViewController(vc, animated: true)
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCellEditingStyle.delete {
offlineTitles.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: UITableViewRowAnimation.automatic)
}
}
}
It appears that you're reading user defaults in cellForRowAt. This is not only inefficient (if you had five favorites, you'd be reading it in five times), but is at the wrong time. For example, what will numberOfRowsInSection return? By the time that's called, you haven't yet read the user defaults into your arrays.
You should read user defaults into your arrays in viewDidLoad (as well as possibly in your reloadTableData, too).

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