when the like button is tapped it should increase the number of likes on the cell thats related to it but im having an issue which it creates a totally new cell with the updated number of likes I believe the problem is coming from how im loading my cells I believe I also need to add the function remove() so it clears the old data after the snapshot listener takes effect but im not too sure
func loaddailymotivation() {
FirebaseReferece(.MotivationDAILY).addSnapshotListener { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) { // this line means if the chage that happened in the document was equal to added something
let data = diff.document.data()
print("we have\(snapshot.documents.count) documents in this array")
let dailyMotivationID = data["objectID"] as! String
let dailymotivationTitle = data["Motivation title"] as! String //calls the data thats heald inside of motivation title in firebase
let dailyMotivationScripture = data["daily motivation scripture"] as! String //calls the data thats heald inside of Motivation script in firebase
let dailyMotivationNumberOfLikes = data["Number of likes in daily motivation post"]as! Int
let MdataModel = motivationDailyModel(RealMotivationID: dailyMotivationID, RealmotivationTitle: dailymotivationTitle, RealmotivationScrip: dailyMotivationScripture, RealmotivationNumberOfLikes: dailyMotivationNumberOfLikes)
self.motivationThoughts.append(MdataModel)
}
//===== //=====
if (diff.type == .modified) {
print("Modified data: \(diff.document.data())")
let newdata = diff.document.data()
let dailyMotivationID = newdata["objectID"] as! String
let dailymotivationTitle = newdata["Motivation title"] as! String //calls the data thats heald inside of motivation title in firebase
let dailyMotivationScripture = newdata["daily motivation scripture"] as! String //calls the data thats heald inside of Motivation script in firebase
let dailyMotivationNumberOfLikes = newdata["Number of likes in daily motivation post"]as! Int
let MdataModel = motivationDailyModel(RealMotivationID: dailyMotivationID, RealmotivationTitle: dailymotivationTitle, RealmotivationScrip: dailyMotivationScripture, RealmotivationNumberOfLikes: dailyMotivationNumberOfLikes)
self.motivationThoughts.append(MdataModel)
// here you will receive if any change happens in your data add it to your array as you want
}
DispatchQueue.main.async {
self.tableview.reloadData()
}
}
}
}
Both branches do exactly the same.
In the modified branch you have to get the corresponding object in motivationThoughts, update it and put it back, rather than creating a new object.
Something like
if diff.type == .modified {
print("Modified data: \(diff.document.data())")
let newdata = diff.document.data()
let objectID = newdata["objectID"] as! String
guard let dailymotivationIndex = motivationThoughts.firstIndex(where: {$0.dailyMotivationID == objectID}) else { return }
var dailymotivation = self.motivationThoughts[dailymotivationIndex]
let dailyMotivationNumberOfLikes = newdata["Number of likes in daily motivation post"] as! Int
dailymotivation.RealmotivationNumberOfLikes = dailyMotivationNumberOfLikes
self.motivationThoughts[dailymotivationIndex] = dailymotivation
}
And I recommend to reload only the row in the modified branch rather than the entire table view.
Related
I am wanting to capture all the values in my childByAutoId in firebase. Essentially, it stores all the items that a person has shortlisted. However, I do not seem to be capturing this, and I assume it is because I am not calling the snapshot correctly to factor the auto id's.
Database:
userID
-> Favourited
-> Auto Id
-> itemName: x
-> Auto Id
-> itemName: x
-> Auto Id
-> itemName: x
Code:
func retrieveItems() {
guard let userId = Auth.auth().currentUser?.uid else { return }
let ref = Database.database().reference().child("users/\(userId)/Favourited")
ref.observe(.value, with: { (snapshot) in
if snapshot.childrenCount>0 {
self.favUsers.removeAll()
for likes in snapshot.children.allObjects as! [DataSnapshot] {
let likesObject = likes.value as? [String: AnyObject]
let itemName = likesObject!["itemName"]
let likesList = Names(id: likes.key, itemName: itemName as! String?)
self.favUsers.append(likesList)
}
} else {
print("not yet")
}
})
self.favList.reloadData()
}
Could someone have a look and let me know what I may be doing wrong? Thank you!
This happens because Firebase loads data asynchronously, and right now you're calling reloadData before the self.favUsers.append(likesList) has ever run.
The call to reloadData needs to be inside the close/completion handler that is called when the data comes back from Firebase:
ref.observe(.value, with: { (snapshot) in
if snapshot.childrenCount>0 {
self.favUsers.removeAll()
for likes in snapshot.children.allObjects as! [DataSnapshot] {
let likesObject = likes.value as? [String: AnyObject]
let itemName = likesObject!["itemName"]
let likesList = Names(id: likes.key, itemName: itemName as! String?)
self.favUsers.append(likesList)
}
self.favList.reloadData() // 👈 Move this here
} else {
print("not yet")
}
})
I also recommend checking out some of these answers asynchronous data loading in Firebase.
I am trying to create a Listener for changes to a Document. When I change the data in Firestore (server) it doesn't update in the TableView (App). The TableView only updates when I reopen the App or ViewController.
I have been able to set this up for a Query Snapshot but not for a Document Snapshot.
Can anyone look at the code below to see why this is not updating in realtime?
override func viewDidAppear(_ animated: Bool) {
var newDocIDString = newDocID ?? ""
detaliPartNumberListerner = firestore.collection(PARTINFO_REF).document(newDocIDString).addSnapshotListener { documentSnapshot, error in
guard let document = documentSnapshot else {
print("Error fetching document: \(error!)")
return
}
guard let data = document.data() else {
print("Document data was empty.")
return
}
print("Current data: \(data)")
self.partInfos.removeAll()
self.partInfos = PartInfo.parseData2(snapshot: documentSnapshot)
self.issueTableView.reloadData()
}
In my PartInfo file
class func parseData2(snapshot: DocumentSnapshot?) -> [PartInfo] {
var partNumbers = [PartInfo]()
guard let snap = snapshot else { return partNumbers }
//for document in snap.documents {
// let data = document.data()
let area = snapshot?[AREA] as? String ?? "Not Known"
let count = snapshot?[COUNT] as? Int ?? 0
//let documentId = document.documentID
let documentId = snapshot?.documentID ?? ""
let newPartInfo = PartInfo(area: area, count: count, documentId: documentId)
partNumbers.append(newPartInfo)
return partNumbers
}
UI work must always be done on the main thread. So instead of your last line in your first code snippet, do this:
DispatchQueue.main.async {
self.issueTableView.reloadData()
}
I think this might be the solution to your problem. (A little late, I know ...)
I am a new swift developer. I am using Swift 4.2 and Xcode 10.2.
I need my UI to wait until a method has finished so I can use the result to display a balance. I am trying to use a dispatchGroup for this, but it does not appear to be waiting because the value of user?.userId below is nil. Here is my code:
// Load the local user data. Must wait until this is done to continue.
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
let user = LocalStorageService.loadCurrentUser()
dispatchGroup.leave()
// Display the current balance.
// Get a reference to the Firestore database.
let db = Firestore.firestore()
// Make sure we have a userId and then update the balance with a listener that keeps it updated.
// Only run this part when the dispatchGroup has completed (in this case, the user is loaded).
dispatchGroup.notify(queue: .main) {
if let userId = user?.userId {
db.collection("subs").whereField("ID", isEqualTo: userId)
.addSnapshotListener { querySnapshot, error in
// Make sure we have a document
guard let document = querySnapshot?.documents.first else {
print("Error fetching document: \(error!)")
return
}
// We have a document and it has data. Use it.
self.balance = document.get("balance") as! Double
// Format the balance
let currencyFormatter = NumberFormatter()
currencyFormatter.numberStyle = .currency
let balanceString = currencyFormatter.string(from: self.balance as NSNumber)
self.balanceLabel.setTitle(balanceString, for: .normal)
}
}
}
How can I make the UI wait until the method called in dispatchGroup.enter() has completed?
Here's what's in LoadCurrentUser....
static func loadCurrentUser() -> User? {
// Loads the current user in the UserDefaults if there is one
let defaults = UserDefaults.standard
let userId = defaults.value(forKey: Constants.LocalStorage.storedUserId) as? String
let phoneNumber = defaults.value(forKey: Constants.LocalStorage.storedPhoneNumber) as? String
let subscriberId = defaults.value(forKey: Constants.LocalStorage.storedDocumentId) as? String
guard userId != nil && phoneNumber != nil && subscriberId != nil else {
return nil
}
// Return the user
let u = User(userId:userId!, phoneNumber:phoneNumber!, subscriberId: subscriberId)
return u
}
Currently you do it correctly by setting vars inside the callback so no need for DispatchGroup , but to correctly use it then do ( notice the correct place where each line should be by numbers from 1 to 4 )
let dispatchGroup = DispatchGroup() /// 1
let user = LocalStorageService.loadCurrentUser()
// Display the current balance.
// Get a reference to the Firestore database.
let db = Firestore.firestore()
var balance = ""
// Make sure we have a userId and then update the balance with a listener that keeps it updated.
// Only run this part when the dispatchGroup has completed (in this case, the user is loaded).
if let userId = user?.userId {
dispatchGroup.enter() /// 2
db.collection("subs").whereField("ID", isEqualTo: userId)
.addSnapshotListener { querySnapshot, error in
// Make sure we have a document
guard let document = querySnapshot?.documents.first else {
print("Error fetching document: \(error!)")
return
}
// We have a document and it has data. Use it.
self.balance = document.get("balance") as! Double
dispatchGroup.leave() /// 3
}
}
dispatchGroup.notify(queue: .main) { /// 4
//update here
// Format the balance
let currencyFormatter = NumberFormatter()
currencyFormatter.numberStyle = .currency
let balanceString = currencyFormatter.string(from: self.balance as NSNumber)
self.balanceLabel.setTitle(balanceString, for: .normal)
}
I tried with this code to sort my posts by timestamp it doesn't work, each time I launch the simulator the order of the cells is different, I suppose this isn't the way to do it, could somebody explain me where I am wrong...
I edited the code, now my problem is that the most recent posts are displayed at the bottom and I would like them to to be displayed at the top
self.user.removeAll()
for child in DataSnapshot.children.allObjects as! [DataSnapshot] {
print("Processing user \(child.key)")
let value = child.value as? NSDictionary
//if country == "UNITED STATES"{
if let uid = value?["userID"] as? String{
if uid != Auth.auth().currentUser!.uid {
//
let userToShow = User()
if let fullName = value?["username"] as? String , let imagePath = value?["photoURL"] as? String{
userToShow.username = fullName
userToShow.imagePath = imagePath
userToShow.userID = uid
self.user.append(userToShow)
}
}
}
}
As soon as you call DataSnapshot.value, you're converting the data in the snapshot into a dictionary. And the order if keys in that dictionary is not guaranteed.
To maintain the order of the elements as they come back from the database, you need to loop over DataSnapshot.children. See these questions for examples of how to do that:
Iterate over snapshot children in Firebase
post on the firebase-talk mailing list
For your code this would look something like:
ref.child("users").queryOrdered(byChild: "timestamp").observeSingleEvent(of: .value, with: { snapshot in
self.user.removeAll()
for child in snapshot.children.allObjects as [DataSnapshot] {
print("Processing user \(child.key)")
let value = child.value as? NSDictionary
if let uid = value["userID"] as? String {
...
}
}
self.tableview.reloadData()
})
I am currently trying to fetch all the followers for a specific user with firebase. In my didSet clause, I call the function setFollowingCount() to fetch the users that the current user follows and assign it to a text field:
var user: User? {
didSet {
setFollowingCount()
guard let following = self.user?.following else {return}
let attributedText = NSMutableAttributedString(string: "\(following)\n", attributes: [NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 14)])
attributedText.append(NSAttributedString(string: "followers", attributes: [NSAttributedStringKey.foregroundColor: UIColor.lightGray, NSAttributedStringKey.font: UIFont.systemFont(ofSize: 14)]))
self.followingLabel.attributedText = attributedText
}
}
The setFollowingCount() function is:
func setFollowingCount(){
var i = 0
guard let userId = self.user?.uid else { return }
Database.database().reference().child("following").child(userId).observe(.value) { (snapshot) in
self.user?.following = Int(snapshot.childrenCount)
}
}
The problem is that this takes very long to load and often freezes the entire app when you look at a user's profile. How can I speed this up or make it work more efficiently?
self.user?.following = Int(snapshot.childrenCount)
Is not an efficient solution. .childrenCount actually loops over the snapshot and counts all of the children which is going to be slow.
Instead you want to store the number of followers as a single value you can retrieve it faster.
following: {
uid: {
followingCount: 100,
follwersCount: 150
}
}
Then you can query like this:
Database.database().reference().child("following").child(userId).observeSingleEvent(of: .value) { (snapshot) in
if let counts = snap.value as? [String: AnyObject] }
let followingCount = counts["followingCount"] as? Int
let followersCount = counts["followersCount"] as? Int
// save these values somewhere
}
})
I would also recommend you increment / decrement the follower counts in a transaction block so the count doesn't get messed up. That can look something like this:
static func incrementCount(countName: String) {
if let uid = Auth.auth().currentUser?.uid {
let databaseReference = Database.database().reference()
databaseReference.child("following").child(uid).runTransactionBlock { (currentData: MutableData) -> TransactionResult in
if var data = currentData.value as? [String: Any] {
var count = data[countName] as! Int
count += 1
data[countName] = count
currentData.value = data
return TransactionResult.success(withValue: currentData)
}
return TransactionResult.success(withValue: currentData)
}
}
}
Lastly,
If you're going to use .observe you need to remove the reference. In this case though you aren't looking for updates so you can use .observeSingleEvent