I'm having trouble with loading the table view. The moment I call self.messagesTable.reloadData() my local tableView does not reload the data deleted from a external Firebase server.
These two are my handlers for the observer:
fileprivate var _refHandle: FIRDatabaseHandle!
fileprivate var _refHandleDel: FIRDatabaseHandle!
Outlet
#IBOutlet weak var messagesTable: UITableView!
Code in FCViewController:
var ref: FIRDatabaseReference!
var messages: [FIRDataSnapshot]! = []
var messageClass = [Message]()
var messageDictionary = [String: Message]()
func configureDatabase() {
// configure database to sync messages and connect it to the data base starting at "/"
ref = FIRDatabase.database().reference()
// create a listener on what happend on the database
_refHandle = ref.child("messages").observe(.childAdded) { (snapshot: FIRDataSnapshot!) in
self.messages.append(snapshot)
// Animate and scroll the new message loaded
self.messagesTable.insertRows(at: [IndexPath(row: self.messages.count - 1, section: 0)], with: .automatic)
self.scrollToBottomMessage()
}
_refHandleDel = ref.child("messages").observe(.childRemoved, with: { (snapshot: FIRDataSnapshot!) in
self.messageDictionary.removeValue(forKey: snapshot.key)
// MY PROBLEM IS HERE: The table does not load properly and does not reloadData from the server after deleting the snapshot.key
self.messagesTable.reloadData()
}, withCancel: nil)
}
deinit {
// set up what needs to be deinitialized when view is no longer being used
// we remove the observer that is all the time checking for updates in the .observer handler
ref.child("messages").removeObserver(withHandle: _refHandle)
ref.child("messages").removeObserver(withHandle: _refHandleDel)
// we also remove the listener for auth changes when the user registers
FIRAuth.auth()?.removeStateDidChangeListener(_authHandle)
}
Scroll Messages Function also inside the FCViewController:
func scrollToBottomMessage() {
if messages.count == 0 { return }
let bottomMessageIndex = IndexPath(row: messagesTable.numberOfRows(inSection: 0) - 1, section: 0)
messagesTable.scrollToRow(at: bottomMessageIndex, at: .bottom, animated: true)
}
Message Class Object in a separate .swift file courtesy of #Jay
class Message: NSObject {
class MessageClass {
var key = ""
var name = ""
var text = ""
var timestamp = ""
}
var messagesArray = [MessageClass]()
let ref = FIRDatabase.database().reference()
func readInAllMessages() {
let messagesRef = ref.child("messages")
messagesRef.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let snap = child as! FIRDataSnapshot
let msg = self.snapToMsgClass(child: snap)
self.messagesArray.append(msg)
}
})
}
func addRemoveObserver() {
let messagesRef = ref.child("messages")
messagesRef.observe(.childRemoved, with: { snapshot in
let keyToRemove = snapshot.key
if let i = self.messagesArray.index(where: { $0.key == keyToRemove }) {
self.messagesArray.remove(at: i)
}
})
}
func snapToMsgClass(child: FIRDataSnapshot) -> MessageClass {
let dict = child.value as! [String:Any]
let name = dict["name"] as! String
let msg = MessageClass()
msg.name = name
msg.key = child.key
return msg
}
}
Related
I'm trying to get a list of images from my firebase database. Inside the observe method, if I print the number of posts it works correctly. If I print the number of posts outside the observe function, but still inside the fetchPosts() function, I get 0. If I print the number of posts after the fetchPosts() call (the function that uses observe), I get 0.
How can I save the values to my dictionary posts inside of this async call? I've tried completion and dispatch groups. I might not have implemented them correctly so if you see an easy way to do it then please help me out. Here is the code:
import UIKit
import SwiftUI
import Firebase
import FirebaseUI
import SwiftKeychainWrapper
class FeedViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource{
#IBOutlet weak var collectionview: UICollectionView!
//var posts = [Post]()
var posts1 = [String](){
didSet{
collectionview.reloadData()
}
}
var following = [String]()
//var posts1 = [String]()
var userStorage: StorageReference!
var ref : DatabaseReference!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
fetchPosts()
}
// func lengthyTask(completionHandler: (Int) -> Int)
// {
// let result = completionHandler(42)
// print(result)
// }
//
// lengthyTask(completionHandler: { number in
// print(number)
// return 101
// })
//
func fetchPosts() {
let uid = Auth.auth().currentUser!.uid
let ref = Database.database().reference().child("posts")
let uids = Database.database().reference().child("users")
uids.observe(DataEventType.value, with: { (snapshot) in
let dict = snapshot.value as! [String:NSDictionary]
for (_,value) in dict {
if let uid = value["uid"] as? String{
self.following.append(uid)
}
}
ref.observe(DataEventType.value, with: { (snapshot2) in
let dict2 = snapshot2.value as! [String:NSDictionary]
for(key, value) in dict{
for uid2 in self.following{
if (uid2 == key){
for (key2,value2) in value as! [String:String]{
//print(key2 + "this is key2")
if(key2 == "urlToImage"){
let urlimage = value2
//print(urlimage)
self.posts1.append(urlimage)
self.collectionview.reloadData()
print(self.posts1.count)
}
}
}
}
}
})
self.collectionview.reloadData()
})
//ref.removeAllObservers()
//uids.removeAllObservers()
print("before return")
print(self.posts1.count)
//return self.posts1
}
func numberOfSections(in collectionView: UICollectionView) ->Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return posts1.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PostCell", for: indexPath) as! PostCell
cell.postImage.sd_setImage(with: URL(string: posts1[indexPath.row]))
//creating the cell
//cell.postImage.downloadImage(from: self.posts[indexPath.row])
// let storageRef = Storage.storage().reference(forURL: self.posts[indexPath.row].pathToImage)
//
//
print("im trying")
//let stickitinme = URL(fileURLWithPath: posts1[0])
//cell.postImage.sd_setImage(with: stickitinme)
//cell.authorLabel.text = self.posts[indexPath.row].author
//cell.likeLabel.text = "\(self.posts[indexPath.row].likes) Likes"
return cell
}
#IBAction func signOutPressed(_sender: Any){
signOut()
self.performSegue(withIdentifier: "toSignIn", sender: nil)
}
#objc func signOut(){
KeychainWrapper.standard.removeObject(forKey:"uid")
do{
try Auth.auth().signOut()
} catch let signOutError as NSError{
print("Error signing out: %#", signOutError)
}
dismiss(animated: true, completion: nil)
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
You need only a few slightly changes
Declare posts1 simply
var posts1 = [String]()
and remove the property observer didSet
Delete the line self.collectionview.reloadData() right after self.posts1.append(..
Move the last occurrence of self.collectionview.reloadData() one level up, wrap it in a DispatchQueue block to update the collection view on the main thread and delete the print lines after the outer closure
}
DispatchQueue.main.async {
self.collectionview.reloadData()
}
})
})
}
And there is a typo in the second closure. It must be
let dict2 = snapshot2.value as! [String:NSDictionary]
for(key, value) in dict2 {
Variable names with trailing indices are pretty error-prone, better would be for example userDict and postDict
Edit :
This is the code with the order of execution
override func viewDidLoad() {
super.viewDidLoad()
collectionview.dataSource = self
collectionview.delegate = self
// 1
fetchPosts()
// 5
}
func fetchPosts() {
// 2
let uid = Auth.auth().currentUser!.uid
let ref = Database.database().reference().child("posts")
let uids = Database.database().reference().child("users")
// 3
uids.observe(DataEventType.value, with: { (snapshot) in
// 6
let dict = snapshot.value as! [String:NSDictionary]
for (_,value) in dict {
if let uid = value["uid"] as? String{
self.following.append(uid)
}
}
// 7
ref.observe(DataEventType.value, with: { (snapshot2) in
// 9
let dict2 = snapshot2.value as! [String:NSDictionary]
for(key, value) in dict2 { // TYPO!!!!
for uid2 in self.following{
if (uid2 == key){
for (key2,value2) in value as! [String:String]{
//print(key2 + "this is key2")
if(key2 == "urlToImage"){
let urlimage = value2
//print(urlimage)
self.posts1.append(urlimage)
print(self.posts1.count)
}
}
}
}
}
DispatchQueue.main.async {
// 11
self.collectionview.reloadData()
}
// 10
})
// 8
})
// 4
}
I am doing a call into a child "following" and am seeing if the logged-in user's UID is there and has a child of another user which the logged-in user is following.
I am printing who the logged-in user is following into a tableview. The first problem is my code, because I know it is bad practice to have two firebase calls within each other so I need someone to teach me a better method. Because of the poor code, when I go unfollow the other user and come back to the tab where the logged-in users list of who they are following is displayed it shows this (image below). When the logged-in user is following nobody it should just display the "Sorry!" text, yet still keeps who the user was following. Need someone to teach me a better method for doing this type of firebase call. Code and a firebase JSON stack image are below... In the firebase JSON stack image, the expanded UID is the logged-in user and the child in is the other user the logged-in user is following. I need a better way to call and extract this information, I am just ignorant of how-to.
func getFollowingData() {
Database.database().reference().child("following").child(uid!).observe(DataEventType.value, with: { (snapshot) in
if snapshot.exists() {
print("Got Snapshot")
Database.database().reference().child("following").child(self.uid!).observe(.childAdded, with: { (snapshot) in
if snapshot.exists() {
print(snapshot)
let snapshot = snapshot.value as? NSDictionary
self.listFollowing.append(snapshot)
self.followingTableView.insertRows(at: [IndexPath(row:self.listFollowing.count-1,section:0)], with: UITableViewRowAnimation.automatic)
self.followingTableView.backgroundView = nil
}
})
} else {
print("No Snapshot")
self.followingTableView.backgroundView = self.noDataView
}
})
}
Figured it out, just needed to do it how I did it before on other feeds.
import UIKit
import Firebase
class BusinessFollowing: UITableViewController {
#IBOutlet var noDataView: UIView!
#IBOutlet var followingTableView: UITableView!
var yourFollowing = [Information]()
var listFollowing = [NSDictionary?]()
var databaseRef = Database.database().reference()
let uid = Auth.auth().currentUser?.uid
var loggedInUser = Auth.auth().currentUser
var loggedInUserData:NSDictionary?
var following = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.followingTableView.backgroundView = nil
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.followingTableView.reloadData()
self.yourFollowing.removeAll()
self.following.removeAll()
getFollowingData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if segue.identifier == "following" {
// gotta check if we're currently searching
if let indexPath = followingTableView.indexPathForSelectedRow {
let user = listFollowing[indexPath.row]
let controller = segue.destination as? ExploreBusinessProfileSwitchView
controller?.otherUser = user
}
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return self.yourFollowing.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! BusinessFollowingCell
let following = yourFollowing[indexPath.row]
let businessName = following.businessName
let businessStreet = following.businessStreet
let businessCity = following.businessCity
let businessState = following.businessState
cell.businessName.text = businessName
cell.businessStreet.text = businessStreet
cell.businessCity.text = businessCity
cell.businessState.text = businessState
// cell.businessName?.text = self.listFollowing[indexPath.row]?["businessName"] as? String
// cell.businessStreet?.text = self.listFollowing[indexPath.row]?["businessStreet"] as? String
// cell.businessCity?.text = self.listFollowing[indexPath.row]?["businessCity"] as? String
// cell.businessState?.text = self.listFollowing[indexPath.row]?["businessState"] as? String
return cell
}
func getFollowingData() {
self.yourFollowing.removeAll()
self.following.removeAll()
self.followingTableView.reloadData()
Database.database().reference().child("Businesses").child((loggedInUser?.uid)!).child("following").observe(.value, with: { snapshot in
if snapshot.exists() {
MBProgressHUD.showAdded(to: self.view, animated: true)
let databaseRef = Database.database().reference()
databaseRef.child("Businesses").queryOrderedByKey().observeSingleEvent(of: .value, with: { (usersSnapshot) in
let users = usersSnapshot.value as! [String: AnyObject]
for (_, value) in users {
if let userID = value["uid"] as? String {
if userID == Auth.auth().currentUser?.uid {
print(value)
if let followingUsers = value["following"] as? [String : String] {
for (_,user) in followingUsers {
self.following.append(user)
}
}
databaseRef.child("following").queryOrderedByKey().observeSingleEvent(of: .value, with: { (postsSnapshot) in
let posts = postsSnapshot.value as! [String: AnyObject]
for (_, post) in posts {
for (_, postInfo) in post as! [String: AnyObject] {
if let followingID = postInfo["uid"] as? String {
for each in self.following {
if each == followingID {
guard let uid = postInfo["uid"] as! String? else {return}
guard let name = postInfo["businessName"] as! String? else {return}
guard let address = postInfo["businessStreet"] as! String? else {return}
guard let state = postInfo["businessState"] as! String? else {return}
guard let city = postInfo["businessCity"] as! String? else {return}
self.yourFollowing.append(Information(uid: uid, businessName: name, businessStreet: address, businessCity: city, businessState: state))
}
self.followingTableView.backgroundView = nil
self.followingTableView.reloadData()
}
}
}
}
MBProgressHUD.hide(for: self.view, animated: true)
}) { (error) in
print(error.localizedDescription)
}
}
}
}
})
} else {
print("Not following anyone")
self.followingTableView.backgroundView = self.noDataView
MBProgressHUD.hide(for: self.view, animated: true)
}
})
}
}
As the title says, I only want reloadtableview() to be called once. How do i approach this?
Here is my code:
var posts = [Post]() {
didSet {
posts.reverse()
self.tableView.reloadData()
}
}
func fetchData(){
currentQuery.observe(.value, with: { (snapshot) in
if let snapshot = snapshot.children.allObjects as? [DataSnapshot]{
var foundPosts = [Post]()
for snap in snapshot {
if let postDict = snap.value as? Dictionary<String, Any>{
let key = snap.key
let post = Post.init(postKey: key, postData: postDict)
foundPosts.append(post)
}
}
self.posts = foundPosts
geoQuery?.removeAllObservers()
}
})
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
fetchData()
// if I put "tableView.reloadData()" here, it wont load when the app opens for the first time
}
With this, the tableview is reloading everytime something is changed in the database. I want to only reload it once, but still have that observer there.
If I understood you right, the code below should do what you wanted. (Couldn't squeeze it into the comments)
var shouldReloadTableView = true
var posts = [Post]() {
didSet {
posts.reverse()
guard shouldReloadTableView else { return }
shouldReloadTableView = false
self.tableView.reloadData()
}
}
I'm using a FirebaseManager singleton class to fetch data from Firebase. I'm calling getAllCharts in my ViewController, which needs this data. I receive the data initially, but when I update my data in firebase it won't update the values in the app (getAllCharts() is never called).
FirebaseManager:
final class FirebaseManager {
static let sharedInstance = FirebaseManager()
private init() {}
private let reference = FIRDatabase.database().reference()
private var charts = [ChartRepresentable]()
func getAllCharts(completionHandler: ((_ charts: [ChartRepresentable]) -> ())? = nil) {
guard let path = FirebasePath.allCharts.path else {
return
}
guard charts.isEmpty else {
completionHandler?(charts)
return
}
reference.child(path).observe(.value, with: { (snapshot) in
if snapshot.hasChildren() {
var availableCharts = [ChartRepresentable]()
for child in snapshot.children {
guard let chartSnapshot = child as? FIRDataSnapshot else {
continue
}
switch chartSnapshot.key {
case "bar":
availableGraphs.append(BarChart(snapshot: chartSnapshot))
case "line":
availableGraphs.append(LineChart(snapshot: chartSnapshot))
default: break
}
}
self.charts = availableCharts
completionHandler?(self.charts)
}
})
}
}
ViewController:
class ViewController: UIViewController {
var setupCards: [SetupCard]?
override func viewDidLoad() {
super.viewDidLoad()
FirebaseManager.sharedInstance.getAllCharts { [weak self] (charts) in
self?.setupCards = [SetupCard(charts: charts)]
self?.setupView.collectionView.reloadData()
self?.setupView.pageControl.numberOfPages = self?.setupCards?.count ?? 0
}
}
How do I get the updates?
I am trying to save my Firebase Data in an array but the array count is everytime 0.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
getAllSkateShops()
}
func getAllSkateShops() {
ref = FIRDatabase.database().reference()
ref.child("Shops").observeSingleEvent(of: .value, with: { (snapshot) in
//var newShops: [SkateShop] = []
for item in snapshot.children {
let skateShopItem = SkateShop(snapshot: item as! FIRDataSnapshot)
self.shops.append(skateShopItem)
DispatchQueue.main.async(execute: {
print("OBSERVE SHOP COUNT: \(self.shops.count)")
})
}
})
}
And in the function viewDidLoad() is self.shop.count is zero but I need this array with all Shops.
I hope anybody can help me ;)
I had the same problem, idk why this works but in DispatchQueue.main.async do (edited):
func getAllSkateShops() {
ref = FIRDatabase.database().reference()
ref.child("Shops").observeSingleEvent(of: .value, with: { (snapshot) in
//var newShops: [SkateShop] = []
for item in snapshot.children {
let skateShopItem = SkateShop(snapshot: item as! FIRDataSnapshot)
self.shops.append(skateShopItem)
self.shops2.append(skateShopItem)
DispatchQueue.main.async(execute: {
var temporaryArray = self.shop2
self.shop = temporaryArray
})
}
})
}
If that doesn't work comment, I'll give another option that might work.