Swift / Firebase Tableview cells not refreshing after child removed - ios

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

Related

Swift user online status updates but it repeats on cells that is not supposed to

I'm working on a chat app and I configured a function to check if a user is online. I'm able to see if another user is active or not, but, the issue I'm having is that if I scroll down (I'm using a UITableView) other users show as active and they are not. I placed the code inside the UITableViewCell class. Any suggestions as to what could be the problem are greatly appreciated. Here is my code:
UITableViewCell
func configureHomeFeedCell(member: Member) {
profileImage.loadImage(with: member.imageURL)
profileName.text = "\(member.name)" + ", " + "\(member.age)"
checkUserOnlineStatus(with: member.documentId) { _ in }
}
func checkUserOnlineStatus(with userId: String, completion: #escaping(Bool) -> Void) {
let query = USERS_COLLECTION.document(userId).collection(IS_ONLINE)
query.getDocuments { (snapshot, error) in
if let error = error {
print("ERROR..\(error.localizedDescription)")
} else {
snapshot?.documents.forEach({ diff in
let isOnline = diff.get(USER_IS_ONLINE) as? Bool
self.onlineViewStatus.backgroundColor = isOnline == true ? .green : .red
completion(isOnline!)
})}}
query.addSnapshotListener { (snapshot, error) in
snapshot?.documentChanges.forEach { diff in
let isOnline = diff.document.get(USER_IS_ONLINE) as? Bool
if (diff.type == .modified) {
self.onlineViewStatus.backgroundColor = isOnline == true ? .green : .red
completion(isOnline!)
}}}
}
** cellForRowAt**
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let hCell = homeFeedTableView.dequeueReusableCell(withIdentifier: "HomeFeedCell", for: indexPath) as! HomeFeedCell
if !members.isEmpty {
hCell.configureHomeFeedCell(member: members[indexPath.row])
}
hCell.selectionStyle = .none
hCell.backgroundColor = UIColor.white
return hCell
}
Updated Solution
Service Class
This function is to update the user's presence when online or not and it's called inside SceneDelegate
static func isUserOnline(bool: Bool) {
guard let currentUID = Auth.auth().currentUser?.uid else { return }
if !currentUID.isEmpty {
let dictionary = [USER_IS_ONLINE: bool as Any,
USER_LASTEST_ONLINE: Date().timeIntervalSince1970 as Any]
USERS_COLLECTION.document(currentUID).getDocument { (document, error) in
if let error = error {
print("Error..\(error.localizedDescription)")
} else {
if document?.exists == true {
USERS_COLLECTION.document(currentUID).updateData(dictionary)
} else {
USERS_COLLECTION.document(currentUID).updateData(dictionary)
}}}}}
**SceneDelegate**
func sceneDidDisconnect(_ scene: UIScene) {
Service.isUserOnline(bool: false)
}
func sceneDidBecomeActive(_ scene: UIScene) {
Service.isUserOnline(bool: true)
}
func sceneWillResignActive(_ scene: UIScene) {
Service.isUserOnline(bool: false)
}
func sceneDidEnterBackground(_ scene: UIScene) {
Service.isUserOnline(bool: false)
}
**This function is to monitor the user activity**
static func checkUserOnlineStatus(with userId: String, completion: #escaping(Bool) -> Void) {
let query = USERS_COLLECTION.document(userId)
query.getDocument { (document, error) in
if let document = document, document.exists {
let isOnline = document.get(USER_IS_ONLINE) as? Bool ?? false
completion(isOnline)
}
}
query.addSnapshotListener { (document, error) in
if let document = document, document.exists {
let isOnline = document.get(USER_IS_ONLINE) as? Bool ?? false
completion(isOnline)
}}}
Calling the function inside the cell class
func configureHomeFeedCell(member: Member) {
Service.checkUserOnlineStatus(with: member.documentId) { isOnline in
self.onlineViewStatus.backgroundColor = isOnline == true ? .green : .red
} }

Network call is successful but the data not showing (swift)

I am trying to make a very simple app in MVVM and I must be missing something here but I can't figure it out. I have all the error handling in my NewsService class and I print success if all goes right and it receives the data. I get that success every time, the issue is the "print(articles)" are not printing anything at all.
class NewsTableViewModel {
var articles = [Article]() {
didSet {
print(articles)
}
}
func fetchNews() {
NewsService.shared.fetchNews { [weak self] articles in
guard let self = self else { return }
self.articles = articles
print(articles)
}
}
}
class NewsTableVC: UITableViewController, NewsTableViewModelDelegate {
private let reuseIdentifier = "ArticleCell"
private let newsTableVM = NewsTableViewModel()
// var article = [Article]() {
// didSet {
// DispatchQueue.main.async {
// self.tableView.reloadData()
// }
// }
// }
override func viewDidLoad() {
super.viewDidLoad()
newsTableVM.delegate = self
newsTableVM.fetchNews()
updateUI()
}
func updateUI() {
tableView.register(ArticleCell.self, forCellReuseIdentifier: reuseIdentifier)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// return article.count
return self.newsTableVM.articles.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! ArticleCell
// cell.articleTitleLabel.text = article[indexPath.row].title
// cell.articleDescriptionLabel.text = article[indexPath.row].description
cell.articleTitleLabel.text = newsTableVM.articles[indexPath.row].title
cell.articleDescriptionLabel.text = newsTableVM.articles[indexPath.row].description
return cell
}
}
struct Response: Codable {
let articles: [Article]
}
struct Article: Codable {
let title: String
let description: String
}
class NewsService {
static let shared = NewsService()
func fetchNews(completion: #escaping ([Article]) -> (Void)) {
if let urlString = URL(string: "") {
let task = URLSession.shared.dataTask(with: urlString) { data, response, error in
if let _ = error {
print("error")
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else { return }
guard let data = data else {
return
}
let decoder = JSONDecoder()
do {
print("success")
let articles = try decoder.decode(Response.self, from: data).articles
completion(articles)
} catch {
return
}
}
task.resume()
}
}
}
In my view controller viewDidLoad, I call NewsTableViewModel().fetchNews(). And here is the entire NewsTableViewModel class. Ignore the double use of print(articles), I'm just trying to figure out where it's going wrong.
you did not cover all the cases, put debug print at:
guard let self = self else {
print("self is nill")
return completion([])
}
and:
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
print("error: \(response)")
return completion([])
}
guard let data = data else {
print("error: data is nill")
return completion([])
}
and:
do {
print("success")
let articles = try decoder.decode(Response.self, from: data).articles
completion(articles)
} catch (let error){
print("catch an error: \(error)
completion([])
}
also put the completion([]) in the error cases instead of return only.

Filtered view is leading to incorrect detail view when tapped on, but correct detail view when swiped

I think this is my last question before I can finally 1.0 release this thing. Here's my last remaining "can't launch yet" issue: When I filter by state, the resulting rows are not opening the appropriate detail view.
For example, if I search for TX, I see the seven Texas bonuses. If I tap on the 3rd one, I end up in AK3 (which is the 3rd item in the list if it isn't being filtered). The weird part, is that my swipe action DOES know that I am on TX3. Only when tapping to go to the detail does it jump to the wrong bonus.
Here is the full UITableViewController page:
import UIKit
import os.log
import Foundation
class BonusListViewController: UITableViewController {
var bonuses = [JsonFile.JsonBonuses]()
var bonus: JsonFile.JsonBonuses?
var filteredBonuses = [JsonFile.JsonBonuses]()
var detailViewController: BonusDetailViewController? = nil
var riderNumToH:String = UserDefaults.standard.string(forKey: Constants.RiderData().riderNumToH) ?? "000"
var pillionNumToH:String = UserDefaults.standard.string(forKey: Constants.RiderData().pillionNumToH) ?? "000"
var emailDestinationToH:String = UserDefaults.standard.string(forKey: Constants.RallyData().emailDestinationToH) ?? "photos#tourofhonor.com"
struct Constants {
struct RiderData {
let riderNumToH = "riderNumToH"
let pillionNumToH = "pillionNumToH"
}
struct RallyData {
let emailDestinationToH = "emailDestinationToH"
}
}
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"
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
print("About to call loadBonuses")
loadBonuses { [weak self] bonuses in
self?.bonuses = bonuses ?? []
DispatchQueue.main.async {
self?.tableView.reloadData()
}
print("loadBonuses called")
}
// MARK: Set Rider Defaults to Initial Values.
let defaults = UserDefaults.standard
print("Setting initial defaults")
if riderNumToH == "" {
print("riderNumToH is blank")
defaults.set("000", forKey: Constants.RiderData().riderNumToH)
} else if riderNumToH == "000" {
print("riderNumToH is 000")
} else {
print("riderNumToH is custom")
}
if pillionNumToH == "" {
print("pillionNumToH is blank")
defaults.set("000", forKey: Constants.RiderData().pillionNumToH)
} else if pillionNumToH == "000" {
print("pillionNumToH is 000")
} else {
print("pillionNumToH is custom")
}
if emailDestinationToH == "" {
print("emailDestinationToH is blank")
defaults.set("photos#tourofhonor.com", forKey: Constants.RallyData().emailDestinationToH)
} else if emailDestinationToH == "photos#tourofhonor.com" {
print("emailDestinationToH is set to default")
} else {
print("emailDestinationToH has been customized")
}
}
// 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
}
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")
// Delete the created images
let bonus: JsonFile.JsonBonuses
if self.isFiltering() {
bonus = self.filteredBonuses[indexPath.row]
} else {
bonus = self.bonuses[indexPath.row]
}
print("Selected Bonus is \(bonus.bonusCode)")
let fileNameToDeletePri = "\(bonus.bonusCode)_1.jpg"
let fileNameToDeleteOpt = "\(bonus.bonusCode)_2.jpg"
var filePathPri = ""
var filePathOpt = ""
// Find documents directory on device
let dirs : [String] = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.allDomainsMask, true)
if dirs.count > 0 {
let dir = dirs[0] //documents directory
filePathPri = dir.appendingFormat("/" + fileNameToDeletePri)
filePathOpt = dir.appendingFormat("/" + fileNameToDeleteOpt)
print("Local path = \(filePathPri)")
print("Local path = \(filePathOpt)")
} else {
print("Could not find local directory to store file")
return
}
do {
let fileManager = FileManager.default
// Check if primary file exists
if fileManager.fileExists(atPath: filePathPri) {
// Delete file
try fileManager.removeItem(atPath: filePathPri)
} else {
print("Primary image does not exist")
}
// Check if optional file exists
if fileManager.fileExists(atPath: filePathOpt) {
// Delete file
try fileManager.removeItem(atPath: filePathOpt)
} else {
print("Optional image does not exist")
}
}
catch let error as NSError {
print("An error took place: \(error)")
}
tableView.reloadData()
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: JsonFile.JsonBonuses
if self.isFiltering() {
bonus = self.filteredBonuses[indexPath.row]
} else {
bonus = self.bonuses[indexPath.row]
}
// Set Primary Image
let documentsUrl = URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
let imgUrl = documentsUrl.appendingPathComponent(bonus.bonusCode + "_1.jpg")
if(FileManager.default.fileExists(atPath:imgUrl.path))
{
do
{
let data = try Data.init(contentsOf:imgUrl)
cell.primaryImage.image = UIImage.init(data:data)
}
catch {
print(error)
}
}
else
{
cell.primaryImage.image = #imageLiteral(resourceName: "DefaultImage")
}
cell.nameLabel.text = bonus.name
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)
self.defaults.set(posts.meta.version, forKey: "jsonVersion")
print("URLSession did not fail")
print("JSON Version Set to \(posts.meta.version)")
} catch {
print("Can't decode JSON: \(error)")
}
} 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("BonusData.json")
downloadJSON { bonuses in
if let bonuses = bonuses {
completion(bonuses)
self.saveBonuses(bonuses, to: localBonusesURL)
} else {
print("versions did not match")
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.localizedCaseInsensitiveContains(searchText)
})
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!)
}
}
The part of the code that should be filtering the view is identical in both the cellForRowAt and the swipeAction methods:
let bonus: JsonFile.JsonBonuses
if self.isFiltering() {
bonus = self.filteredBonuses[indexPath.row]
} else {
bonus = self.bonuses[indexPath.row]
}
Originally the cellForRowAt did not have the self entries, but I added those to see if it would resolve it, and it didn't.
I found the cause of the issue. Near the bottom of the UITableViewController I had the following:
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? BonusDetailViewController {
destination.bonus = bonuses[(tableView.indexPathForSelectedRow?.row)!]
}
}
This is where the issue was. I fixed it by adding the same if/else logic I used in my cellForRowAt and SwipeAction:
// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? BonusDetailViewController {
if self.isFiltering() {
destination.bonus = filteredBonuses[(tableView.indexPathForSelectedRow?.row)!]
} else {
destination.bonus = bonuses[(tableView.indexPathForSelectedRow?.row)!]
}
}
}

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

Pictures shifting randomly in UITableView after being retrieved from Firebase

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.

Resources