Tableview using realm won't reload when deleted in swift - ios

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

Related

Problem with saving data using Core Data in swift

I'm trying to save data to the core data and then display it on another view controller. I have a table view with custom cell, which have a button. I've created a selector, so when we tap on the button in each of the cell, it should save all the data from the cell. Here is my parent view controller:
import UIKit
import SafariServices
import CoreData
class ViewController: UIViewController, UISearchBarDelegate {
#IBOutlet weak var pecodeTableView: UITableView!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var savedNews = [SavedNews]()
var newsTitle: String?
var newsAuthor: String?
var urlString: String?
var newsDate: String?
var isSaved: Bool = false
private var articles = [Article]()
private var viewModels = [NewsTableViewCellViewModel]()
private let searchVC = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
pecodeTableView.delegate = self
pecodeTableView.dataSource = self
pecodeTableView.register(UINib(nibName: S.CustomCell.customNewsCell, bundle: nil), forCellReuseIdentifier: S.CustomCell.customCellIdentifier)
fetchAllNews()
createSearchBar()
loadNews()
saveNews()
countNewsToCategory()
}
#IBAction func goToFavouritesNews(_ sender: UIButton) {
performSegue(withIdentifier: S.Segues.goToFav, sender: self)
}
private func fetchAllNews() {
APICaller.shared.getAllStories { [weak self] result in
switch result {
case .success(let articles):
self?.articles = articles
self?.viewModels = articles.compactMap({
NewsTableViewCellViewModel(author: $0.author ?? "Unknown", title: $0.title, subtitle: $0.description ?? "No description", imageURL: URL(string: $0.urlToImage ?? "")
)
})
DispatchQueue.main.async {
self?.pecodeTableView.reloadData()
}
case .failure(let error):
print(error)
}
}
}
private func createSearchBar() {
navigationItem.searchController = searchVC
searchVC.searchBar.delegate = self
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewModels.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: S.CustomCell.customCellIdentifier, for: indexPath) as! CustomNewsCell
cell.configure(with: viewModels[indexPath.row])
cell.saveNewsBtn.tag = indexPath.row
cell.saveNewsBtn.addTarget(self, action: #selector(didTapCellButton(sender:)), for: .touchUpInside)
return cell
}
#objc func didTapCellButton(sender: UIButton) {
guard viewModels.indices.contains(sender.tag) else { return }
print("Done")// check element exist in tableview datasource
if !isSaved {
saveNews()
print("success")
}
//Configure selected button or update model
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let article = articles[indexPath.row]
guard let url = URL(string: article.url ?? "") else {
return
}
let vc = SFSafariViewController(url: url)
present(vc, animated: true)
}
//Search
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
guard let text = searchBar.text, !text.isEmpty else {
return
}
APICaller.shared.Search(with: text) { [weak self] result in
switch result {
case .success(let articles):
self?.articles = articles
self?.viewModels = articles.compactMap({
NewsTableViewCellViewModel(author: $0.author ?? "Unknown", title: $0.title, subtitle: $0.description ?? "No description", imageURL: URL(string: $0.urlToImage ?? "")
)
})
DispatchQueue.main.async {
self?.pecodeTableView.reloadData()
self?.searchVC.dismiss(animated: true, completion: nil)
}
case .failure(let error):
print(error)
}
}
}
}
extension ViewController {
func loadNews() {
let request: NSFetchRequest<SavedNews> = SavedNews.fetchRequest()
do {
let savedNews = try context.fetch(request)
//Handle saved news
if savedNews.count > 0 {
isSaved = true
}
} catch {
print("Error fetching data from context \(error)")
}
}
func saveNews() {
//Initialize the context
let news = SavedNews(context: self.context)
//Putting data
news.title = newsTitle
news.author = newsAuthor
news.publishedAt = newsDate
news.url = urlString
do {
try context.save()
} catch {
print("Error when saving data \(error)")
}
}
func countNewsToCategory() {
//Initialize the context
let request: NSFetchRequest<SavedNews> = SavedNews.fetchRequest()
let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
])
request.predicate = predicate
do {
savedNews = try context.fetch(request)
} catch {
print("Error fetching data from category \(error)")
}
}
}
I don't know where is the problem, I've created a correct data model, but data could not be saved. Here is my model:
import Foundation
struct APIResponse: Codable {
let articles: [Article]
}
struct Article: Codable {
let author: String?
let source: Source
let title: String
let description: String?
let url: String?
let urlToImage: String?
let publishedAt: String
}
struct Source: Codable {
let name: String
}
And also my model in Core Data:
My second view controller, to which I want display the data:
import UIKit
import CoreData
class FavouriteNewsViewController: UIViewController {
#IBOutlet weak var favTableView: UITableView!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var savedNews = [SavedNews]()
override func viewDidLoad() {
super.viewDidLoad()
favTableView.delegate = self
favTableView.delegate = self
loadSavedNews()
favTableView.register(UINib(nibName: S.FavouriteCell.favouriteCell, bundle: nil), forCellReuseIdentifier: S.FavouriteCell.favouriteCellIdentifier)
// Do any additional setup after loading the view.
}
}
extension FavouriteNewsViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return savedNews.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = favTableView.dequeueReusableCell(withIdentifier: S.FavouriteCell.favouriteCellIdentifier, for: indexPath) as! FavouritesCell
print(savedNews)
let article = savedNews[indexPath.row]
if let articleTitle = article.title {
cell.favTitle.text = articleTitle
}
if let articleAuthor = article.author {
cell.favAuthor.text = articleAuthor
}
if let articleDesc = article.desc {
cell.favDesc.text = article.desc
}
return cell
}
}
extension FavouriteNewsViewController {
func loadSavedNews() {
let request: NSFetchRequest<SavedNews> = SavedNews.fetchRequest()
do {
savedNews = try context.fetch(request)
} catch {
print("Error fetching data from context \(error)")
}
}
func deleteNews(at indexPath: IndexPath) {
// Delete From NSObject
context.delete(savedNews[indexPath.row])
// Delete From current News list
savedNews.remove(at: indexPath.row)
// Save deletion
do {
try context.save()
} catch {
print("Error when saving data \(error)")
}
}
}
you did not assign your properties that you are trying to save
newsTitle,newsAuthor,newsDate,urlString
seems these properties have nil value . make sure these properties have valid value before save .

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

Completion handler issues

So I have a tableview controller which has headers and cells which are supposed to be present under those headers. I have already done the job of correctly creating the headers. I currently have prepared the datasource for the cells which takes its content from the a list of currentEvents that a user is currently attending. The issue isn't the pulling of the data it seems to be the completion block. It takes so long to come back that the currentEvents array never gets appended when it is supposed to. It also keeps appending events onto the original array even after it should reset upon entering the new section. I have tried many things and moved code around many places but nothing seems to be having any effect
The key function in all this is
self.fetchEventsFromServer()
specifically the part where the EventService.show takes place
import UIKit
import Firebase
class FriendsEventsView: UITableViewController{
var cellID = "cellID"
var friends = [Friend]()
var attendingEvents = [Event]()
//label that will be displayed if there are no events
var currentUserName: String?
var currentUserPic: String?
var currentEventKey: String?
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Friends Events"
view.backgroundColor = .white
// Auto resizing the height of the cell
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "close_black").withRenderingMode(.alwaysOriginal), style: .done, target: self, action: #selector(self.goBack))
tableView.register(EventDetailsCell.self, forCellReuseIdentifier: cellID)
self.tableView.tableFooterView = UIView(frame: CGRect.zero)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
DispatchQueue.global(qos: .background).async {
print("This is run on the background queue")
self.fetchEventsFromServer { (error) in
if error != nil {
print(error)
return
} else {
DispatchQueue.main.async {
self.tableView.reloadData()
print("This is run on the main queue, after the previous code in outer block")
}
}
}
}
}
#objc func goBack(){
dismiss(animated: true)
}
override func numberOfSections(in tableView: UITableView) -> Int {
// print(friends.count)
return friends.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// print(friends[section].events.count)
return friends[section].collapsed ? 0 : friends[section].events.count
}
func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID) as! EventDetailsCell? ?? EventDetailsCell(style: .default, reuseIdentifier: cellID)
// print(indexPath.row)
cell.details = friends[indexPath.section].events[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "header") as? CollapsibleTableViewHeader ?? CollapsibleTableViewHeader(reuseIdentifier: "header")
// print(section)
header.arrowLabel.text = ">"
header.setCollapsed(friends[section].collapsed)
print(friends[section].collapsed)
header.section = section
// header.delegate = self
header.friendDetails = friends[section]
return header
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func fetchEventsFromServer(_ completion: #escaping (_ error: Error?) -> Void ){
//will grab the uid of the current user
guard let myUserId = Auth.auth().currentUser?.uid else {
return
}
let ref = Database.database().reference()
//checking database for users that the current user is following
ref.child("following").child(myUserId).observeSingleEvent(of: .value, with: { (followingSnapshot) in
//handling potentail nil or error cases
guard let following = followingSnapshot.children.allObjects as? [DataSnapshot]
else {return}
//validating if proper data was pulled
let group = DispatchGroup()
for followingId in following {
group.enter()
print(followingId.key)
ref.child("users").child(followingId.key).observeSingleEvent(of: .value, with: { (userInfoSnapShot) in
guard let followingUserInfo = userInfoSnapShot.children.allObjects as? [DataSnapshot] else {
return
}
//validating if proper data was pulled for each follower
for currentUserInfo in followingUserInfo {
//will add this back when I want to event implementation
if currentUserInfo.key == "Attending" {
guard let eventKeys = currentUserInfo.children.allObjects as? [DataSnapshot] else{return}
for event in eventKeys {
print(event.key)
EventService.show(forEventKey: event.key, completion: { (event) in
guard let currentEvent = event else{
return
}
self.attendingEvents.append(currentEvent)
print(self.attendingEvents.count)
})
}
}
if currentUserInfo.key == "profilePic"{
self.currentUserPic = currentUserInfo.value as! String
//print(self.currentUserPic)
}
if currentUserInfo.key == "username"{
self.currentUserName = currentUserInfo.value as! String
//print(self.currentUserName)
var friend = Friend(friendName: self.currentUserName!, events: self.attendingEvents, imageUrl: self.currentUserPic!)
print(friend.events.count)
self.friends.append(friend)
//print(self.friends.count)
}
}
group.leave()
let result = group.wait(timeout: .now() + 0.01)
completion(nil)
}, withCancel: { (err) in
completion(err)
print("Couldn't grab info for the current list of users: \(err)")
})
}
completion(nil)
}) { (err) in
completion(err)
print("Couldn't grab people that you are currently following: \(err)")
}
}
}
If anyone sees something I don't please dont hesitate to say something
How would I alter my dispatch group implementation to accomplish this goal
EventService Function
struct EventService {
static func show(forEventKey eventKey: String, completion: #escaping (Event?) -> Void) {
// print(eventKey)
let ref = Database.database().reference().child("events").child(eventKey)
// print(eventKey)
//pull everything
ref.observeSingleEvent(of: .value, andPreviousSiblingKeyWith: { (snapshot,eventKey) in
// print(snapshot.value ?? "")
guard let event = Event(snapshot: snapshot) else {
return completion(nil)
}
completion(event)
})
}
}

Swift - Update/add/delete item on a table view with sections and source

How can I update/add/delete an item on a table view with sections and its source?
Im having issues when trying to update a cell. Sometimes it happens on delete and add too.
It seems that a problem on sections is triggering it.
1- Data is loaded from a JSON response from a server.
2 - This data is sorted alphabetically and sections based on the first letter from the name is created adding each client to its indexed letter.
Im adding to print-screens:
Before:
After:
I renamed 'Bowl' to 'Bowl 2' and it 'created' a new entry, keeping the old and new value. If I refresh (pull), it gets fixed.
Also, sometimes, it removes 'Abc MacDon' and after refreshing, it gets fixed.
class ClientsViewController: UITableViewController {
var sortedFirstLetters: [String] = []
var sections: [[Client]] = [[]]
var tableArray = [Client]()
var client: Client?
var wasDeleted: Bool?
var refresher: UIRefreshControl!
#IBOutlet weak var noClientsLabel: UILabel!
#IBOutlet var noClientsView: UIView!
#IBAction func unwindToClients(sender: UIStoryboardSegue) {
if let sourceViewController = sender.source as? ClientViewController,
let client = sourceViewController.client,
let wasDeleted = sourceViewController.wasDeleted {
if(wasDeleted) {
if let selectedIndexPath = tableView.indexPathForSelectedRow {
print("Delteted")
tableArray.remove(at: selectedIndexPath.row)
// DispatchQueue.main.async {
// // Deleting the row in the tableView
// if self.tableView.numberOfRows(inSection: selectedIndexPath.section) > 1 {
// self.tableView.deleteRows(at: [selectedIndexPath], with: UITableViewRowAnimation.bottom)
// } else {
// let indexSet = NSMutableIndexSet()
// indexSet.add(selectedIndexPath.section)
// self.tableView.deleteSections(indexSet as IndexSet, with: UITableViewRowAnimation.bottom)
// }
//
// }
}
}
else {
if let selectedIndexPath = tableView.indexPathForSelectedRow {
// Update an existing client.
tableArray[selectedIndexPath.row] = client
//tableView.reloadRows(at: [selectedIndexPath], with: .automatic)
print("update")
print(tableArray)
}
else {
// Add a client.
tableArray.append(client)
print("add")
}
}
self.prepareData()
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let secondScene = segue.destination as! ClientViewController
if segue.identifier == "ShowDetail", let indexPath = self.tableView.indexPathForSelectedRow {
let currentPhoto = sections[indexPath.section][indexPath.row]
secondScene.client = currentPhoto
}
else if segue.identifier == "AddItem" {
print("add")
}
else {
fatalError("The selected cell is not being displayed by the table")
}
}
#objc func handleRefresh(_ refreshControl: UIRefreshControl) {
getClients()
}
}
extension ClientsViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.refreshControl?.addTarget(self, action: #selector(ClientsViewController.handleRefresh(_:)), for: UIControlEvents.valueChanged)
tableView.backgroundView = nil
noClientsLabel.text = ""
getClients() //for only the 1st time ==> when view is created ==> ok ish
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if(self.tableArray.count > 0) {
return sortedFirstLetters[section]
}
else {
return ""
}
}
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return sortedFirstLetters
}
override func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = sections[indexPath.section][indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "ClientCell", for: indexPath)
cell.textLabel?.text = item.name
cell.detailTextLabel?.text = item.city + " - " + item.province
return cell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].count
}
func getClients() {
print("called server")
self.refreshControl?.beginRefreshing()
self.tableView.setContentOffset(CGPoint(x:0, y:-100), animated: true)
makeRequest(endpoint: "api/clients/all",
parameters: [:],
completionHandler: { (container : ApiContainer<Client>?, error : Error?) in
if let error = error {
print("error calling POST on /getClients")
print(error)
return
}
self.tableArray = (container?.result)!
self.prepareData()
DispatchQueue.main.async {
if(self.tableArray.isEmpty)
{
self.noClientsLabel.text = "bNo Clients"
self.tableView.backgroundView?.isHidden = false
self.noClientsLabel.text = ""
print("all")
}
else{
print("nothing")
}
self.tableView.reloadData()
self.refreshControl?.endRefreshing()
}
} )
}
//sorts and makes the index
func prepareData() {
let firstLetters = self.tableArray.map { $0.nameFirstLetter }
let uniqueFirstLetters = Array(Set(firstLetters))
self.sortedFirstLetters = uniqueFirstLetters.sorted()
self.sections = self.sortedFirstLetters.map { firstLetter in
return self.tableArray
.filter { $0.nameFirstLetter == firstLetter }
.sorted { $0.name < $1.name }
}
}
}
Struct
struct Client: Codable {
var client_id: Int!
let name: String!
let postal_code: String!
let province: String!
let city: String!
let address: String!
init(name: String, client_id: Int! = nil, postal_code: String, province: String, city: String, address: String) {
self.client_id = client_id
self.name = name
self.postal_code = postal_code
self.province = province
self.city = city
self.address = address
}
var nameFirstLetter: String {
return String(self.name[self.name.startIndex]).uppercased()
}
}
code after some interaction with Hardik
import UIKit
import Foundation
class ClientsViewController: UITableViewController {
var sortedFirstLetters: [String] = []
var sections: [[Client]] = [[]]
// var tableArray = [Client]()
var tableArray : [Client] = [Client]()
var client: Client?
var wasDeleted: Bool?
var refresher: UIRefreshControl!
#IBOutlet weak var noClientsLabel: UILabel!
#IBOutlet var noClientsView: UIView!
#IBAction func unwindToClients(sender: UIStoryboardSegue) {
if let sourceViewController = sender.source as? ClientViewController,
let client = sourceViewController.client,
let wasDeleted = sourceViewController.wasDeleted {
if(wasDeleted) {
if let index = self.tableArray.index(where: { (item) -> Bool in
item.client_id == client.client_id
}) {
self.tableArray.remove(at: index)
print("Delteted")
// Find the client in the tableArray by the client id and remove it from the tableArray
// I am writing an example code here, this is not tested so just get the logic from here.
//self.tableArray.remove(at: selectedIndexPath.row)
}
}
else {
if self.tableArray.contains(where: { (item) -> Bool in
item.client_id == client.client_id
}) {
//Find the item in the tableArray by the client id and update it there too
// I am writing an example code here, this is not tested so just get the logic from here.
//if let index = self.tableArray.index(where: { (item) -> Bool in
// item.id == client.id
//}) {
self.tableArray[index] = client
//self.tableArray.replace(client, at: index)
//}
print("update")
print(tableArray)
}
else {
// Add a client.
tableArray.append(client)
print("add")
}
}
// Now update the sections Array and it will have all the correct values
self.prepareData()
DispatchQueue.main.async {
self.tableView.reloadData()
}
// if(wasDeleted) {
// if let selectedIndexPath = tableView.indexPathForSelectedRow {
// print("Delteted")
// sections[selectedIndexPath.section].remove(at: selectedIndexPath.row)
// }
//
// }
// else {
// if let selectedIndexPath = tableView.indexPathForSelectedRow {
// // Update an existing client.
// sections[selectedIndexPath.section][selectedIndexPath.row] = client
// //tableView.reloadRows(at: [selectedIndexPath], with: .automatic)
// print("update")
// print(tableArray)
// }
// else {
// // Add a client.
// tableArray.append(client)
// print("add")
// self.prepareData()
// }
// }
//
// DispatchQueue.main.async {
//
// self.tableView.reloadData()
// }
// if(wasDeleted) {
// if let selectedIndexPath = tableView.indexPathForSelectedRow {
// print("Delteted")
// tableArray.remove(at: selectedIndexPath.row)
//// DispatchQueue.main.async {
//// // Deleting the row in the tableView
//// if self.tableView.numberOfRows(inSection: selectedIndexPath.section) > 1 {
//// self.tableView.deleteRows(at: [selectedIndexPath], with: UITableViewRowAnimation.bottom)
//// } else {
//// let indexSet = NSMutableIndexSet()
//// indexSet.add(selectedIndexPath.section)
//// self.tableView.deleteSections(indexSet as IndexSet, with: UITableViewRowAnimation.bottom)
//// }
////
//// }
// }
//
// }
// else {
// if let selectedIndexPath = tableView.indexPathForSelectedRow {
// // Update an existing client.
// tableArray[selectedIndexPath.row] = client
// //tableView.reloadRows(at: [selectedIndexPath], with: .automatic)
// print("update")
// print(tableArray)
// }
// else {
// // Add a client.
// tableArray.append(client)
// print("add")
// }
// }
// self.prepareData()
// DispatchQueue.main.async {
//
// self.tableView.reloadData()
// }
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let secondScene = segue.destination as! ClientViewController
if segue.identifier == "ShowDetail", let indexPath = self.tableView.indexPathForSelectedRow {
let currentPhoto = sections[indexPath.section][indexPath.row]
secondScene.client = currentPhoto
}
else if segue.identifier == "AddItem" {
print("add")
}
else {
fatalError("The selected cell is not being displayed by the table")
}
}
#objc func handleRefresh(_ refreshControl: UIRefreshControl) {
getClients()
}
}
extension ClientsViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.refreshControl?.addTarget(self, action: #selector(ClientsViewController.handleRefresh(_:)), for: UIControlEvents.valueChanged)
tableView.backgroundView = nil
noClientsLabel.text = ""
getClients() //for only the 1st time ==> when view is created ==> ok ish
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if(self.tableArray.count > 0) {
return sortedFirstLetters[section]
}
else {
return ""
}
}
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return sortedFirstLetters
}
override func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = sections[indexPath.section][indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "ClientCell", for: indexPath)
cell.textLabel?.text = item.name
cell.detailTextLabel?.text = item.city + " - " + item.province
return cell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].count
}
func getClients() {
print("called server")
self.refreshControl?.beginRefreshing()
self.tableView.setContentOffset(CGPoint(x:0, y:-100), animated: true)
makeRequest(endpoint: "api/clients/all",
parameters: [:],
completionHandler: { (container : ApiContainer<Client>?, error : Error?) in
if let error = error {
print("error calling POST on /getClients")
print(error)
return
}
self.tableArray = (container?.result)!
self.prepareData()
DispatchQueue.main.async {
if(self.tableArray.isEmpty)
{
self.noClientsLabel.text = "bNo Clients"
self.tableView.backgroundView?.isHidden = false
self.noClientsLabel.text = ""
print("all")
}
else{
print("nothing")
}
self.tableView.reloadData()
self.refreshControl?.endRefreshing()
}
} )
}
//sorts and makes the index
func prepareData() {
let firstLetters = self.tableArray.map { $0.nameFirstLetter }
let uniqueFirstLetters = Array(Set(firstLetters))
self.sortedFirstLetters = uniqueFirstLetters.sorted()
self.sections = self.sortedFirstLetters.map { firstLetter in
return self.tableArray
.filter { $0.nameFirstLetter == firstLetter }
.sorted { $0.name < $1.name }
}
}
}
You are editing the wrong datasource. You should edit or update the sections array not tableArray.
Change your code in unwindToClients here :
if(wasDeleted) {
if let selectedIndexPath = tableView.indexPathForSelectedRow {
print("Delteted")
tableArray.remove(at: selectedIndexPath.row)
}
}
else {
if let selectedIndexPath = tableView.indexPathForSelectedRow {
// Update an existing client.
tableArray[selectedIndexPath.row] = client
//tableView.reloadRows(at: [selectedIndexPath], with: .automatic)
print("update")
print(tableArray)
}
else {
// Add a client.
tableArray.append(client)
print("add")
}
}
self.prepareData()
DispatchQueue.main.async {
self.tableView.reloadData()
}
with this :
if(wasDeleted) {
if let selectedIndexPath = tableView.indexPathForSelectedRow {
print("Delteted")
sections[selectedIndexPath.section].remove(at: selectedIndexPath.row)
}
}
else {
if let selectedIndexPath = tableView.indexPathForSelectedRow {
// Update an existing client.
sections[selectedIndexPath.section][selectedIndexPath.row] = client
//tableView.reloadRows(at: [selectedIndexPath], with: .automatic)
print("update")
print(tableArray)
}
else {
// Add a client.
tableArray.append(client)
print("add")
self.prepareData()
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
To fix the incorrect section headers, try doing something like this:
if(wasDeleted) {
if let index = self.tableArray.index(where: { (item) -> Bool in
item.id == client.id
}) {
print("Delteted")
// Find the client in the tableArray by the client id and remove it from the tableArray
// I am writing an example code here, this is not tested so just get the logic from here.
//self.tableArray.remove(at: index)
}
}
else {
if self.tableArray.contains(client) {
//Find the item in the tableArray by the client id and update it there too
// I am writing an example code here, this is not tested so just get the logic from here.
//if let index = self.tableArray.index(where: { (item) -> Bool in
// item.id == client.id
//}) {
// self.tableArray.replace(client, at: index)
//}
print("update")
print(tableArray)
}
else {
// Add a client.
tableArray.append(client)
print("add")
}
}
// Now update the sections Array and it will have all the correct values
self.prepareData()
DispatchQueue.main.async {
self.tableView.reloadData()
}

Displaying the count of comments in the feed

I have an Instagram like feed with pictures shown one at a time in each cell. You can press on them and get directed to a commentTBV. Here is where I have all the comments underneath the post and they get counted correctly in this View.
var commentsArray = [FotoComment]()
is the array holding the comments
kommentarArray is the array i want to fill with the assignArray func so i can use it to display the amount of counts.
var kommentarArray = [FotoComment]()
[FotoComment] is my struc I use for my comments
What I want is that already in the feed the commentArray.count will show the correct number of comments.
func assignArray() {
let otherVC = CommentTableViewController()
kommentarArray = otherVC.commentsArray
print(kommentarArray.count)
}
This way I get access from my feed to the array of comments in the CommentTBVC.
My cell is:
cell.kommentarZähler.text = "Kommentare: \(kommentarArray.count)"
But it always shows 0 even though it already has 5 comments and it correctly displayed on the CommentTBV.
M complete code for MemesTableViewConbtroller (the feed)
import UIKit
import Firebase
import FirebaseAuth
import FirebaseDatabase
class MemesTableViewController: UITableViewController {
var kommentarArray = [FotoComment]()
var dataBaseRef : FIRDatabaseReference!
var storageRef : FIRStorageReference!
var posts = [PostMitBild]()
var segmentedControl : HMSegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
assignArray()
segmentedControl = HMSegmentedControl(sectionTitles: ["Top Heute", "Beliebteste", "Neue"])
segmentedControl.frame = CGRect(x: 10, y: 10, width: 300, height: 60)
segmentedControl.backgroundColor = UIColor.red
segmentedControl.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
segmentedControl.borderColor = UIColor.brown
segmentedControl.tintColor = UIColor.black
segmentedControl.selectionIndicatorColor = UIColor.gray
segmentedControl.addTarget(self, action: #selector(getter: MemesTableViewController.segmentedControl), for: UIControlEvents.valueChanged)
tableView.tableHeaderView = segmentedControl
segmentedAction()
}
func segmentedAction() {
if segmentedControl.selectedSegmentIndex == 0 {
let postRef = FIRDatabase.database().reference().child("MemesBilder")
postRef.observe(.value, with: { (snapshot) in
var newPost = [PostMitBild]()
for post in snapshot.children {
let Post = PostMitBild(snapshot: post as! FIRDataSnapshot)
newPost.insert(Post, at: 0)
}
self.posts = newPost
DispatchQueue.main.async {
self.tableView.reloadData()
}
}, withCancel: { (error) in
print(error.localizedDescription)
})
}
}
//------------------------------------------
override func viewWillAppear(_ animated: Bool) {
if FIRAuth.auth()?.currentUser == nil {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Login")
self.present(vc, animated: true, completion: nil)
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell
if let seconds = posts[indexPath.row].postDate {
let timestamp = NSDate(timeIntervalSince1970: seconds)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd/MM/yyyy HH:mm"
cell.uploadDatum.text = dateFormatter.string(from: timestamp as Date)
}
cell.kommentarZähler.text = "Kommentare: \(kommentarArray.count)"
cell.usernameTextField.text = posts[indexPath.row].username
cell.postContent.text = posts[indexPath.row].content
storageRef = FIRStorage.storage().reference(forURL: posts[indexPath.row].userImageUrlString)
storageRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
if error == nil {
DispatchQueue.main.async {
if let data = data {
cell.UserImageView.image = UIImage (data: data)
}
}
}else {
print(error?.localizedDescription)
}
})
let storageRef2 = FIRStorage.storage().reference(forURL: posts[indexPath.row].PostImageUrlString)
storageRef2.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
if error == nil {
DispatchQueue.main.async {
if let data = data {
cell.postImageView.image = UIImage (data: data)
}
}
}else {
print(error?.localizedDescription)
}
})
return cell
}
//done!!!! ------------------------------------------
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
tableView.deleteRows(at: [indexPath], with: .fade)
let ref = posts[indexPath.row].ref
ref!.removeValue()
posts.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var numberOfRows = 0
switch segmentedControl.selectedSegmentIndex {
case 0 : numberOfRows = posts.count
case 1: numberOfRows = posts.count
default: break
}
return numberOfRows
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 420.00
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segmentedControl.selectedSegmentIndex == 0 {
if segue.identifier == "addComment" {
let vc = segue.destination as! CommentTableViewController
let indexPath = tableView.indexPathForSelectedRow!
vc.selectedPosts = posts[indexPath.row]
}
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if segmentedControl.selectedSegmentIndex == 0 {
performSegue(withIdentifier: "addComment", sender: self)
}
if segmentedControl.selectedSegmentIndex == 1 {
performSegue(withIdentifier: "addComment", sender: self)
}
if segmentedControl.selectedSegmentIndex == 2 {
performSegue(withIdentifier: "addComment", sender: self)
}
}
func assignArray() {
let otherVC = CommentTableViewController()
kommentarArray = otherVC.commentsArray
print(kommentarArray.count)
}
}
The code for the CommentTableViewController ( where i want to get the count of comments from the array var commentsArray = FotoComment which is already working on this TableView)
import UIKit
import Firebase
import FirebaseAuth
import FirebaseDatabase
import FirebaseStorage
class CommentTableViewController: UITableViewController {
#IBOutlet weak var komentarZähler: UILabel!
#IBOutlet weak var UserImageView: UIImageView!
#IBOutlet weak var usernameTextField: UILabel!
#IBOutlet weak var postImageView: UIImageView!
#IBOutlet weak var postContent: UITextView!
var dataBaseRef : FIRDatabaseReference!
var storageRef : FIRStorageReference!
var commentsArray = [FotoComment]()
var selectedPosts:PostMitBild!
override func viewDidLoad() {
super.viewDidLoad()
configurePost()
let commentRef = selectedPosts.ref!.child("Kommentare")
commentRef.observe(.value, with: { (snapshot) in
var newComments = [FotoComment]()
for item in snapshot.children {
let neuerKommentar = FotoComment(snapshot: item as! FIRDataSnapshot)
newComments.insert(neuerKommentar, at: 0)
}
self.commentsArray = newComments
self.tableView.reloadData()
}, withCancel: { (error) in
print(error.localizedDescription)
})
}
#IBAction func addComment(_ sender: UIBarButtonItem) {
let alertView = UIAlertController(title: "Kommentar", message: "Füge einen Kommentar hinzu", preferredStyle: UIAlertControllerStyle.alert)
alertView.addTextField { (textfield) in
textfield.placeholder = "Einen neuen Kommentar hinzufügen"
}
let sendCommentAction = UIAlertAction(title: "Kommentieren", style: .default) { (action) in
let textfield = alertView.textFields!.first!
let comment = FotoComment(content: textfield.text! , postId: self.selectedPosts.postId , username: (FIRAuth.auth()!.currentUser!.displayName!) , userImageUrlString: String(describing: FIRAuth.auth()!.currentUser!.photoURL!), postDate: (NSDate().timeIntervalSince1970))
let commentRef = self.selectedPosts.ref!.child("Kommentare").childByAutoId()
commentRef.setValue(comment.toAnyObject())
}
let cancelAction = UIAlertAction(title: "Abbrechen", style: .cancel, handler: nil)
alertView.addAction(sendCommentAction)
alertView.addAction(cancelAction)
self.present(alertView, animated: true, completion: nil)
}
// 2----------------------------------------------
func configurePost() {
usernameTextField.text = selectedPosts.username
postContent.text = selectedPosts.content
storageRef = FIRStorage.storage().reference(forURL: selectedPosts.userImageUrlString)
storageRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
if error == nil {
DispatchQueue.main.async {
if let data = data {
self.UserImageView.image = UIImage (data: data)
}
}
}else {
print(error?.localizedDescription)
}
})
let storageRef2 = FIRStorage.storage().reference(forURL: selectedPosts.PostImageUrlString)
storageRef2.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
if error == nil {
DispatchQueue.main.async {
if let data = data {
self.postImageView.image = UIImage (data: data)
}
}
}else {
print(error?.localizedDescription)
}
})
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
komentarZähler.text = "Kommentare: \(commentsArray.count)"
return commentsArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "commentCell", for: indexPath) as! CommentTableViewCell
if let seconds = commentsArray[indexPath.row].postDate {
let timestamp = NSDate(timeIntervalSince1970: seconds)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd/MM/yyyy HH:mm:ss"
cell.uploadDatum.text = dateFormatter.string(from: timestamp as Date)
}
cell.usernameTextField.text = commentsArray[indexPath.row].username
cell.postContent.text = commentsArray[indexPath.row].content
storageRef = FIRStorage.storage().reference(forURL: commentsArray[indexPath.row].userImageUrlString!)
storageRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
if error == nil {
DispatchQueue.main.async {
if let data = data {
cell.UserImageView.image = UIImage (data: data)
}
self.tableView.reloadData()
}
}else {
print(error?.localizedDescription)
}
})
return cell
}
}
this is how i safe my data in firebase. I want to update the number of comments (kommentarAnzahl which is currently 0) every time a comment is added to the post
You have to convert your Array count to string value to display in cell.
Try this Code,
cell.kommentarZähler.text = String("Kommentare:",kommentarArray.count)
I hope this will work for You.
So you have two separate view controllers namely CommentTableViewController and MemesTableViewController consisting of commentsArray and kommentarArray resepctively. These arrays are of class-scope meaning that once you get outside of your class; for this case it would be leaving the view, then they would be deallocated. Once you enter the view, they'll be re-created again and filled with values.
Since you want to get the number of elements in commentsArray, I'd recommend you create a static variable which would keep track of this. When you make something static you're pretty much making it accessible throughout your entire app/program and changes made to it are reflected across the entire app. In other words, a memory block is reserved to store your variable which will only be de-allocated once you quit the app entirely. You may think of this as an app-scope variable.
Two ways
Lousy Way
Change your commentsArray definition from var commentsArray = [FotoComment]() to static var commentsArray = [FotoComment](). By doing this, then you may access and manipulate the contents of this array from any other class. This is great if you have few elements but what happens when you have tens of thousands or even a million comments? It'll mean that we'll be walking around with huge amounts of data everywhere even when we really don't need it.
Recommended Way
Keep your current commentsArray definition and add this static var numberOfComments: Int = 0 inside your CommentTableViewController. Right after adding your elements to commentsArray, update the element tracker as shown below
CommentTableViewController.numberOfComments = commentsArray.count
Then when you go back to your MemesTableViewController, you can simply delete assignArray(); since we have a global element counter now, and simply amend your cell to this
cell.kommentarZähler.text = String(CommentTableViewController.numberOfComments)
With this, even if you create say another class called FriendsVC, you can still access numberOfComments and even change it as well.
PS: Since numberOfComments is static, whenever and wherever you want to access or amend it, you MUST always first call the class or struct in which it was defined in. For this case, it is inside CommentTableViewController so you need to always do CommentTableViewController.numberOfComments to access it; even when you are inside CommentTableViewController itself. Always remember that.

Resources