My app has an option to save posts for users to watch later. The code is:
func savedPost(for cell: FirstView) {
guard let indexPath = collectionView.indexPath(for: cell) else { return }
var post = self.posts[indexPath.item]
guard let currentuserId = Auth.auth().currentUser?.uid else { return }
let targetUid = post.user.uid
guard let postId = post.id else { return }
let ref = Database.database().reference().child("save_post").child(currentuserId).child(postId)
if post.hasSaved {
ref.removeValue { (err, _) in
if let _ = err {
showErr(info: NSLocalizedString("failtoUnsave", comment: ""), subInfo: tryLater)
return
}
post.hasSaved = false
self.posts[indexPath.item] = post
self.collectionView.reloadItems(at: [indexPath])
}
} else {
let values = ["userId": targetUid]
ref.updateChildValues(values) { (err, ref) in
if let _ = err {
showErr(info: NSLocalizedString("failtoSave", comment: ""), subInfo: tryLater)
}
post.hasSaved = true
self.posts[indexPath.item] = post
self.collectionView.reloadItems(at: [indexPath])
}
}
}
With this code my firebase database in "save_post" has -> currentUseruId -> postid -> postUserId.
On ProfileController users can view saved Posts from "savedPost" Tab. The code is:
var savedPosts = [Post]()
fileprivate func fetchSaved() {
var userIds = [String]()
var postIds = [String]()
guard let uid = self.user?.uid else { return }
let getIDsRef = Database.database().reference().child("save_post").child(uid)
let query = getIDsRef.queryOrderedByKey()
query.observeSingleEvent(of: .value) { (snapshot) in
let dictionary = snapshot.value as? [String: Any]
dictionary?.forEach({ (key,value) in
guard let dic = value as? [String: String] else { return }
postIds.append(key)
userIds.append(dic["userId"] ?? "")
})
var i = 0
while i < userIds.count {
self.fetchPostsWithUserIDPostID(userID: userIds[i], postID: postIds[i])
i += 1
}
}
}
fileprivate func fetchPostsWithUserIDPostID(userID: String, postID: String) {
let getPostRef = Database.database().reference().child("video_list")
getPostRef.child(userID).child(postID).observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String: Any] else { return }
let ref = Database.database().reference().child("users").child(userID)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dict = snapshot.value as? [String: Any] else { return }
let user = User(uid: userID, dictionary: dict)
var post = Post(user: user, dictionary: dictionary)
post.id = postID
guard let currentUserUID = Auth.auth().currentUser?.uid else { return }
Database.database().reference().child("likes").child(postID).child(currentUserUID).observeSingleEvent(of: .value, with: { (snapshot) in
if let value = snapshot.value as? Int, value == 1 {
post.hasLiked = true
} else {
post.hasLiked = false
}
post.hasSaved = true
self.savedPosts.append(post)
self.savedPosts.sort(by: { (p1, p2) -> Bool in
return p1.creationDate.compare(p2.creationDate) == .orderedDescending
})
self.collectionView.reloadData()
})
})
})
}
However, when I click "savedPost" tab, there is no post shown. I don't know where my mistake is. I have all the necessary code under all override func collectionView(....). I believe the error should come from the code listed above. I am sincerely looking for help to resolve this issue. Thanks.
There could be a number of things going on here. It would be good to throw some print statements in there to make sure that 1) the data you're getting back from the database looks like what you expect, and 2) that you're properly parsing it into Post objects. Do you have your cells defined properly for your CollectionView? Also, I don't see where you are defining the data source for the CollectionView.
Related
I am trying to get data from firestore collection and assign it to an array of dictionaries. for this part of the code below... i get the error "Cast from 'QuerySnapshot?' to unrelated type '[[String : Any]]' always fails" and the console prints "is not working".
guard let snap = snapshot as? [[String:Any]] else {
print("is not working")
completion(.failure(DatabaseError.failedToFetch))
return
}
Here is the full code.
// fetches and returns all conversations for the user with passed in uid
public func getAllConversations(for uid: String, completion: #escaping(Result<[Conversation], Error>) -> Void) {
print("fetching all convos")
//NEW
let db = Firestore.firestore()
let CurrentUser = Auth.auth().currentUser?.uid
let ListRef = db.collection("users").document(CurrentUser!).collection("conversations")
// fetch the current users convo list
ListRef.getDocuments { snapshot, error in
if let err = error {
debugPrint("Error fetching documents: \(err)")
} else {
guard let snap = snapshot as? [[String:Any]] else {
print("is not working")
completion(.failure(DatabaseError.failedToFetch))
return
}
print("is working")
let conversations: [Conversation] = snap.compactMap({ dictionary in
guard let id = dictionary["id"] as? String,
let name = dictionary["name"] as? String,
let otherUserUID = dictionary["other_user-uid"] as? String,
let latestMessage = dictionary["latest-message"] as? [String:Any],
let date = latestMessage["date"] as? String,
let message = latestMessage["message"] as? String,
let isRead = latestMessage["is-read"] as? Bool else {
return nil
}
//save other user ID to a global var
self.test = otherUserUID
//assign data into an array of dictionaries
let latestConvoObject = LatestMessage(date: date, text: message, isRead: isRead)
return Conversation(id: id, name: name, otherUserUid: otherUserUID, latestMessage: latestConvoObject)
})
completion(.success(conversations))
}
}
}
There are a numbers of way to read that data, and the process can be simplified by conforming objects to the codable protocol but let me provide a straight forward example. I don't know what your Conversation object looks like so here's mine
class ConversationClass {
var from = ""
var to = ""
var msg = ""
var timestamp = 0
convenience init(withDoc: DocumentSnapshot) {
self.init()
self.from = withDoc.get("from") as? String ?? "no from"
self.to = withDoc.get("to") as? String ?? "no to"
self.msg = withDoc.get("msg") as? String ?? "no msg"
self.timestamp = withDoc.get("timestamp") as? Int ?? 0
}
}
and then here's the the code that reads in all the conversation documents from a Collection, stores each in a ConversationClass object, puts those in an array and returns it through an escaping completion handler
func getConversations(completion: #escaping( [ConversationClass] ) -> Void) {
let conversationCollection = self.db.collection("conversations")
conversationCollection.getDocuments(completion: { snapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
guard let docs = snapshot?.documents else { return }
var convoArray = [ConversationClass]()
for doc in docs {
let convo = ConversationClass(withDoc: doc)
convoArray.append(convo)
}
completion(convoArray)
})
}
I have a search function in my app which works perfectly except for the fact that it seems to be one reload behind. For example if I type in 'S' nothing will show but if I then type an 'a' after all the results for 'S' will show up. I tried adding collectionView reload at the end of the fetch function but if I do that nothing shows up period.
#objc func textFieldDidChange(_ textField: UITextField) {
guard let text = textField.text else { return }
if text.count == 0 {
self.posts.removeAll()
self.filteredPosts.removeAll()
} else {
fetchSearchedPosts(searchTerm: text)
}
self.collectionView.reloadData()
}
func fetchSearchedPosts(searchTerm: String) {
self.collectionView.refreshControl?.endRefreshing()
let ref = Database.database().reference().child("posts").queryOrdered(byChild: "title").queryStarting(atValue: searchTerm).queryEnding(atValue: "\(searchTerm)\u{f8ff}")
ref.observeSingleEvent(of: .value) { (snapshot) in
if !snapshot.exists() { return }
guard let dictionaries = snapshot.value as? [String: Any] else { return }
self.posts.removeAll()
dictionaries.forEach({ (key, value) in
guard let postDictionary = value as? [String: Any] else { return }
guard let uid = postDictionary["uid"] as? String else { return }
Database.fetchUserWithUID(uid: uid, completion: { (user) in
let post = Post(postId: key, user: user, dictionary: postDictionary)
let nowTimeStamp = Date().timeIntervalSince1970
let dateTime = post.endTimeDate
let timeStamp = dateTime.timeIntervalSince1970
if nowTimeStamp < timeStamp {
post.id = key
self.posts.append(post)
} else {
return
}
})
self.posts.sort(by: { (post1, post2) -> Bool in
return post1.title.compare(post2.title) == .orderedAscending
})
})
self.collectionView.reloadData()
}
}
You need a DispatchGroup as you have nested asynchronous calls
func fetchSearchedPosts(searchTerm: String) {
self.collectionView.refreshControl?.endRefreshing()
let ref = Database.database().reference().child("posts").queryOrdered(byChild: "title").queryStarting(atValue: searchTerm).queryEnding(atValue: "\(searchTerm)\u{f8ff}")
ref.observeSingleEvent(of: .value) { (snapshot) in
if !snapshot.exists() { return }
guard let dictionaries = snapshot.value as? [String: Any] else { return }
self.posts.removeAll()
let g = DispatchGroup() ///// 1
dictionaries.forEach({ (key, value) in
guard let postDictionary = value as? [String: Any] else { return }
guard let uid = postDictionary["uid"] as? String else { return }
g.enter() ///// 2
Database.fetchUserWithUID(uid: uid, completion: { (user) in
let post = Post(postId: key, user: user, dictionary: postDictionary)
let nowTimeStamp = Date().timeIntervalSince1970
let dateTime = post.endTimeDate
let timeStamp = dateTime.timeIntervalSince1970
if nowTimeStamp < timeStamp {
post.id = key
self.posts.append(post)
} else {
g.leave() ///// 3.a
return
}
g.leave() ///// 3.b
})
})
g.notify(queue:.main) { ///// 4
self.posts.sort(by: { (post1, post2) -> Bool in
return post1.title.compare(post2.title) == .orderedAscending
})
self.collectionView.reloadData()
}
}
}
I'm trying to filter my firebase data within the table view to group items that have the same category (child value) after each other. Currently, it's not returning the values based on the child value. I think it's being done in random order.
Here is my code:
How I'm attempting to filter the data:
self.items.append(item)
self.items.sort(by: { (item1, item2) -> Bool in
return item1.category.compare(item2.category) == .orderedSame
})
self.tableView.reloadData()
Model:
struct Item {
var id: String?
var user: User
var fromId: String?
let item: String
let category: String
let creationDate: Date
init(user: User, dictionary: [String: Any]) {
self.user = user
self.item = dictionary["item"] as? String ?? ""
self.fromId = dictionary["fromId"] as? String ?? ""
self.category = dictionary["category"] as? String ?? ""
let secondsFrom1970 = dictionary["creationDate"] as? Double ?? 0
self.creationDate = Date(timeIntervalSince1970: secondsFrom1970)
}
}
Fetch function:
fileprivate func fetchListItems() {
self.tableView.refreshControl?.endRefreshing()
guard let uid = Auth.auth().currentUser?.uid else { return }
guard let listId = list?.id else { return }
let ref = Database.database().reference().child("lists").child(listId).child("list-items")
ref.observe(.childAdded) { (snapshot) in
let itemId = snapshot.key
let itemRef = Database.database().reference().child("items").child(itemId)
itemRef.observeSingleEvent(of: .value, with: { (snapshot) in
let itemId = snapshot.key
guard let dictionary = snapshot.value as? [String: Any] else { return }
guard let uid = dictionary["fromId"] as? String else { return }
Database.fetchUserWithUID(uid: uid, completion: { (user) in
var item = Item(user: user, dictionary: dictionary)
item.id = snapshot.key
self.items.append(item)
self.items.sort(by: { (item1, item2) -> Bool in
return item1.category.compare(item2.category) == .orderedSame
})
self.tableView.reloadData()
ref.keepSynced(true)
})
})
}
}
Database:
I'm initializing some data in Firebase that I will use to track a user's activity. I need to make sure this data is written to Firebase so I'm wondering what the best practice is for ensuring a critical upload was successful?
static func createUserActivityCounts(uid: String) {
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + .seconds(5)) {
let databaseReference = Database.database().reference()
let userCounts: [String: Any] = ["posts": 0,
"comments": 0,
"likes": 0]
databaseReference.child("userActivity").child(uid).child("counts").setValue(userCounts) { (error, ref) -> Void in
if error != nil {
print(error!.localizedDescription)
}
}
}
}
I think the best way to resolve this issue is to check if the value exists whenever you attempt to query a critical upload. If the value isn't there then you initialize it. Here is the function I wrote to handle this.
private func getUserActivityCounts() {
if let uid = Auth.auth().currentUser?.uid {
userRef.child("userActivity").child(uid).child("counts").observeSingleEvent(of: .value, with: { snap in
if !snap.exists() {
// create user counts
if let uid = Auth.auth().currentUser?.uid {
DatabaseFunctions.createUserActivityCounts(uid: uid)
}
}
if let counts = snap.value as? [String: Any] {
if let numberOfLikes = counts["likes"] as? Int, let commentCount = counts["comments"] as? Int, let postCount = counts["posts"] as? Int {
DispatchQueue.main.async {
self.headerRef.postCount.text = String(postCount)
self.headerRef.commentCount.text = String(commentCount)
self.headerRef.likeCount.text = String(numberOfLikes)
self.collectionView.reloadData()
}
self.numberOfUserPosts = postCount
self.commentCount = commentCount
self.likeCount = numberOfLikes
}
}
})
}
}
I've been attempting to utilize firebase's snapshots, but when I try to access specific children, the output is a null.
var ref = FIRDatabaseReference.init()
func loadData {
ref = FIRDatabase.database().reference(
ref.child("Posts").child(postId).observeSingleEventOfType(.Value, withBlock: { snapshot in
print(snapshot.value?["PostText"] as! String) // Optional(<null>)
print(snapshot)
for child in snapshot.children {
if child.childSnapshotForPath("PostText").value == nil {
self.postText.text = ""
} else {
self.postText.text = child.childSnapshotForPath("PostText").value as? String // Optional(<null>)
print(child.childSnapshotForPath("PostText").value)
}
}
})
}
Output of print(snapshot)
Snap (84844) {
Author = lGAV1KUhSCP8hnFiKY1N9lBPrmmst1;
CommentsCount = 1;
Group = 665555;
ImageUrl = "http://i.telegraph.co.uk/multimedia/archive/03589/Wellcome_Image_Awa_3589699k.jpg";
PostText = "I like cakeh, but theijijijijijij truth is, it's too sweet. So SOMETIMES I dont eat it, but i LIKE CAKE.";
}
It looks like your snapshot is a Dictionary. Then you have to cast it as a Dictionary:
func loadData {
ref = FIRDatabase.database().reference(
ref.child("Posts").child(postId).observeSingleEventOfType(.Value, withBlock: { snapshot in
print(snapshot.value?["PostText"] as! String) // Optional(<null>)
print(snapshot)
let dict = snapshot.value as! Dictionary<String, AnyObject>
if let author = dict["Author"] as? String, commentsCount = dict["CommentsCount"] as? Int, group = dict["Group"] as? Int {
print("author \(author) commentsCount \(commentsCount), group: \(group)")
}
})
}
Do the same for ImageUrl and PostText, they should be cast as String