How to delete user default data in tableView? - ios

I added a homework page using the user default.
Null value also adding to tableView and delete function not working.
ButtonAction
#IBAction func addHomeWork(_ sender: Any) {
let newHomeWork = HomeWork(addtitle:addHomeworktxt.text!)
HomeWork.saveHomeWork(homeWork: newHomeWork)
}
viewWillAppear
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
if let arrayDetails = HomeWork.getHomeWork() {
homeWorkArray = arrayDetails
}
}
EditingStyle
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCell.EditingStyle.delete {
homeWorkArray.remove(at: indexPath.row)
self.tableView.deleteRows(at: [indexPath], with: UITableView.RowAnimation.automatic)
self.tableView.reloadData()
HomeWork.getHomeWork()
}
}
model class HomeWork
class HomeWork: Codable {
var title:String
init(addtitle:String) {
self.title = addtitle}
public static func getHomeWork() -> [HomeWork]? {
var homeWorkArray:[HomeWork] = []
if let homeWorkData = UserDefaults.standard.data(forKey: "homeworkData"){
homeWorkArray = try! JSONDecoder().decode([HomeWork].self, from: homeWorkData)
}
return homeWorkArray
}
saveHomeWork method in HomeWork model class
public static func saveHomeWork(homeWork:HomeWork){
var retrivedHomeWorkArray:[HomeWork] = []
if let homeWorkData = UserDefaults.standard.data(forKey: "homeworkData"){
retrivedHomeWorkArray = try! JSONDecoder().decode([HomeWork].self, from: homeWorkData)
}
if retrivedHomeWorkArray.count == 0 {
var homeWorkArray = [HomeWork]()
homeWorkArray.append(homeWork)
let homeWorkDat = try! JSONEncoder().encode(homeWorkArray)
UserDefaults.standard.set(homeWorkDat, forKey: "homeworkData")
}else{
retrivedHomeWorkArray.append(homeWork)
let homeWorkDat = try! JSONEncoder().encode(retrivedHomeWorkArray)
UserDefaults.standard.set(homeWorkDat, forKey: "homeworkData") } }}

When you delete , you don't alter saved array
homeWorkArray.remove(at: indexPath.row)
self.tableView.deleteRows(at: [indexPath], with: UITableView.RowAnimation.automatic)
self.tableView.reloadData()
HomeWork.getHomeWork()
so consider adding a function that re-saves the current array like
public static func reSave(homeWorks:[HomeWork]){
let homeWorkDat = try! JSONEncoder().encode(homeWorks)
UserDefaults.standard.set(homeWorkDat, forKey: "homeworkData")
}
then replace above code with
homeWorkArray.remove(at: indexPath.row)
self.tableView.deleteRows(at: [indexPath], with: UITableView.RowAnimation.automatic)
HomeWork.reSave(homeWorks:homeWorkArray)

Related

Error in my code when trying to storeData

I get
Referencing instance method 'encode' on 'Array' requires that '(item: String?, price: String?, salesPrice: String?)' conform to 'Encodable' "
this error in the storeData() function. Did I even save the tuple correctly in user defaults? If anyone can help that would be great!Any help is appreciated!
import UIKit
let defaults = UserDefaults(suiteName: "com.Saving.Data")
struct Product: Codable {
var title: String
var price: String
var salePrice: String
}
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var items: [(item: String?, price: String?, salesPrice: String?)] = []
override func viewDidLoad() {
super.viewDidLoad()
getData()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
getData()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(true)
storeData()
}
override var prefersStatusBarHidden: Bool {
return true
}
#IBAction func addButtonTapped(_ sender: Any) {
let alert = UIAlertController(title: "Product Information", message: nil, preferredStyle: .alert)
alert.addTextField { (itemTF) in
itemTF.placeholder = "Item"
}
alert.addTextField { (textField) in
textField.placeholder = "Price"
}
alert.addTextField { (textField) in
textField.placeholder = "Sale Price"
}
let action = UIAlertAction(title: "Add", style: .default) { (_) in
var product : (item: String, price: String, salesPrice: String) = ("","","")
if let textField1 = alert.textFields?[0], let text = textField1.text {
print(text)
product.item = text
}
if let textField2 = alert.textFields?[1], let text = textField2.text {
print(text)
product.price = text
}
if let textField3 = alert.textFields?[2], let text = textField3.text {
print(text)
product.salesPrice = text
}
self.add(product)
}
alert.addAction(action)
present(alert, animated: true)
storeData()
}
func add(_ product: (item: String, price: String, salesPrice: String)) {
let index = 0
items.insert(product, at: index)
let indexPath = IndexPath(row: index, section: 0)
tableView.insertRows(at: [indexPath], with: .left)
storeData()
}
func storeData() {
if let data = try? PropertyListEncoder().encode(items) {
UserDefaults.standard.set(data, forKey: "savedData")
}
}
func getData() {
if let data = UserDefaults.standard.data(forKey: "savedData") {
let items = try! PropertyListDecoder().decode([Product].self, from: data)
print(items)
}
}
}
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let product = items[indexPath.row]
cell.textLabel?.text = product.item
print(product.price ?? "")
print(product.salesPrice ?? "")
cell.contentView.backgroundColor = UIColor(red:0.92, green:0.92, blue:0.92, alpha:1.0)
cell.textLabel?.textColor = UIColor(red:0.13, green:0.13, blue:0.13, alpha:1.0)
tableView.separatorColor = UIColor(red:0.92, green:0.92, blue:0.92, alpha:1.0)
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
guard editingStyle == .delete else { return }
items.remove(at: indexPath.row)
tableView.reloadData()
storeData()
}
}
As already mentioned in the comments it's incomprehensible why you use an extra tuple with the same design as your struct Product. This causes your problems.
So get rid of the tuple.
Declare items
var items = [Product]()
Replace the alert action with
let action = UIAlertAction(title: "Add", style: .default) { _ in
let item = alert.textFields?[0].text ?? ""
let price = alert.textFields?[1].text ?? ""
let salesPrice = alert.textFields?[2].text ?? ""
let product = Product(item: item, price: price, salesPrice: salesPrice)
self.addProduct(product)
}
Replace add with
func addProduct(_ product: Product) {
let index = 0
items.insert(product, at: index)
let indexPath = IndexPath(row: index, section: 0)
tableView.insertRows(at: [indexPath], with: .left)
storeData()
}
Replace getData with
func getData() {
if let data = UserDefaults.standard.data(forKey: "savedData") {
do {
items = try PropertyListDecoder().decode([Product].self, from: data)
print(items)
tableView.reloadData()
} catch { print(error) }
}
}
Side note: Be aware that the code saves the data in standard UserDefaults, not in your custom suite.
And finally – not related to the issue – to get a nice animation in tableView:commit editingStyle: replace
tableView.reloadData()
with
tableView.deleteRows(at: [indexPath], with: .fade)

Deleting item from tableview from filtered data crashing

I'm trying to delete a reminder from my tableview but when its been sourced from filtering through the searchbar, the regular delete works perfect when a filter isnt taking place, but when a filter is in the search bar it crashes the app.
Heres the code;
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
var updatedArray = [Reminder]()
if editingStyle == .delete {
if filtered == false {
reminders.remove(at: indexPath.row)
tableReminders.deleteRows(at: [indexPath], with: .fade)
convertAndSaveInDDPath(array: reminders)
}
if filtered == true {
updatedArray = reminders.filter{ $0.reminderName != filterData[indexPath.row].reminderName}
print(updatedArray)
reminders = updatedArray
tableReminders.deleteRows(at: [indexPath], with: .fade)
tableReminders.reloadData()
//convertAndSaveInDDPath(array: reminders)
}
}
}
Any help would be appreciated, thank you.
EDIT (NEW CODE):
public struct Reminder {
var reminderName : String
var reminderPriority : String
var reminderDate : Date
var reminderStatus : String
var reminderSavedTime : Date
}
var reminders : [Reminder] = []
var filtered : Bool = false
public func getFilePath(fileName:String) -> String {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = NSURL(fileURLWithPath: path)
let filePath = url.appendingPathComponent(fileName)?.path
return filePath!
}
public func convertAndSaveInDDPath (array:[Reminder]) {
let objCArray = NSMutableArray()
for obj in array {
// we have to do something like this as we can't store struct objects directly in NSMutableArray
let dict = NSDictionary(objects: [obj.reminderName ,obj.reminderPriority, obj.reminderDate, obj.reminderStatus, obj.reminderSavedTime ], forKeys: ["reminderName" as NSCopying,"reminderPriority" as NSCopying, "reminderDate" as NSCopying, "reminderStatus" as NSCopying, "reminderSavedTime" as NSCopying])
objCArray.add(dict)
}
// this line will save the array in document directory path.
objCArray.write(toFile: getFilePath(fileName: "remindersArray"), atomically: true)
}
public func getArray() -> [Reminder]? {
var remindersArray = [Reminder]()
if let _ = FileManager.default.contents(atPath: getFilePath(fileName: "remindersArray")) {
let array = NSArray(contentsOfFile: getFilePath(fileName: "remindersArray"))
for (_,userObj) in array!.enumerated() {
let reminderDict = userObj as! NSDictionary
let reminder = Reminder(reminderName: (reminderDict.value(forKey: "reminderName") as? String)!, reminderPriority: (reminderDict.value(forKey: "reminderPriority") as? String)!, reminderDate: (reminderDict.value(forKey: "reminderDate") as? Date)!, reminderStatus: (reminderDict.value(forKey: "reminderStatus") as? String)!, reminderSavedTime: (reminderDict.value(forKey: "reminderSavedTime") as? Date)!)
remindersArray.append(reminder)
}
return remindersArray
}
return nil
}
class ViewController: UIViewController, UITableViewDataSource, UITextFieldDelegate, UITableViewDelegate, UISearchBarDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Reminder Section
return filteredReminder.count
}
#IBOutlet var searchBar: UISearchBar!
var filterData = [Reminder]()
var originalReminder = [Reminder]() // original data array.
var filteredReminder = [Reminder]() // data that used to show in tableview.
var sortedAZState : Bool = false
var sortedTimeState : Bool = false
var sortedPrioState : Bool = false
#IBOutlet weak var tableReminders: UITableView!
#IBOutlet weak var timeSortBtn: UIButton!
#IBOutlet weak var sortBtn: UIButton!
#IBOutlet weak var prioritySortBtn: UIButton!
#IBAction func BtnSort(_ sender: Any) {
sortList(sender: sortBtn) // sorts by a-z through the sort function
}
#IBAction func btnSortTime(_ sender: Any) {
sortList(sender: timeSortBtn)
}
#IBAction func btnSortPriority(_ sender: Any) {
sortList(sender: prioritySortBtn)
}
func sortList(sender: UIButton) { // should probably be called sort and not filter
if sender.tag == 1 && sortedAZState == false {
reminders.sort() { $0.reminderName < $1.reminderName } // sort the reminder by name
tableReminders.reloadData(); // notify the table view the data has changed
print("sender.tag 1")
sortedAZState = true
}
else if sender.tag == 1 && sortedAZState == true {
reminders.sort() { $0.reminderName > $1.reminderName } // sort the reminder by name
tableReminders.reloadData(); // notify the table view the data has changed
print("sender.tag 1")
sortedAZState = false
}
else if sender.tag == 2 && sortedTimeState == false {
reminders.sort { $0.reminderSavedTime.compare($1.reminderSavedTime) == .orderedAscending }
tableReminders.reloadData();
print("sender.tag 2")
sortedTimeState = true
}
else if sender.tag == 2 && sortedTimeState == true {
reminders.sort { $0.reminderSavedTime.compare($1.reminderSavedTime) == .orderedDescending }
tableReminders.reloadData();
print("sender.tag 2")
sortedTimeState = false
}
else if sender.tag == 3 && sortedPrioState == false {
reminders.sort() { $0.reminderPriority.count < $1.reminderPriority.count } // sort the reminder by priority
tableReminders.reloadData(); // notify the table view the data has changed
print("sender.tag 3")
sortedPrioState = true
}
else if sender.tag == 3 && sortedPrioState == true {
reminders.sort() { $0.reminderPriority.count > $1.reminderPriority.count } // sort the reminder by priority
tableReminders.reloadData(); // notify the table view the data has changed
print("sender.tag 3")
sortedPrioState = false
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Create an object of the dynamic cell "plainCell"
let cell = tableView.dequeueReusableCell(withIdentifier: "ReminderTableViewCell", for: indexPath) as! ReminderTableViewCell
// Depending on the section, fill the textLabel with the relevant text
// Reminder Section
cell.reminderLabel.text = filteredReminder[indexPath.row].reminderName
if filteredReminder[indexPath.row].reminderPriority == "!" {
let yourImage: UIImage = UIImage(named: "lowpriority")!
cell.priorityImage.image = yourImage
}
else if filteredReminder[indexPath.row].reminderPriority == "!!" {
let yourImage: UIImage = UIImage(named: "mediumpriority")!
cell.priorityImage.image = yourImage
}
else if filteredReminder[indexPath.row].reminderPriority == "!!!" {
let yourImage: UIImage = UIImage(named: "highpriority")!
cell.priorityImage.image = yourImage
}
/* I DON'T KNOW WHAT THIS COMPLETION FOR, HOPE IT IS WORKING A/C TO YOUR NEED. */
// cell.completeButtonAction = { [unowned self] in
// let reminderCall = reminders[indexPath.row].reminderName
// let alert = UIAlertController(title: "Complete!", message: "You have completed \(reminderCall).", preferredStyle: .alert)
// let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
// alert.addAction(okAction)
//
// reminders.remove(at: indexPath.row)
// self.tableReminders.deleteRows(at: [indexPath], with: .fade)
// convertAndSaveInDDPath(array: reminders)
//
// print("reminder deleted")
//
// self.present(alert, animated: true, completion: nil)
// }
//
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
print(reminders[indexPath.row].reminderName)
let selectedReminder = reminders[indexPath.row].reminderName
let destinationVC = EditReminderViewController()
destinationVC.reminderPassed = selectedReminder
performSegue(withIdentifier: "editSegue", sender: indexPath)
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
filteredReminder.remove(at: indexPath.row)
tableReminders.deleteRows(at: [indexPath], with: .fade)
print(filteredReminder)
convertAndSaveInDDPath(array: filteredReminder) // UNCOMMENT THIS
}
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
// var decapValue = searchBar.text?.lowercased()
// print(decapValue!)
// print(LinearSearch(searchText: decapValue!, array: reminders))
if searchBar.text != "" {
self.filteredReminder = originalReminder.filter({ reminder -> Bool in
return reminder.reminderName.lowercased().contains(searchText.lowercased())
})
}
else {
self.filteredReminder = self.originalReminder
}
tableReminders.reloadData()
// print(filterData)
}
/*
func LinearSearch(searchText: String, array: [Reminder]) -> Bool { // search function to return a true or a false bool (contains two parameneters, search value and array
for i in reminders { // cycles through each element in the array
if i.reminderName.lowercased().contains(searchText) { // if element = search (return true)
filterData.append(i)
print(filterData)
return true
}
}
return false // returns false if no element comes back to equal the searchValue
}
*/
#IBAction func btnAdd(_ sender: Any) {
performSegue(withIdentifier: "addSegue", sender: (Any).self)
}
override func viewDidLoad() {
super.viewDidLoad() // example cell
// reminders.append(Reminder(reminderName: "HOMEWORK", reminderPriority: "LOW", reminderDate: "4324", reminderStatus: "INCOMPLETE"))
tableReminders.dataSource = self
tableReminders.delegate = self
searchBar.delegate = self
tableReminders.reloadData()
// print file path of array saved
// print(getFilePath(fileName: "remindersArray"))
let reminderRetrievedArray = getArray()
reminders = reminderRetrievedArray!
originalReminder = reminders
gfilteredReminder = reminders
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I didn't know which code you were after so there is all of it. But yes you are correct, it deletes one at a time, but it is saving an empty array to be loaded for next open.
Here are the changes to make your code work perfect, clean, understandable and working. If you have any doubt then write comment and if there is any wrong in answer then update the answer.
You don not need to maintain flag filtered. I am assuming you have two array
var originalReminder = [Reminder]() // original data array.
var filteredReminder = [Reminder]() // data that used to show in tableview.
In ViewDidLoad , set right data in originalReminder and if you wanted to show all the original data in table then assign same data in filteredReminder also.
Now we will manage the tableview with 1 array i.e. filteredReminder and if searchbar text is empty the then we will assign orignal array to filtered array. So your searchbar textDidChange look like this.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text != "" {
self.filteredReminder = originalReminder.filter({ reminder -> Bool in
return reminder.reminderName.lowercased().contains(searchText.lowercased())
})
}
else {
self.filteredReminder = self.originalReminder
}
tableReminders.reloadData()
}
You can remove redundant code from cellForRow like below
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredReminder.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Create an object of the dynamic cell "plainCell"
let cell = tableView.dequeueReusableCell(withIdentifier: "ReminderTableViewCell", for: indexPath) as! ReminderTableViewCell
// Depending on the section, fill the textLabel with the relevant text
// Reminder Section
cell.reminderLabel.text = filteredReminder[indexPath.row].reminderName
if filteredReminder[indexPath.row].reminderPriority == "!" {
let yourImage: UIImage = UIImage(named: "lowpriority")!
cell.priorityImage.image = yourImage
}
else if filteredReminder[indexPath.row].reminderPriority == "!!" {
let yourImage: UIImage = UIImage(named: "mediumpriority")!
cell.priorityImage.image = yourImage
}
else if filteredReminder[indexPath.row].reminderPriority == "!!!" {
let yourImage: UIImage = UIImage(named: "highpriority")!
cell.priorityImage.image = yourImage
}
/* I DON'T KNOW WHAT THIS COMPLETION FOR, HOPE IT IS WORKING A/C TO YOUR NEED. */
// cell.completeButtonAction = { [unowned self] in
// let reminderCall = reminders[indexPath.row].reminderName
// let alert = UIAlertController(title: "Complete!", message: "You have completed \(reminderCall).", preferredStyle: .alert)
// let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
// alert.addAction(okAction)
//
// reminders.remove(at: indexPath.row)
// self.tableReminders.deleteRows(at: [indexPath], with: .fade)
// convertAndSaveInDDPath(array: reminders)
//
// print("reminder deleted")
//
// self.present(alert, animated: true, completion: nil)
// }
//
return cell
}
To delete the cell, now need to do this only.
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
filteredReminder.remove(at: indexPath.row)
tableReminders.deleteRows(at: [indexPath], with: .fade)
// convertAndSaveInDDPath(array: filteredReminder) // UNCOMMENT THIS
}
}

Tableview using realm won't reload when deleted in swift

I have a simple tableView with saved data. I created a delete button that lets me multi-delete from realm. That part works, it is when the tableview is suppose to reload that it seems to not work. I have seen a lot of answers that say you should reload it on the main thread, or view or whatever, using dispatchQueue.main.async
using just normal tableView.reloadData() didn't reload the tableview but when I use the dispatchQueue version it does delete a value but usually the last value in the tableView.
For example my tableView has the strings Uno and Un in that descending order. If I chose to delete Uno when I press the delete button the tableview does reload leaving only one value but that value is Uno, but realm Database tells me I deleted Uno and when I go back to that view it shows Un. It just isn't reloading correctly.
I have tried to place the reloadData in the dispatch at many different locations, but it still doesn't reload correctly. I am curious what I am doing wrong.
this is the viewController with the tableview where I delete the data in the tableView:
import UIKit
import Realm
import RealmSwift
class OtherViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var otherTableView: UITableView!
var realm: Realm!
var realmedData = ""
var realmList: Results<Realmed> {
get {
return realm.objects(Realmed.self)
}
}
let deleteBtn = UIBarButtonItem()
var testingBool = false
var realmArr = [String]()
var idValue = [Int]()
var idArr = [Int]()
var spanArrValue: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
otherTableView.reloadData()
realm = try! Realm()
self.otherTableView.delegate = self
self.otherTableView.dataSource = self
self.otherTableView.reloadData()
deleteBtnInfo(btn: deleteBtn)
self.navigationItem.rightBarButtonItem = deleteBtn
}
func deleteBtnInfo(btn: UIBarButtonItem) {
btn.title = "Delete"
btn.style = .plain
btn.target = self
btn.action = #selector(didTapDeleteBtn(sender:))
testingBool = false
}
#objc func didTapDeleteBtn(sender: AnyObject) {
testingBool = !testingBool
if testingBool == true {
deleteBtn.title = "Remove"
otherTableView.allowsMultipleSelection = true
otherTableView.allowsMultipleSelectionDuringEditing = true
} else if testingBool == false {
deleteBtn.title = "Delete"
didPressRemove()
DispatchQueue.main.async {
self.otherTableView.reloadData()
}
otherTableView.allowsMultipleSelection = false
otherTableView.allowsMultipleSelectionDuringEditing = false
}
}
func didPressRemove() {
if idValue.count == 0 {
print("Please Select what to Delete")
} else {
deleteRealm(idInt: idValue)
}
}
func deleteRealm(idInt: [Int]) {
do {
try realm.write {
for deleteIndex in idInt {
let deleteValue = realm.objects(RealmTwo.self).filter("id == %#", deleteIndex as Any)
print(deleteIndex)
realm.delete(deleteValue)
}
}
} catch {
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var counted = realm.objects(RealmTwo.self).filter("realmLbl == %#", realmedData)
return counted.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "otherCell", for: indexPath) as! OtherTableViewCell
var celledItem = realm.objects(Realmed.self)
for item in celledItem {
for items in item.realmTwo {
self.idArr.append(items.id)
self.realmArr.append(items.spanish)
}
}
cell.otherLbl.text = "\(realmArr[indexPath.row])"
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if testingBool == false {
print(realmArr[indexPath.row])
} else {
self.idValue.append(idArr[indexPath.row])
print(spanArrValue)
}
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if testingBool == true {
if let index = idValue.index(of: idArr[indexPath.row]) {
idValue.remove(at: index)
print(spanArrValue)
}
}
}
}
this is the realm class for the data that I am trying to delete.
import Foundation
import UIKit
import Realm
import RealmSwift
class RealmTwo: Object {
#objc dynamic var id = Int()
#objc dynamic var realmLbl = String()
#objc dynamic var spanish = String()
#objc dynamic var french = String()
let realmed = LinkingObjects(fromType: Realmed.self, property: "realmTwo")
convenience init(id: Int, realmLbl: String, spanish: String, french: String) {
self.init()
self.id = id
self.realmLbl = realmLbl
self.spanish = spanish
self.french = french
}
}
As I said above, I placed reloadData() in different places and these are where I placed them, just in case you want to know:
func didPressRemove() {
if idValue.count == 0 {
print("Please Select what to Delete")
} else {
deleteRealm(idInt: idValue)
DispatchQueue.main.async {
self.otherTableView.reloadData()
}
}
}
func deleteRealm(idInt: [Int]) {
do {
try realm.write {
for deleteIndex in idInt {
let deleteValue = realm.objects(RealmTwo.self).filter("id == %#", deleteIndex as Any)
print(deleteIndex)
realm.delete(deleteValue)
DispatchQueue.main.async {
self.otherTableView.reloadData()
}
}
}
} catch {
}
}
I am just not sure where the reloadData is suppose to go, or if that is the real problem. Thank you for the help, and ask if there is anything else I can do.
There are a couple of issues but the main issue is that you're deleting the object from realm but that object is still hanging around in your dataSource tableView array, realmArr.
There are a whole bunch of solutions but the simplest is to add an observer to the realm results and when an item is added, changed or removed, have that update your dataSource array and then reload the tableview. One option also here is to use those results as the dataSource instead of a separate array. Realm Results objects behave very similar to an array and are great a a dataSource.
Conceptually the realm code is similar to
notificationToken = results.observe { [weak self] (changes: RealmCollectionChange) in
guard let tableView = self?.tableView else { return }
switch changes {
case .initial:
tableView.reloadData() //this is when the realm data is intially loaded.
case .update(_, let deletions, let insertions, let modifications):
//handle add, edit and modify per event.
// with an add, add the provided object to your dataSource
// same thing for remove and modify
case .error(let error):
// An error occurred while opening the Realm file on the background worker thread
fatalError("\(error)")
}
//reload the tableView now the dataSource has been updated
}
There are several options of handling those events and they are all covered in the Realm documentation. See Realm Notifications for further details about setting up the notifications.
A second option is to manually keep things in sync; e.g. when deleting the item from Realm, also delete the item from your dataSource array
This is how I managed to solve this problem.
import UIKit
import Realm
import RealmSwift
class OtherViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var notificationToken: NotificationToken? = nil
#IBOutlet weak var otherTableView: UITableView!
var realm: Realm!
var realmedData = ""
var realmList: Results<RealmTwo> {
get {
return realm.objects(RealmTwo.self).filter("%# == realmLbl", realmedData)
}
}
var realmingList: Results<RealmTwo> {
get {
return realm.objects(RealmTwo.self)
}
}
let deleteBtn = UIBarButtonItem()
var testingBool = false
var realmArr = [String]()
var idValue = [Int]()
var idArr = [Int]()
var spanArrValue: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
otherTableView.allowsMultipleSelectionDuringEditing = true
realm = try! Realm()
notificationToken = realmList.observe { [weak self] (changes: RealmCollectionChange) in
guard let tableView = self?.otherTableView else {return}
switch changes {
case .initial:
tableView.reloadData()
case .update(_, let deletions, let insertions, let modifications):
tableView.beginUpdates()
tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }),
with: .automatic)
tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}),
with: .automatic)
tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }),
with: .automatic)
tableView.endUpdates()
case .error(let error):
fatalError("\(error)")
}
}
self.otherTableView.delegate = self
self.otherTableView.dataSource = self
self.otherTableView.reloadData()
deleteBtnInfo(btn: deleteBtn)
self.navigationItem.rightBarButtonItem = deleteBtn
}
func deleteBtnInfo(btn: UIBarButtonItem) {
btn.title = "Delete"
btn.style = .plain
btn.target = self
btn.action = #selector(didTapDeleteBtn(sender:))
testingBool = false
}
#objc func didTapDeleteBtn(sender: AnyObject) {
testingBool = !testingBool
if testingBool == true {
deleteBtn.title = "Remove"
} else if testingBool == false {
deleteBtn.title = "Delete"
}
}
func didPressRemove() {
if testingBool == false {
print("Select what to Delete")
} else {
deleteRealm(idInt: idValue)
otherTableView.isEditing = false
}
}
#IBAction func pressEdit(_ sender: Any) {
testingBool = !testingBool
if testingBool == true {
otherTableView.isEditing = true
} else if testingBool == false {
otherTableView.isEditing = false
}
}
#IBAction func pressDelete(_ sender: Any) {
deleteRealm(idInt: idValue)
}
func deleteRealm(idInt: [Int]) {
do {
try realm.write {
for deleteIndex in idInt {
let deletingValue = realmList.filter("id == %#", deleteIndex as Any)
print("DeleteValue: \(deletingValue)")
realm.delete(deletingValue)
}
}
} catch {
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return realmList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "otherCell", for: indexPath) as! OtherTableViewCell
cell.otherLbl.text = realmList.filter("%# == realmLbl", realmedData)[indexPath.row].spanish
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if otherTableView.isEditing == false {
} else {
let idArr = realmList.filter("%# == realmLbl", realmedData)[indexPath.row].id
self.idValue.append(idArr)
print("ID: \(idValue)")
}
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if otherTableView.isEditing == true {
let idArr = realmList.filter("%# == realmLbl", realmedData)[indexPath.row].id
if let index = idValue.index(of: idArr) {
idValue.remove(at: index)
print("ID: \(idValue)")
}
}
}
deinit {
notificationToken?.invalidate()
}
}
Thank you

Delete CoreData object

I am still struggling with CoreData to start the week haha. I finally succeeded in saving and fetching my array, now is time to edit and delete.
I'm adding the delete function first but I'm having trouble passing in the correct argument:
Core Data functions:
class CDHandler: NSObject {
private class func getContext() -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
class func saveObject(name:String, code:String, symbol:String, placeholder:String, amount:String) -> Bool {
let context = getContext()
let entity = NSEntityDescription.entity(forEntityName: "CryptosMO", in: context)
let managedObject = NSManagedObject(entity: entity!, insertInto: context)
managedObject.setValue(name, forKey: "name")
managedObject.setValue(code, forKey: "code")
managedObject.setValue(symbol, forKey: "symbol")
managedObject.setValue(placeholder, forKey: "placeholder")
managedObject.setValue(amount, forKey: "amount")
do {
try context.save()
return true
} catch {
return false
}
}
class func fetchObject() -> [CryptosMO]? {
let context = getContext()
var cryptos: [CryptosMO]? = nil
do {
cryptos = try context.fetch(CryptosMO.fetchRequest()) as? [CryptosMO]
return cryptos
} catch {
return cryptos
}
}
class func deleteObject(crypto: CryptosMO) -> Bool {
let context = getContext()
context.delete(crypto)
do {
try context.save()
return true
} catch {
return false
}
}
}
Creating and saving the array :
if addedCrypto != "" {
if addedCrypto == "Bitcoin BTC" {
if CDHandler.saveObject(name: "Bitcoin", code: "bitcoin", symbol: "BTC", placeholder: "BTC Amount", amount: "0.0") {
for crypto in CDHandler.fetchObject()! {
print("\(String(describing: crypto.name))'s symbol is \(String(describing: crypto.symbol))")
}
}
}
}
Fetching Core Data for the TableView:
override func viewWillAppear(_ animated: Bool) {
tableView.delegate = self
tableView.dataSource = self
if CDHandler.fetchObject() != nil {
cryptos = CDHandler.fetchObject()!
tableView.reloadData()
}
}
TableView functions:
extension WalletTableViewController: UITableViewDelegate, UITableViewDataSource, CryptoCellDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cryptos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! WalletTableViewCell
cell.cryptoNameLabel.text = cryptos[indexPath.row].name
cell.amountTextField.placeholder = cryptos[indexPath.row].placeholder
cell.delegate = self
cell.amountTextField.delegate = self
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
cryptos.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
CDHandler.deleteObject(crypto: cryptos) // <----- Cannot convert value of type '[CryptosMO]' to expected argument type 'CryptosMO'
}
}
}
What is the problem here? I can change func deleteObject(crypto: CryptosMO) to func deleteObject(crypto: [CryptosMO]) but then I get Cannot convert value of type '[CryptosMO]' to expected argument type 'NSManagedObject'.
I read that delete() only take an NSManagedObject as its sole argument so I believe I created an incorrect object in the first place to be able to delete it??
Just call this method and pass entity with managedObjectwhich you want to delete:
func deleteData(entity:String,deleteObject:NSManagedObject){
//for iOS 10+
// let delegate = UIApplication.shared.delegate as? AppDelegate
// let context = delegate!.persistentContainer.viewContext
let context = getContext()
context.delete(deleteObject)
do {
try context.save()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let selectedManagedObject = cryptos[indexPath.row]
deleteData(entity:"yourEntityName",deleteObject: selectedManagedObject)
cryptos.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
same like save method you can edit, just you need to pass the managedObject which you want to edit:
class func updateObject(name:String, code:String, symbol:String, placeholder:String, amount:String,selectedManagedObject:NSManagedObject) {
let context = getContext()
selectedManagedObject.setValue(name, forKey: "name")
selectedManagedObject.setValue(code, forKey: "code")
selectedManagedObject.setValue(symbol, forKey: "symbol")
selectedManagedObject.setValue(placeholder, forKey: "placeholder")
selectedManagedObject.setValue(amount, forKey: "amount")
do {
try context.save()
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
When you call CDHandler.deleteObject(crypto: ...) just pass (crypto: cryptos[indexPath.row]) instead of (crypto: cryptos).
...
CDHandler.deleteObject(crypto: cryptos[indexPath.row])
cryptos.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)

How to delete tableview items after they are deleted in Firebase

My tableview currently updates my table and adds new items in real-time when they are added to my firebase database. The problem is that I cannot delete in real-time. I am storing my data from firebase in a local array, and then loading that array to the tableview.
I tried to condense my code a bit. I also tried to put the Firebase code that is inside my removeDeletedItems() function inside my populateArrays() function, and to put it after the .childAdded listener, but did not have luck with deleting the data in real-time.
override func viewDidLoad() {
super.viewDidLoad()
populateArrays()
}
func removeDeletedItems() {
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("Users").observe(FIRDataEventType.childRemoved, with: { (FIRDataSnapshot) in
guard let emailToFind = FIRDataSnapshot.value as? String else { return }
for (index, email) in self.usernames.enumerated() {
if email == emailToFind {
let indexPath = IndexPath(row: index, section: 0)
self.usernames.remove(at: index)
self.tableView.deleteRows(at: [indexPath], with: .fade)
self.tableView.reloadData()
}
}
})
}
func populateArrays(){
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("Users").observe(FIRDataEventType.childAdded, with: { (FIRDataSnapshot) in
if let data = FIRDataSnapshot.value as? NSDictionary {
if let name = data[Constants.NAME] as? String {
self.usernames.append(name)
self.removeDeletedItems()
self.tableView.reloadData()
}
}
})
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = usernames[indexPath.row]
return cell
}
Isn't the observed value always a dictionary? And shouldn't you check also for the name rather than the email?
The loop to find the name is not needed. There is a convenience function.
databaseRef.child("Users").observe(FIRDataEventType.childRemoved, with: { snapshot in
guard let data = snapshot.value as? [String:Any],
let nameToFind = data[Constants.NAME] as? String else { return }
if let index = self.usernames.index(of: nameToFind) {
let indexPath = IndexPath(row: index, section: 0)
self.usernames.remove(at: index)
self.tableView.deleteRows(at: [indexPath], with: .fade)
// don't reload the table view after calling `deleteRows`
}
}
})

Resources