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()
}
}
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)
}
})
}
}
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.
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
}
}
Ok, so my text label should display the name of the user, but the codes runs before the user data is fetched and the label doesn't change when the data is loaded.
First time i print the users name i get nil, but when i print within the firebase call i get the users name. how can i do this so i don't have to change the label with in the firebase call?
var ref: FIRDatabaseReference!
var user: User!
override func viewDidLoad() {
super.viewDidLoad()
ref = FIRDatabase.database().reference()
fetchUser()
self.titleLabel.text = user?.name
// Returns nil
print(user?.name)
}
func fetchUser() {
ref.child("users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let name = value?["name"] as? String ?? ""
let birthdate = value?["birthdate"] as? String ?? ""
let gender = value?["gender"] as? String ?? ""
self.user = User(name: name, birthdate: birthdate, gender: gender)
// Now i get the users name
print(self.user.name)
}) { (error) in
print(error.localizedDescription)
}
}
If you do not want to access the label from fetchUser, you can use a simple callback.
override func viewDidLoad() {
//viewDidLoad code
fetchUser() {
self.titleLabel.text = user?.name
}
}
func fetchUser(_ completion: #escaping () -> Void) {
ref.child("users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
//fetchUser code
// Now i get the users name
print(self.user.name)
completion()
}) { (error) in
print(error.localizedDescription)
}
}