How to delete objects from Realm using UITableView - Swift - ios

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

Related

How to add swipe to remove table cells from UITableViewController in Swift?

There aren't very many tutorials for this for Swift 5.1 and I am pretty novice. I am wondering how to make the tableView delete override func work for my code. It gives me an error on objects because they are an unresolved identifier. Also what would go in the insert?
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
let mainImageView = cell?.viewWithTag(2) as! UIImageView
mainImageView.image = posts[indexPath.row].mainImage
let mainLabel = cell?.viewWithTag(1) as! UILabel
mainLabel.text = posts[indexPath.row].name
return cell!
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
objects.remove(at: indexPath.row) // Here I get an error because objects is an unresolved identifier.
tableView.deleteRows(at: [indexPath], with: .fade)
} 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. I am also confused as to what goes here.
}
}
All of the information you will need can be found here.
This method allows the delegate to customize the editing style of the cell located atindexPath. If the delegate does not implement this method and the UITableViewCell object is editable (that is, it has its isEditing property set to true), the cell has the UITableViewCell.EditingStyle.delete style set for it.
This is what you are missing:
https://developer.apple.com/documentation/uikit/uitableviewdelegate/1614869-tableview
override func tableView(_ tableView: UITableView,
editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .delete
}
Here you are using an object to remove data for the index path which is wrong you are using "posts" named array to show values on the table view. So, this "posts" array contains objects which are visible on the table view.
So, here when you want to remove object then this means you have to remove objects from the "posts" array. Below the attached function with some changes, you can check this it will work for you.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
posts.remove(at: indexPath.row) // Here I get an error because objects is an unresolved identifier.
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
posts.append("Here insert what you want") //inside append you can append/insert values to the array
tableView.reloadData()
}
}

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

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!

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

Why when I delete a row in a tableview it deletes multiple rows?

I'm making a notes app and added the swipe to delete a row method. The problem I'm having is when there is multiple notes saved in the table view and I go to swipe one row to delete it deletes all the notes. Also when I quit the app and go back the notes are back in the table view. Heres the code I have:
class MasterViewController: UITableViewController {
var notesItems: NSMutableArray = NSMutableArray()
override func viewDidAppear(animated: Bool) {
let userDefaults:NSUserDefaults = NSUserDefaults.standardUserDefaults()
let itemListFromUserDefaults:NSMutableArray? = userDefaults.objectForKey("itemList") as? NSMutableArray
if ((itemListFromUserDefaults) != nil) {
notesItems = itemListFromUserDefaults!
}
self.tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.setNavigationBarHidden(false, animated: true)
self.navigationController?.toolbarHidden = false
self.tableView.dataSource = self
UINavigationBar.appearance().barTintColor = UIColor.orangeColor()
UIToolbar.appearance().barTintColor = UIColor.orangeColor()
}
override func numberOfSectionsInTableView(tableView: UITableView?) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return notesItems.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
let notesItem:NSDictionary = notesItems.objectAtIndex(indexPath.row) as! NSDictionary
cell.textLabel?.text = notesItem.objectForKey("text") as? String
return cell
}
// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == UITableViewCellEditingStyle.Delete {
self.tableView.reloadData()
self.tableView.beginUpdates()
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
// Delete the row from the data source
}
}
}
Your code in commitEditingStyle is all wrong.
Don't reload the table view.
You must update the database before calling deleteRowsAtIndexPaths.
You don't need to call beginUpdates/endUpdated to make one call to deleteRowsAtIndexPaths.
You want:
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == UITableViewCellEditingStyle.Delete {
// remove an object from notesItem for this index path
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
}

Resources