Changing a collectionView cell changes all of them - ios

This is the code that gets the info from Firebase. It changes the cell's imageView image and the label's text. When it runs the code all of the cells have the same image and label text.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "suggestionCollectionViewCell", for: indexPath) as! SuggestionCollectionViewCell
Database.database().reference().child("users").observeSingleEvent(of: .childAdded) { (snapshot: DataSnapshot) in
if let dict = snapshot.value as? [String : Any] {
let imageURL = dict["imageURL"] as! String
let url = URL(string: imageURL)
let username = dict["username"] as! String
cell.imageView!.sd_setImage(with: url, completed: nil)
cell.usernameLabel.text = username
}
}
return cell
}

Actually for every cell rendering, you are fetching all the users from your Firebase database. Every call's last call is the user you are seeing multiple times. It overrides the previous ones.
To fix you problem you can store your users in an array.
var users = [User]()
To populate this array, you call a similar function like this in your viewDidLoad
func fetchUsers() {
Database.database().reference().child("users").observeSingleEvent(of: .childAdded) { (snapshot: DataSnapshot) in
if let dict = snapshot.value as? [String : Any] {
let user = User()
//set user properties here
users.append(user)
}
}
}
When you fetched all the users, you should reload your collectionView
collectionView.reloadData()
In your cellForItemAt function you can now access the users
let user = users[indexPath.row]
Use the user variable to setup you cell

To remove this problem, firstly you should gather all the data from the firebase in array and then use that array for table view accordingly.

Related

How to get current child (AutoID) from Firebase clicking TableViewCell - SWIFT?

I need to get current child (AutoID) from Firebase by clicking TableViewCell. I have cells with names, and I need to get ID from current name (cell) and passing to another view controller where will be displayed more details for that person.
So, I'm new in swift and I want to know how to get the ID?
This is in my VC1 ->
'''
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedGuest = guestsList[indexPath.row]
let controller = self.storyboard?.instantiateViewController(identifier:
"GuestDetail") as! GuestDetailsViewController
controller.guestUser = selectedGuest
self.present(controller, animated: true, completion: nil)
}
//THIS IS MY VC 2
func showGuestDetails(){
ref = Database.database().reference().child("userInfo").child(uid!).child("guests")
ref.queryOrderedByKey().observeSingleEvent(of: .value) { (snapshot) in
if snapshot.childrenCount>0{
self.guestDetail.removeAll()
for guests in snapshot.children.allObjects as![DataSnapshot]{
let guestObject = guests.value as? [String: AnyObject]
let name = guestObject?["guestName"]
let familyName = guestObject?["guestFamilyName"]
let phone = guestObject?["guestPhoneNumber"]
guard let email = guestObject?["guestEmail"] as? String,
email != self.guestUser?.guestEmail else {
continue
}
let guest = GuestModel(guestName: name as? String, guestFamilyName: familyName as! String, guestPhoneNumber: phone as? String, guestEmail: email as? String)
self.phoneNoLabel.text = guest.guestPhoneNumber
self.emailLabel.text = guest.guestEmail
}
}
}
self.nameLabel.text = guestUser!.guestName
}
'''
Try guests.key inside the for-loop

Firebase - sort posts by number of keys and show in TableView

I have made app that save users message and its username to Firebase with childByAutoID key. Inside there are childs that saves Username, message, likes and PostID (as you can see on image below).
After a lot of research and a lot of trying writing code by myself, I figure out that likes need to be saved as autoID keys inside separate child and then you have to count that keys to get number of likes (you'll see that child also on image under and that child is named "Liked")
Everything is displayed in tableView cell.
But all of them are displayed randomly which is OK (I would prefer to be ordered by date added), but what would really like is that loaded data in next next VC to be displayed as:
TOP 10 of the week
TOP 10 of the month
ALL BEST etc...
There'll be separate button for menu and when you press it, you'll be presented with next VC that contain Table View with same data, but this time sorted by most liked post.
This is code that writes keys to LIKED child path (when like is pressed on already loaded data from Firebase database):
#IBAction func likePressed(_ sender: Any) {
let ref = Database.database().reference()
self.likeButton.isEnabled = false
let key = ref.child("Frusters").childByAutoId().key
ref.child("Frusters").child(self.postID).observeSingleEvent(of: .value, with: { (snapshot) in
let updateLikes = ["Liked/\(key)" : key] as [String : Any]
ref.child("Frusters").child(self.postID).updateChildValues(updateLikes, withCompletionBlock: { (error, reff) in
if error == nil {
ref.child("Frusters").child(self.postID).observeSingleEvent(of: .value, with: { (snap) in
if let properties = snap.value as? [String : AnyObject] {
if let likes = properties["Liked"] as? [String : AnyObject] {
let count = likes.count
self.likeLabel.text = "\(count) Likes"
let update = ["likes" : count]
ref.child("Frusters").child(self.postID).updateChildValues(update)
self.likeButton.isHidden = true
self.unlikeButton.isHidden = false
self.likeButton.isEnabled = true
}
}
})
}
})
})
ref.removeAllObservers()
}
and this is the code that loads data and put it in my table view:
func loadData() {
self.fetchPosts.removeAll()
let ref = Database.database().reference()
ref.child("Frusters").observeSingleEvent(of: .value, with: { (snapshot) in
if let postDict = snapshot.value as? [String:AnyObject] {
for (_,postElement) in postDict {
print(postElement);
let post = Post()
post.username = postElement["Username"] as? String
post.message = postElement["Message"] as? String
post.likes = postElement["likes"] as? Int
post.postID = postElement["postID"] as? String
self.fetchPosts.append(post)
}
}
self.tableView.reloadData()
}) { (error) in
print(error.localizedDescription)
}
ref.removeAllObservers()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.fetchPosts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! PostTableViewCell
cell.messageLabel.text = self.fetchPosts[indexPath.row].message
cell.usernameLabel.text = self.fetchPosts[indexPath.row].username
cell.likeLabel.text = "\(self.fetchPosts[indexPath.row].likes!) Likes"
cell.postID = self.fetchPosts[indexPath.row].postID
cell.bckgView.layer.cornerRadius = 0
cell.bckgView.layer.shadowOffset = CGSize(width: 0, height: 1)
cell.bckgView.layer.masksToBounds = false
cell.bckgView.layer.shadowColor = UIColor.black.cgColor
cell.bckgView.layer.shadowOpacity = 0.3
cell.bckgView.layer.shadowRadius = 4
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension;
}
Well, problem is that I do not know how to insert inside my new UITableView top 10 post with most likes, to sort them from most liked post to next 9 of them with most likes.
Also, is it possible to sort them most liked this month or this week?
Does this keys that Firebase database makes (by autoID) contain date of created post or do I have to insert new child with date inside and then in code combine "date" child and "liked" child to be presented as top 10 liked post between 1st and last of this month?
Thanks in advance. ;)
1-You don't have to store each like separately, if all you care about is the number. You can just update the number.
#IBAction func likePressed(_ sender: Any) {
let ref = Database.database().reference()
self.likeButton.isEnabled = false
let key = ref.child("Frusters").childByAutoId().key
ref.child("Frusters").child(self.postID).observeSingleEvent(of: .value, with: { (snapshot) in
let counted = snapshot.value as? Int
self.ref.child("Flusters".child(self.postID).child("likes").setValue(counted! + 1)
})
2- Yes, you can sort by likes. you'd need to use the .queryOrdered function. Update the code as follows
func loadData() {
self.fetchPosts.removeAll()
let ref = Database.database().reference()
ref.child("Frusters").queryOrdered(byChild: "likes").observeSingleEvent(of: .value, with: { (snapshot) in
if let postDict = snapshot.value as? [String:AnyObject] {
for (_,postElement) in postDict {
print(postElement);
let post = Post()
post.username = postElement["Username"] as? String
post.message = postElement["Message"] as? String
post.likes = postElement["likes"] as? Int
post.postID = postElement["postID"] as? String
self.fetchPosts.append(post)
}
}
self.tableView.reloadData()
3- To order by top week, month, you'd have to keep track of a timestamp.

Loading next 15 posts each time the bottom of the page is reached

I have a function to fetch posts from my Firebase database and populate a collection view, 15 posts at a time using:
ref.child("posts").queryOrderedByKey().queryLimited(toLast: 15).observeSingleEvent(of: .value, with: { (snap) in
Now I want to have the next 15 posts fetched every time the user reaches the bottom of the screen. In my cellForItem method I'm trying to come up with a check to see if the bottom of the page has been reached, and if so, load the next 15 posts where it left off.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "postCell", for: indexPath) as! PostCell
cell.postImage.loadImageUsingCacheWithUrlString(posts[indexPath.row].pathToImage)
cell.postID = posts[indexPath.row].postID
cell.postImage.contentMode = UIViewContentMode.scaleAspectFill
if indexPath.row == posts.count - 1
{
fetchPosts()
}
return cell
}
I just don't know how I can specify that when the next 15 posts are loaded, they need to start where the last 15 left off, and not just fetch all the posts in the database again.
This is the whole fetchPosts() function:
func fetchPosts() {
let ref = FIRDatabase.database().reference()
ref.child("users").queryOrderedByKey().observe(.value, with: { snapshot in
let users = snapshot.value as! [String : AnyObject]
for (_, value) in users {
if let uid = value["uid"] as? String {
if uid == FIRAuth.auth()?.currentUser?.uid {
if let followingUsers = value["following"] as? [String : String] {
for (_, user) in followingUsers {
self.following.append(user)
}
}
self.following.append(FIRAuth.auth()!.currentUser!.uid)
ref.child("posts").queryOrderedByKey().queryLimited(toLast: 15).observeSingleEvent(of: .value, with: { (snap) in
for postSnapshot in snap.children.allObjects as! [FIRDataSnapshot] {
let value = postSnapshot.value as! [String : AnyObject]
if let userID = value["userID"] as? String {
for each in self.following {
if each == userID {
let posst = Post()
if let poster = value["poster"] as? String, let likes = value["likes"] as? Int, let pathToImage = value["pathToImage"] as? String, let postID = value["postID"] as? String {
posst.poster = poster
posst.likes = likes
posst.pathToImage = pathToImage
posst.postID = postID
posst.userID = userID
if let people = value["peopleWhoLike"] as? [String : AnyObject] {
for (_, person) in people {
posst.peopleWhoLike.append(person as! String)
}
}
posts.append(posst)
}
}
}
self.collectionView.reloadData()
}
}
})
ref.removeAllObservers()
}
}
}
})
}
What can I add/change here to ensure that 15 posts are loaded, in order, each time the bottom of the page is reached?
Haven't tried but here is the idea.
I would set two variables:
let readLimit:Int = 15
var readOffset:Int = 15
First variable is hardcoded and represents limit, where new results should be loaded.
In cellForItem function, check:
// Check if user scrolled to last row
if (indexPath.item + 1) == readOffset {
// Yes, scrolled to last row
// Increase limit to load more from database
readOffset += readLimit
// Call function which loads data from database
self.fetchPosts()
}
Do you see where am I getting? Now you have to modify read function a bit:
Change queryLimited(toLast: 15): replace 15 with self.readOffset
Keep in mind that this has not been tested, just wrote it here to help you understand.

I want to get specific data with clicking on collection view cell with parsing Json

I have a collection view and Json array. I Want to get specific data on another view by tapping collection view cell. I used to get data by using tags on buttons on main view, but when I changed view with collection view, now theres no separate buttons, so collection view now works with indexes. How to do kind of parser to get data with tapped index from collection view?
import Foundation
class CardGenerator {
static func generateCards(onMode mode: Letter) -> JsonEnum {
let filepath = Bundle.main.path(forResource: "Data", ofType: "json")!
let data = NSData(contentsOfFile: filepath)!
let json = try! JSONSerialization.jsonObject(with: data as Data, options: JSONSerialization.ReadingOptions.allowFragments) as! [String: AnyObject]
print(json)
print(json as? NSObject )
let cards: JsonEnum = JsonEnum(img: "", txt: "", lett: "", sound: "")
if let jsonCards = json[mode.rawValue] as? [String: AnyObject] {
//for aso in jsonCards {
print(jsonCards)
let card = JsonEnum()
if let image = jsonCards["image"] as? String {
card.image = image
}
if let text = jsonCards["text"] as? String {
card.text = text
}
if let letter = jsonCards["letter"] as? String {
card.letter = letter
}
if let sound = jsonCards["sound"] as? String {
card.sound = sound
}
print(card.image, card.text, card.letter, card.sound)
return card
}
return cards
}
}
The below UICollectionViewDelegate gets called when you select the UICollectionViewCell
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
To use the data of specific cell tabbed, use the below code:-
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
//here get data from cell
}

Access values in multidimentional Swift array?

I'd like to populate a tableView with results from a Firebase query. I successfully get the results and store them into an array but I'm struggling with how to put those values into the table.
I'm getting back a "userID" and a "title". I'm storing those in an array [[String]]. An example of an array after getting back a value would be [["OYa7U5skUfTqaGBeOOplRLMvvFp1", "manager"],["JQ44skOblqaGBe98ll5FvXzZad", "employee"]]
How would I access an individual value, such as the userID? This is what I have so far in my cellForRowAtIndexPath function:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
let invite = inviteArray[indexPath.row]
print(inviteArray)
print(invite)
return cell
}
Thanks!
EDIT: added function to store snapshot in an array
var dictArray: [Dictionary<String, String>] = []
func getAlerts(){
let invitesRef = self.rootRef.child("invites")
let query = invitesRef.queryOrderedByChild("invitee").queryEqualToValue(currentUser?.uid)
query.observeEventType(.Value, withBlock: { snapshot in
for child in snapshot.children {
guard let invitee = child.value["invitee"] as? String else{
return
}
guard let role = child.value["role"] as? String else{
return
}
self.dictArray.append(child as! Dictionary<String, String>)
print(self.dictArray)
}
})
}

Resources