Pictures shifting randomly in UITableView after being retrieved from Firebase - ios

As my title states, the pictures in my tableView shift around and are not being displayed on the correct posts when scrolling through the table view. After I stop scrolling they seem to be back into place.
I've been trying to make a sense out of the following articles:
new Firebase retrieve data and put on the tableview swift
retrieve image from Firebase storage to show on tableview swift
swift Firebase sort posts in tableview by date
But I cannot figure out how to make the pictures to display better.
Here's what I have:
import UIKit
import FirebaseAuth
import FirebaseDatabase
import FirebaseStorage
class MainFeedTableViewController: UITableViewController {
var posts = [Post]()
let alert = AlertsViewController()
var databaseRef: FIRDatabaseReference! {
return FIRDatabase.database().reference()
}
var storageRef: FIRStorage! {
return FIRStorage.storage()
}
override func viewDidLoad() {
super.viewDidLoad()
fetchPosts()
}
// populates the tableView with posts content in real time
private func fetchPosts(){
let postRefs = databaseRef.child("posts")
postRefs.observe(.value) { (snapshot: FIRDataSnapshot) in
var newPost = [Post]()
for post in snapshot.children{
let postObject = Post(snapshot: post as! FIRDataSnapshot)
newPost.insert(postObject, at: 0)
}
self.posts = newPost
self.tableView.reloadData()
}
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let postsAtIndexPath = posts[indexPath.row]
if postsAtIndexPath.postWithImage == true {
let cell = tableView.dequeueReusableCell(withIdentifier: "postWithImage", for: indexPath) as! PostWithImageTableViewCell
let postUser = postsAtIndexPath.uid
let userRef = databaseRef.child("users/\(postUser!)")
userRef.observe(.value, with: { (snapshot) in
let user = User(snapshot: snapshot)
DispatchQueue.main.async(execute: {
cell.userRealNameLabel.text = user.name
cell.usernameLabel.text = "#" + user.username
cell.postTextView.text = postsAtIndexPath.postText
cell.timeStampLabel.text = postsAtIndexPath.date
})
self.storageRef.reference(forURL: user.photoURL).data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
if error == nil{
DispatchQueue.main.async(execute: {
if let data = data{
cell.userProfilePicture.image = UIImage(data: data)
}
})
}
else{
print(error!.localizedDescription)
}
})
self.storageRef.reference(forURL: postsAtIndexPath.postPictureURL).data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
if error == nil{
DispatchQueue.main.async(execute: {
if let data = data{
cell.postImage.image = UIImage(data: data)
}
})
}
else{
self.alert.displayAlert(alertTitle: "Error", alertMessage: error!.localizedDescription, fromController: self)
}
})
}) { (error) in
self.alert.displayAlert(alertTitle: "Error", alertMessage: error.localizedDescription, fromController: self)
}
return cell
}
else{
let cell = tableView.dequeueReusableCell(withIdentifier: "postWithText", for: indexPath) as! PostTableViewCell
let postUser = postsAtIndexPath.uid
let userRef = databaseRef.child("users/\(postUser!)")
userRef.observe(.value, with: { (snapshot) in
let user = User(snapshot: snapshot)
DispatchQueue.main.async(execute: {
cell.userRealNameLabel.text = user.name
cell.usernameLabel.text = "#" + user.username
cell.postTextView.text = postsAtIndexPath.postText
cell.timestampLabel.text = postsAtIndexPath.date
})
self.storageRef.reference(forURL: user.photoURL).data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
if error == nil{
DispatchQueue.main.async(execute: {
if let data = data{
cell.userProfilePicture.image = UIImage(data: data)
}
})
}
else{
print(error!.localizedDescription)
}
})
}) { (error) in
self.alert.displayAlert(alertTitle: "Error", alertMessage: error.localizedDescription, fromController: self)
}
return cell
}
}
}
The reason I'm having a hard time, it's because I want the user's username, name, and profile picture to change everywhere and in real time when they edit their info. That's why I'm retrieving the user info based on the post's user uid.
What's a better way to implement this?

You have to override the prepareForReuse function in PostWithImageTableViewCell.
EXAMPLE
override func prepareForReuse() {
super.prepareForReuse()
self.userProfilePicture.image = nil
//reset the rest of the values on your `UITableViewCell` subclass
}
EDIT
Since the reuse issue has been resolved, i would like to recommend the following caching framework, Kingfisher, for more convenient image display experience.
Kingfisher has a great extension on UIImageView, what will take care of the caching for your.
Here how you would use it:
let url = URL(string: "https://domain.com/image.jpg")!
imageView.kf.setImage(with: url)
You only set to the URL, what will uniquely identify the image resource, and downloads only once. Here is a cheat sheet, how to use it.

Related

how to delete particular record in coredata using Swift?

my scenario, I am loading JSON Data into CoreData, after that I am fetching into Tableview. Now, Each and every tableview cell have swipe with Delete and Edit button. If I click delete I need to remove data from coredata and tableview both place.
My JSON Structure
class displyDataClass {
var name : String
var username : String
var email : String
init(name : String,username : String,email :String) {
self.name = name
self.username = username
self.email = email
}
}
JSON Load Into CoreData
import UIKit
import CoreData
class ViewController: UIViewController ,UITableViewDelegate,UITableViewDataSource{
var displayDatasssss = [displyDataClass]()
var context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
print("hai")
// 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.
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return displayDatasssss.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell1") as! TableViewCell1
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "User")
cell.label.text = displayDatasssss[indexPath.row].email
let _:AppDelegate = (UIApplication.shared.delegate as! AppDelegate)
let context:NSManagedObjectContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let newUser = NSEntityDescription.insertNewObject(forEntityName: "User", into: context) as NSManagedObject
newUser.setValue(cell.label.text, forKey: "name")
do {
try context.save()
} catch {}
print(newUser)
print("Object Saved.")
let myStringValue = cell.label.text
request.predicate = NSPredicate (format: "name == %#", myStringValue!)
do
{
let result = try context.fetch(request)
if result.count > 0
{
let nameData = (result[0] as AnyObject).value(forKey: "name") as! String
print(nameData)
}
}
catch {
//handle error
print(error)
}
return cell
}
#IBAction func tap(_ sender: Any) {
let url = "http://jsonplaceholder.typicode.com/users"
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = "GET"
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration, delegate: nil, delegateQueue: OperationQueue.main)
let task = session.dataTask(with: request){(data, response,error)in
if (error != nil){
print("Error")
}
else{
do{
// Array of Data
let fetchData = try JSONSerialization.jsonObject(with: data!, options: .mutableLeaves) as! NSArray
for eachData in fetchData {
let eachdataitem = eachData as! [String : Any]
let name = eachdataitem["name"]as! String
let username = eachdataitem["username"]as! String
let email = eachdataitem["email"]as! String
self.displayDatasssss.append(displyDataClass(name: name, username: username,email : email))
}
self.tableView.reloadData()
}
catch{
print("Error 2")
}
}
}
task.resume()
}
}
class displyDataClass {
var name : String
var username : String
var email : String
init(name : String,username : String,email :String) {
self.name = name
self.username = username
self.email = email
}
}
Below code For delete
// delete action two
let deleteAction = UITableViewRowAction(style: .default, title: "Delete", handler: { (action, indexPath) in
print("Delete tapped")
// remove the deleted item from the model
let appDel:AppDelegate = UIApplication.shared.delegate as! AppDelegate
let managedObjectContext = appDel.persistentContainer.viewContext
managedObjectContext.delete(self.displayDatasssssindexPath.row])
self.milestoneTitles.remove(at: indexPath.row)
do {
try managedObjectContext.save()
} catch _ {
}
self.tableView.deleteRows(at: [indexPath], with: .automatic)
return [editAction, deleteAction]
}
Don't use a custom class. Use only the provided User class.
First of all declare a data source array (replacing displayDatasssss)
var users = [User]()
In the tap method load the data and insert new items in the Core Data stack. Consider that each tap on the button inserts duplicate items into the database. Older entries are not removed.
As User has only name and id properties email is assigned to id.
The items are appended to the data source array and saved in the context.
#IBAction func tap(_ sender: Any) {
let url = "http://jsonplaceholder.typicode.com/users")!
let task = session.dataTask(with: url){ [unowned self] (data, response,error)in
if let error = error { print(error); return }
do {
// Array of Data
let fetchData = try JSONSerialization.jsonObject(with: data!) as! [[String:Any]]
for eachDataItem in fetchData {
let name = eachdataitem["name"] as! String
let email = eachdataitem["email"] as! String
let newUser = User(context: self.context)
newUser.name = name
newUser.id = email
self.users.append(newUser)
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
try self.context.save()
} catch{
print("Error 2", error)
}
}
task.resume()
}
In viewDidLoad fetch the data from CoreData and reload the table view
override func viewDidLoad() {
super.viewDidLoad()
do {
let request : NSFetchRequest<User> = User.fetchRequest()
users = try context.fetch(request)
tableView.reloadData()
} catch { print(error) }
}
In cellForRow assign the property value(s) to the labels, nothing else
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell1") as! TableViewCell1
let user = users[indexPath.row]
cell.label.text = user.name
return cell
}
The delete method is quite similar to yours
let deleteAction = UITableViewRowAction(style: .default, title: "Delete", handler: { [unowned self] (action, indexPath) in
print("Delete tapped")
// remove the deleted item from the model
let objectToDelete = self.users.remove(at: indexPath.row)
self.context.delete(objectToDelete)
do {
try self.context.save()
self.tableView.deleteRows(at: [indexPath], with: .automatic)
} catch {
print(error)
}
}
return [editAction, deleteAction]
Note: Print always errors, don't ignore them or print only meaningless literal strings

TableView not updating after button action

I'm relatively new to swift, and I'm trying to have a view that when it loads it will display some info on my tableView, then in the same view I have a textfield and a button
I want that the button performes an action that fetchs data from my server and updates my tableView
The problem is that the table is not being updated. How can I get the table to be updated?
CODE:
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = 80;
tableView.tableFooterView = UIView();
self.url = URL(string: "http://xxxxxxxx.com/xxxxx/api/produtos/listagem/favoritos/\(userID)");
downloadJson(_url: url!);
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return produtos.count;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "ProdutoCell1") as? ProdutoCell else{
return UITableViewCell()
}
cell.lbl_nome_json.text = produtos[indexPath.row].nome;
cell.lbl_categoria_json.text = produtos[indexPath.row].categoria;
cell.lbl_loja_json.text = produtos[indexPath.row].loja
//= produtos[indexPath.row].categoria;
// cell.lbl_loja.text = produtos[indexPath.row].loja;
if let imageURL = URL(string: "http://xxxxxxxxxx.com/myslim/api/"+produtos[indexPath.row].url){
DispatchQueue.global().async {
let data = try? Data(contentsOf: imageURL);
if let data = data{
let image = UIImage(data: data);
DispatchQueue.main.async {
cell.imgView_json.image = image;
}
}
}
}
return cell;
}
ACTION:
#IBAction func btn_search(_ sender: Any) {
if self.txt_search.text == "" {
let alert = UIAlertController(title:"Alert",message: "Insira algo para pesquisar",preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "Click", style: UIAlertAction.Style.default, handler: nil))
self.present(alert,animated: true,completion: nil)
}else{
var pesquisa = String();
pesquisa = self.txt_search.text!;
let url2 = URL(string: "http://xxxxxxxx.com/xxxxxx/api/produtos/listagem/favoritos/\(pesquisa)/\(self.userID)");
downloadJson(_url: url2!);
}
func downloadJson(_url: URL){
guard let downloadURL = url else {return}
URLSession.shared.dataTask(with: downloadURL){data, urlResponse, error in
guard let data = data, error == nil, urlResponse != nil else{
print("algo está mal");
return;
}
do{
let decoder = JSONDecoder();
let downloadedProdutos = try decoder.decode([ProdutoModel].self, from: data);
self.produtos = downloadedProdutos
print(downloadedProdutos);
DispatchQueue.main.async {
self.tableView.reloadData();
print("reload");
}
}catch{
print("algo mal depois do download")
}
}.resume();
}
EDIT
I added some print to see how many object were being returned on my downloadedProdutos variable, on the downloadJson() function.
And at the viewdidload I get 2 object, its normal because I only have 2 Products, but when the action is done I still get 2 object although I should get only 1 object
You need to set tableView.dataSource = self in viewWillAppear and it looks you missed func numberOfSections() -> Int method.
Add UITableViewDataSource like this
class YourViewController: UIViewController, UITableViewDataSource and it will recommend you required methods

UISearchBar seems to be returning right count, but not the right rows

I am trying to implement a search function in my app. For now, I'm just trying to search by the State value my JSON, though I'd like to eventually include Category as well. There are 9 rows total, the first 7 are State=AZ and the last 2 are State=CA. When I search for "KK" the table is empty, which makes sense. But when I search for "CA" I get two rows like I expect, but they are the first two rows in the JSON, which are both AZ, not the two CA rows it should be.
I suspect my issue is somewhere in my filterContentForSearchText function, but since I'm not sure exactly which code you need, here is the ViewController (the function I think is the issue is down near the end):
import UIKit
import os.log
import Foundation
class BonusListViewController: UITableViewController {
var bonuses = [JsonFile.JsonBonuses]()
var filteredBonuses = [JsonFile.JsonBonuses]()
var detailViewController: BonusDetailViewController? = nil
let defaults = UserDefaults.standard
let searchController = UISearchController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
// MARK: Search Support
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Enter two letter state to filter"
navigationItem.searchController = searchController
definesPresentationContext = true
// MARK: Settings Data Struct
struct Constants {
struct RiderData {
let riderNumToH = "riderNumToH"
let pillionNumToH = "pillionNumToH"
}
struct RallyData {
let emailDestinationToH = "emailDestinationToH"
}
}
//MARK: Load the bonuses
loadBonuses { [weak self] bonuses in
self?.bonuses = bonuses ?? []
DispatchQueue.main.async {
self?.tableView.reloadData()
}
print("loadBonuses called")
}
}
// MARK: - Table View Configuration
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isFiltering() {
print("Showing \(filteredBonuses.count) Filtered Results")
return filteredBonuses.count
}
print("Found \(bonuses.count) rows in section.")
return bonuses.count
}
/* Disabling the swipe function until I code it to actually do something
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?
{
let clearAction = UIContextualAction(style: .normal, title: "Clear Data") { (contextAction: UIContextualAction, sourceView: UIView, completionHandler: (Bool) -> Void) in
print("Clear Action Tapped")
completionHandler(true)
}
clearAction.backgroundColor = .blue
let swipeConfig = UISwipeActionsConfiguration(actions: [clearAction])
return swipeConfig
}
*/
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "BonusListViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? BonusListViewCell else {
fatalError("The dequeued cell is not an instance of BonusListViewCell.")
}
let bonus = bonuses[indexPath.row]
let bonusSet: JsonFile.JsonBonuses
if isFiltering() {
bonusSet = filteredBonuses[indexPath.row]
} else {
bonusSet = bonus
}
let urlString = "http://tourofhonor.com/appimages/"+(bonus.imageName)
let url = URL(string: urlString)
cell.primaryImage.downloadedFrom(url: url!)
cell.nameLabel.text = bonus.name.capitalized
cell.bonusCodeLabel.text = bonus.bonusCode.localizedUppercase
cell.categoryLabel.text = bonus.category
cell.valueLabel.text = "\(bonus.value)"
cell.cityLabel.text = "\(bonus.city.capitalized),"
cell.stateLabel.text = bonus.state.localizedUppercase
return cell
}
// MARK: Functions
// MARK: - Fetch JSON from ToH webserver
func downloadJSON(completed: #escaping ([JsonFile.JsonBonuses]?) -> ()) {
let url = URL(string: "http://tourofhonor.com/BonusData.json")!
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error == nil, let data = data {
do {
let posts = try JSONDecoder().decode(JsonFile.self, from: data)
completed(posts.bonuses)
} catch {
print("JSON Download Failed")
}
} else {
print("downloadJSON completed")
completed(nil)
}
}.resume()
}
func saveBonuses(_ bonuses: [JsonFile.JsonBonuses], to url: URL) {
try? FileManager.default.removeItem(at: url)
do {
let data = try JSONEncoder().encode(bonuses)
try data.write(to: url)
print("saveBonuses successful")
} catch {
print("Error saving bonuses to file:", error)
}
}
func loadBonusesFromFile(_ url: URL) -> [JsonFile.JsonBonuses]? {
do {
let data = try Data(contentsOf: url)
let bonuses = try JSONDecoder().decode([JsonFile.JsonBonuses].self, from: data)
print("loadBonusesFromFile successful")
return bonuses
} catch {
print("Error loading bonuses from file:", error)
return nil
}
}
func loadBonuses(completion: #escaping ([JsonFile.JsonBonuses]?) -> Void) {
let localBonusesURL = try! FileManager.default
.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("Bonuses.json")
downloadJSON { bonuses in
if let bonuses = bonuses {
completion(bonuses)
self.saveBonuses(bonuses, to: localBonusesURL)
} else {
completion(self.loadBonusesFromFile(localBonusesURL))
}
}
}
func searchBarIsEmpty() -> Bool {
// Returns true if the text is empty or nil
return searchController.searchBar.text?.isEmpty ?? true
}
func filterContentForSearchText(_ searchText: String, scope: String = "All") {
filteredBonuses = bonuses.filter({( bonus: JsonFile.JsonBonuses) -> Bool in
return bonus.state.lowercased().contains(searchText.lowercased())
})
tableView.reloadData()
}
func isFiltering() -> Bool {
return searchController.isActive && !searchBarIsEmpty()
}
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? BonusDetailViewController {
destination.bonus = bonuses[(tableView.indexPathForSelectedRow?.row)!]
}
}
}
extension BonusListViewController: UISearchResultsUpdating {
// MARK: - UISearchResultsUpdating Delegate
func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchController.searchBar.text!)
}
}
and here is the JsonFile.swift:
import Foundation
struct JsonFile: Codable {
struct Meta: Codable {
let fileName: String
let version: String
}
struct JsonBonuses: Codable {
let bonusCode: String
let category: String
let name: String
let value: Int
let city: String
let state: String
let flavor: String
let imageName: String
}
let meta: Meta
let bonuses: [JsonBonuses]
}
EDIT: The JSON itself can be found at http://www.tourofhonor.com/BonusData.json
Also, on the line that says let bonusSet: JsonFile.JsonBonuses (under the cellForRowAt), I'm getting a warning that says "Immutable value bonusSet was never used; consider removing it" even though I use it in the very next line.
I guess the issue is in your cellForRow method, you are supposed to assignv alues with bonusSet and not bonus. as you are initializing the value from bonus dara structure which should be from bonusSet.
Try changing cellForRow as:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "BonusListViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? BonusListViewCell else {
fatalError("The dequeued cell is not an instance of BonusListViewCell.")
}
let bonus = bonuses[indexPath.row]
let bonusSet: JsonFile.JsonBonuses
if isFiltering() {
bonusSet = filteredBonuses[indexPath.row]
} else {
bonusSet = bonus
}
//CHANGE IS REQUIRED HERE: REPLACE THE bonus WITH bonusSet :
let urlString = "http://tourofhonor.com/appimages/"+(bonusSet.imageName)
let url = URL(string: urlString)
cell.primaryImage.downloadedFrom(url: url!)
cell.nameLabel.text = bonusSet.name.capitalized
cell.bonusCodeLabel.text = bonusSet.bonusCode.localizedUppercase
cell.categoryLabel.text = bonusSet.category
cell.valueLabel.text = "\(bonusSet.value)"
cell.cityLabel.text = "\(bonusSet.city.capitalized),"
cell.stateLabel.text = bonusSet.state.localizedUppercase
return cell
}
The problem is with your cell for row index path
search result you are getting from the filterContentForSearchText you are storing in filteredBonuses but in cellForRowAt you are still setting all your values from
bouns variable
bonus = bonuses[indexPath.row]
if isFiltering() {
bonusSet = filteredBonuses[indexPath.row] //even though you are creating bonusSet you are not using it while setting cell values below so use that bonusSet
} else {
bonusSet = bonus
}
//Like this
let urlString = "http://tourofhonor.com/appimages/"+(bonusSet.imageName)
let url = URL(string: urlString)
cell.primaryImage.downloadedFrom(url: url!)
cell.nameLabel.text = bonus.name.capitalized
cell.bonusCodeLabel.text = bonusSet.bonusCode.localizedUppercase
cell.categoryLabel.text = bonusSet.category
cell.valueLabel.text = "\(bonusSet.value)"
cell.cityLabel.text = "\(bonusSet.city.capitalized),"
cell.stateLabel.text = bonusSet.state.localizedUppercase
This code is useless:
let bonusSet: JsonFile.JsonBonuses
if isFiltering() {
bonusSet = filteredBonuses[indexPath.row]
} else {
bonusSet = bonus
}
You create a local variable bonusSet whose value depends on whether you are filtering; but, as the compiler rightly observes, nothing you do afterwards uses it. Your code thus behaves exactly the same way regardless of whether you are filtering.

Swift / Firebase Tableview cells not refreshing after child removed

I realize this is a bit of a mess but i'm learning how things work by piecing things together from tutorials and reference materials.
My issue is when I remove a node from firebase, the tableview doesnt update. It's deleted from firebase but the cell remains in the tableview. Although not shown below, i've tried adding the mytableview.reload block of code every where I thought it should go, but I've had no success. Any input is appreciated.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return eventsList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = myTableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! ViewControllerTableViewCell
cell.locationLabel.text = eventsList[indexPath.row].location
cell.eventTimeLabel.text = eventsList[indexPath.row].dateTime
cell.eventTypeLabel.text = eventsList[indexPath.row].agencyEventSubtypeCode
cell.agencyIdLabel.text = eventsList[indexPath.row].agencyId
if eventsList[indexPath.row].alarmLevel == "1" {
cell.locationLabel.textColor = UIColor.red
cell.eventTimeLabel.textColor = UIColor.red
cell.eventTypeLabel.textColor = UIColor.red
cell.agencyIdLabel.textColor = UIColor.red
}
return cell
}
ref = Database.database().reference()
fetchData()
}
func fetchData(){
refHandle = ref?.child("caddata").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: Any] {
let event = Events()
event.setValuesForKeys(dictionary)
if event.originatingAction == "CadEventNew" {
self.eventsList.append(event)
DispatchQueue.main.async {
self.myTableView.reloadData()
}
} else if event.originatingAction == "CadCloseEvent" {
self.cadPacketType = event.originatingAction
self.cadEventNumber = event.agencyEventId
self.queryCloseUIDFromDB()
}
}
})
}
func queryCloseUIDFromDB(){
if cadPacketType == "CadCloseEvent" {
let dataRef = ref?.child("caddata")
let queryRef = dataRef?.queryOrdered(byChild: "agencyEventId")
.queryEqual(toValue: cadEventNumber)
queryRef?.observeSingleEvent(of: .value, with: { (snapshot) in
for snap in snapshot.children {
let dataSnap = snap as! DataSnapshot
self.closeUID = dataSnap.key //the uid of each event
if self.closeUID != nil {
self.deleteFromDB()
DispatchQueue.main.async {
self.myTableView.reloadData()
}
}
}
})
}
}
func deleteFromDB () {
if closeUID != nil {
print (cadEventNumber!)
print (closeUID!)
ref?.child("caddata").child(closeUID!).removeValue(completionBlock: { (error, ref) in
if error != nil {
self.fetchData()
print("Error: \(String(describing: error))")
return
}
})
}
print ("\(String(describing: closeUID)) has been removed")
}
You need to fetch the data again after delete,
replace your deleteFromDB method by this one, your fetchData must be executed if error == nil
func deleteFromDB () {
if closeUID != nil {
print (cadEventNumber!)
print (closeUID!)
ref?.child("caddata").child(closeUID!).removeValue(completionBlock: { (error, ref) in
if error != nil {
print("Error: \(String(describing: error))")
return
}
//clean the array
self.eventsList.removeAll()
self.fetchData()
})
}
print ("\(String(describing: closeUID)) has been removed")
}

Why do the cells in table view duplicate when user clicks on accept button?

I am working on an app in which a user can send a friend request to an another user, and the user who receives a request can either accept it or delete it. It's working as intended. However, when a user clicks on accept button, the cells of tableView(which shows received requests) duplicates. And when I reload the table view or restarts the app, it shows desired results, that is, only showing pending requests. Also, data gets stored on firebase database as intended. Only concern is the spontaneity and error behind duplicating cells initially. Also, how can I customize cells, such that whenever the user clicks on accept or delete button, only that cell gets dimmed, then removed from the table?
EDIT:
I have tried using viewDidLoad in place of viewDidAppear too!
Code for received request function:
override func viewDidAppear(_ animated: Bool) {
receivedFunc()
refresher = UIRefreshControl()
refresher.attributedTitle = NSAttributedString(string: "Pull to refresh")
refresher.addTarget(self, action: #selector(ReceivedViewController.receivedFunc), for: UIControlEvents.valueChanged)
receivedTable.addSubview(refresher)
}
func receivedFunc() {
//
ref = Database.database().reference()
let currentUserID = Auth.auth().currentUser?.uid
refHandle = ref.child("users").child(currentUserID!).observe(.value, with: { (snapshot) in
if let userDict = snapshot.value as? [String: Any] {
if let requests = userDict["requests"] as? [String: String] {
print("processing requests")
self.userIDs.removeAll()
self.userNames.removeAll()
self.userImages.removeAll()
for request in requests {
if request.value == "received" {
self.refHandle = self.ref.child("users").child(request.key).observe(.value, with: { (snapshot) in
if let userDict = snapshot.value as? [String: Any] {
if let userName = userDict["name"] {
let storage = Storage.storage()
let storageRef = storage.reference(forURL: "gs://money-owelend.appspot.com")
let profilePhotoRef = storageRef.child(request.key + "profile_photo.jpg")
profilePhotoRef.getData(maxSize: 1 * 1024 * 1024) { data, error in
if let error = error {
print("couldn't download the image")
} else {
self.userNames.append(userName as! String)
self.userIDs.append(request.key)
self.userImages.append(UIImage(data: data!)!)
//
self.receivedTable.reloadData()
self.refresher.endRefreshing()
}
}
}
}
}, withCancel: { (error) in
print("error observing value \(error)")
})
}
}
// reload data here
}
}
}, withCancel: { (error) in
print("error observing value \(error)")
})
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return userIDs.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CellReceived", for: indexPath) as! ReceivedTableViewCell
cell.hiddenIDLabel.text = userIDs[indexPath.row]
cell.userName.text = userNames[indexPath.row]
cell.userImage.image = userImages[indexPath.row]
return cell
}
And Finally, code for cells:
#IBAction func acceptButton(_ sender: Any) {
// add the user to current user's friend list
ref = Database.database().reference()
let currentUserRef = ref.child("users").child(currentUserID!)
currentUserRef.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.hasChild("friends"){
self.ref.child("users").child(self.currentUserID!).child("friends").updateChildValues([self.hiddenIDLabel.text!: "true"])
} else{
self.ref.child("users").child(self.currentUserID!).child("friends").setValue([self.hiddenIDLabel.text!: "true"])
}
print("user successfully added as a friend to current user's list")
})
// remove this request from user's received requests
let currentUserDeleteRef = ref.child("users").child(currentUserID!)
currentUserDeleteRef.observeSingleEvent(of: .value, with: { (snapshot) in
self.ref.child("users").child(self.currentUserID!).child("requests").child(self.hiddenIDLabel.text!).removeValue()
})
// add the current user to the user's friend list
let senderRef = ref.child("users").child(hiddenIDLabel.text!)
senderRef.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.hasChild("friends"){
self.ref.child("users").child(self.hiddenIDLabel.text!).child("friends").updateChildValues([self.currentUserID!: "true"])
} else{
self.ref.child("users").child(self.hiddenIDLabel.text!).child("friends").setValue([self.currentUserID!: "true"])
}
print("user successfully added as a friend to sender's list")
})
// remove this request from sender's sent requests
let senderDeleteRef = ref.child("users").child(hiddenIDLabel.text!)
senderDeleteRef.observeSingleEvent(of: .value, with: { (snapshot) in
self.ref.child("users").child(self.hiddenIDLabel.text!).child("requests").child(self.currentUserID!).removeValue()
})
}
My firebase database

Resources