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

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

Related

Firebase Realtime database - tableview update all the cell data if new data added

I am working on a group chat app and trying to fetch the last message from a node to display the last message under the group name. Everything is working fine. When the group receives a new message, the last message is showing in the correct group and some other random groups as well. If I open the correct group and come back, all the other groups are showing the correct last message. Kindly help me with the below code.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return groupList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:GroupsTableViewCell = tableView.dequeueReusableCell(withIdentifier: "GroupsTableViewCell") as! GroupsTableViewCell
cell.selectionStyle = .none
var user: Groupslist
if groupList.count > 0 {
user = groupList[indexPath.row]
cell.lbl_name_group.text = user.name;
cell.view_count.isHidden = true
GetLastMsg(groupID: user.group_id ?? "", cell: cell)
cell.img_group.image = UIImage.init(named:"final_grp")
GetUnReadMsgCount(groupID: user.group_id ?? "", cell: cell)
}
return cell
}
groupList contains the list of groups which is fetched from firebase on viewWillAppear.
func GetLastMsg(groupID : String, cell : GroupsTableViewCell){
let userRef2 = rootRef2.child("message").child(groupID).queryLimited(toLast: 1)
userRef2.observe( .value, with: { snapshot in
guard let _ = snapshot.value as? [String:AnyObject] else {
print("Error")
return
}
print("children count: \(snapshot.children.allObjects.count)")
for snap in snapshot.children.allObjects as! [DataSnapshot] {
let message = Message()
let value = snap.value as? [String: Any] ?? [:]
let text = value["text"] as? String ?? "Name not found"
let mimType = value["mimType"] as? String
let name_doc = value["name_doc"] as? String ?? "file.file"
message.text = text
message.mimType = mimType
message.name_doc = name_doc
message.timestamp_group = (value["timestamp"] as! NSNumber)
print(value["idSender"] as? String)
print(message.name_doc)
var msg_last = ""
// cell.lbl_lastmsg.text = msg_last
if message.mimType == "image/jpeg" {
msg_last = "Image"
cell.img_small_width.constant=28
cell.img_small.image = UIImage(named: "im")
}else if message.mimType == "audio/3gpp" {
msg_last = "Audio"
cell.img_small_width.constant=28
cell.img_small.image = UIImage(named: "mi")
}else if message.mimType == "video/mp4" {
msg_last = "Video"
cell.img_small_width.constant=28
cell.img_small.image = UIImage(named: "vi")
}else if message.mimType == "application/pdf" {
let name_docs = message.name_doc!.split{$0 == "."}.map(String.init)
msg_last = name_docs.last!
cell.img_small_width.constant=28
cell.img_small.image = UIImage(named: "doc")
}else if mimType == nil{
msg_last = message.text!
cell.img_small_width.constant=0
}
DispatchQueue.main.async {
cell.lbl_lastmsg.text = msg_last
}
}
}
})
}
New method:
func GetUnReadMsgCount(groupID : String, cell : GroupsTableViewCell) {
rootRef2.child("group_message_unread").child(current_user!).child(groupID).observe(.childAdded, with: { (snapshot) -> Void in
print(snapshot.childrenCount)
print(snapshot.children.description)
if snapshot.childrenCount > 0 {
DispatchQueue.main.async {
cell.view_count.isHidden = false
cell.lbl_count.text = String(snapshot.childrenCount)
// self.tbl_groups.reloadRows(at: [IndexPath(row: 3, section: 0)], with: .fade)
// self.tbl_groups.reloadData()
}
}
})
}
That's the expected behavior. In a .observe( .value listener, the snapshot is always the complete data at the path. So when you add a child node, the snapshot fires with the entire new data at userRef2.
You have two main options to not get duplicates in your UI:
Clear all messages when there is an update, before adding the data to the table again. So that'd be right before for snap in snapshot.children in your code. This is by far the simplest way to solve the problem if duplicate rows, but may lead to some flashing in your UI.
Listen for child events, instead of observing the entire value. With this you'll get a single .childAdded event for the new child, you can get rid of the for snap in snapshot.children.allObjects and just add the singular new child node to the table.

Strange behavior in table view after deleting event

I am working on an app which has users register for volunteering events, and am currently working on letting them view their own events and unregister.
This is the screen the users sees when the ask to view their events, in this case the two events are called "ok" and "that" (I just created some random testing events). When you click on one this is the screen you get:
When you click unregister, the event is deleted from your registeredEvents, but the table view now has two of the same events.
When I click back, and then go back to the table view, everything is normal, in this case only displaying the "ok" event, because the other one was deleted. Here is the code for the tableview layout:
override func viewDidLoad() {
super.viewDidLoad()
yourArray = []
actualEvents = []
let id = Auth.auth().currentUser?.uid
Database.database().reference().child("users").child(id!).child("registeredEvents").observe(.value) { snapshot in
let children = snapshot.children
while let rest = children.nextObject() as? DataSnapshot, let value = rest.value {
print(value)
self.yourArray.append(value as! String)
}
Database.database().reference().child("Events").observe(.value) { (data) in
let events = data.value as! [String:[String:Any]]
for(_,value) in events{
if(self.yourArray.contains(value["EventName"]! as! String)){
self.actualEvents.append(PersonalEvents(evName: value["EventName"]! as! String, evDesc: value["EventDescription"]! as! String, evStartDate: value["start time"]! as! String, evEndDate: value["end time"] as! String, evNumPeople: value["NumberOfPeople"]! as! Int, evNumRegistered: value["currentPeople"] as! Int))
}
}
print("Actual events array " + "\(self.actualEvents)")
}
self.tblEvents.reloadData()
}
print(yourArray)
self.tblEvents.dataSource = self
self.tblEvents.delegate = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return yourArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "productstable", for: indexPath)
cell.textLabel?.text = self.yourArray[indexPath.row]
return cell
}
And the unregister button(This is in a different view controller, becuase info is displayed about the events in a different view):
#IBAction func didUnregister(_ sender: Any) {
let id = Auth.auth().currentUser?.uid
let ref = Database.database().reference()
ref.child("users").child(id!).child("registeredEvents").child(personalEventInfo!.eventName!).removeValue { error,arg in
if error != nil {
print("error \(error)")
}
}
let event = ref.child("Events")
event.child(personalEventInfo!.eventName!).observeSingleEvent(of: .value) { (snapshot) in
let value = snapshot.value as! NSDictionary
var currentPeople = value["currentPeople"] as! Int
currentPeople = currentPeople - 1
Database.database().reference().child("Events").child(self.personalEventInfo!.eventName!).child("currentPeople").setValue(currentPeople)
}
}
Please let me know if this is confusing, but if it is not, then please let me know why this is happening, and what I can do to fix it.
Remove the items inside the Array before you append the data inside.
Improvement: You need to take care your memory by using [weak self] inside these closures.
Can you try the following:
Database.database().reference().child("users").child(id!).child("registeredEvents").observe(.value) { [weak self] snapshot in
let children = snapshot.children
self?.yourArray.removeAll()
while let rest = children.nextObject() as? DataSnapshot, let value = rest.value {
print(value)
self?.yourArray.append(value as! String)
}
Database.database().reference().child("Events").observe(.value) { [weak self] (data) in
let events = data.value as! [String:[String:Any]]
self?.actualEvents.removeAll()
for(_,value) in events{
if(self?.yourArray.contains(value["EventName"]! as! String)){
self?.actualEvents.append(PersonalEvents(evName: value["EventName"]! as! String, evDesc: value["EventDescription"]! as! String, evStartDate: value["start time"]! as! String, evEndDate: value["end time"] as! String, evNumPeople: value["NumberOfPeople"]! as! Int, evNumRegistered: value["currentPeople"] as! Int))
}
}
print("Actual events array " + "\(self?.actualEvents)")
}
self?.tblEvents.reloadData()
}

Firebase listen for changes in number of likes and display it on screen

When user doubletap on the image,the number of likes(heart) will increase for each post.
I tried this code but it does not work as expected(For eg when i double tap,it will load one post and duplicate it with different number of likes(Eg Post 1-1like,Post 1-2like,Post 1-3like).How do i display the updated number of likes without duplicating the post?(Post 1- Displaying the X number of likes where X=Incremental)
func loadPosts() {
let ref = Database.database().reference()
ref.child("posts").observe(.childAdded) { (snapshot: DataSnapshot) in
if let dict = snapshot.value as? [String: Any] {
guard let titleText = dict["title"] as? String else{return}
let locationDetails = dict["location"] as! String
let captionText = dict["caption"] as! String
let photoUrlString = dict["photoUrl"] as! String
let priceText = dict["price"] as! String
let categoryText = dict["category"] as! String
let usernameLabel = dict["username"] as! String
let profileImageURL = dict["pic"] as! String
let heartInt = dict["heart"] as! Int
let timestampString = dict["timestamp"] as! String
let post = Post(titleText: titleText, captionText: captionText, locationDetails: locationDetails, photoUrlString: photoUrlString, priceText: priceText,categoryText: categoryText, usernameLabel: usernameLabel, profileImageURL: profileImageURL, heartInt: heartInt, timestampString: timestampString)
//append(post) to array
self.posts.append(post)
print(self.posts)
self.collectionView.reloadData()
}
}
}
func delayCompletionHandler(completion:#escaping (() -> ())) {
let delayInSeconds = 0.5
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayInSeconds) {
completion()
}
}
//CollectionView
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
cell.reloadEvents = {
self.delayCompletionHandler {
self.loadPosts()
}
}
}
To detect the heartTapped which is in another file:
#objc func heartTapped(){
print(" I heart u")
let ref = Database.database().reference()
heartInt1 += 1
ref.child("posts").child(timestamp).observeSingleEvent(of: .value, with: { (snapshot) in
if let dic = snapshot.value as? [String : AnyObject]{
var heartString = dic["heart"] as! Int
heartString += 1
ref.child("posts").child(self.timestamp).updateChildValues(["heart" : heartString])
}
})
reloadEvents?()
}
}
Silly me I should have added self.posts.removeAll()in loadPosts()
Solved.
Your solution is bad practice. When you deal with databases you have to think at scale, if Kim Kardashian that gets a heart every second uses your app, whats going to happen then, load and delete everything for every heart? Does that sound efficient?
You need to find a way to load only the number of hearts not everything in order to get the number of hearts.
A solution would be to add an extra child ref.child("hearts") and then have a loadHearts() where you will get just the number of hearts.

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.

Merging Firebase Data into Table View

To start off I have 2 sets of data stored in Firebase. I will include both sets in a photo.
This first photo shows where I keep all the user's profiles.
When someone checks in on the app I store their username and check-in time at a different place.
In the photo above you will be able to see that there are currently 2 users checked in on "12-3-2017 Block 2"
I have no problem storing the data.
I do however have problems displaying the data.
I want to be able to create a UITableView that shows all the users who are checked in and the time they checked in while in the same list show all the users who have yet to checkin.
import UIKit
import Firebase
struct checkInStruct {
let userName : String!
let hour : String!
let minutes : String!
let userNameFromAllUsers : String!
}
class checkInDaysTableViewController: UITableViewController {
//Variables
var lastChildNameSegue = ""
var posts = [checkInStruct]()
let user = FIRAuth.auth()?.currentUser
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "\(lastChildNameSegue)"
print("Name of Child After Segue -> \(lastChildNameSegue)")
loadCheckedInUsers()
loadAllUsers()
print(posts)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
let userName = posts[indexPath.row].userName
let hour = posts[indexPath.row].hour
var minute = posts[indexPath.row].minutes
let userNameOfAllUsers = posts[indexPath.row].userNameFromAllUsers
let label1 = cell?.viewWithTag(1) as! UILabel
let label2 = cell?.viewWithTag(2) as! UILabel
if(hour == nil || hour == ""){
//Users isnt signed in
label1.text = "\(userName!)"
label2.text = "User Not checked in"
}else{
label1.text = "\(userName!)"
label2.text = "\(hour!):\(minute!)"
}
return cell!
}
func loadCheckedInUsers (){
let databaseRef = FIRDatabase.database().reference()
let userID = user?.uid
databaseRef.child("users").child("\(userID!)").child("checkins").child("\(lastChildNameSegue)").queryOrderedByKey().observe(.childAdded, with: { (snapshot) in
let userNameSnapValue = snapshot.value as? NSDictionary
let userName = userNameSnapValue?["userName"] as? String
let hourSnapValue = snapshot.value as? NSDictionary
let hour = hourSnapValue?["hour"] as? String
let minutesValue = snapshot.value as? NSDictionary
let minutes = minutesValue?["minutes"] as? String
print(userName!)
print(hour!)
print(minutes!)
self.posts.insert(checkInStruct(userName: userName, hour: hour, minutes: minutes, userNameFromAllUsers: userName) , at: 0)
self.tableView.reloadData()
})
}
func loadAllUsers(){
let databaseRef = FIRDatabase.database().reference()
let userID = user?.uid
databaseRef.child("users").child("\(userID!)").child("employeesorstudents").queryOrderedByKey().observe(.childAdded, with: { (snapshot) in
let userNameSnapValue = snapshot.value as? NSDictionary
let userName = userNameSnapValue?["userName"] as? String
let hour = ""
let minutes = ""
print(userName!)
self.posts.insert(checkInStruct(userName: userName, hour: hour, minutes: minutes, userNameFromAllUsers: userName) , at: 0)
self.tableView.reloadData()
})
}
}
As you can see I am storing data in one struct.
However, on the app, it is showing both the name of the user from the database of all users and the checked in users.
In the screenshot, you will see "Brandon Happy" has a check-in time and then he reappears again saying he was never checked in. How can I delete the duplicate data from the struct?
Everything looks good however what you should do is not add data to the struct if it already exists so what you need to do is fix your loadAllUsers() function.
func loadAllUsers(){
let databaseRef = FIRDatabase.database().reference()
let userID = user?.uid
databaseRef.child("users").child("\(userID!)").child("employeesorstudents").queryOrderedByKey().observe(.childAdded, with: { (snapshot) in
let userNameSnapValue = snapshot.value as? NSDictionary
let userName = userNameSnapValue?["userName"] as? String
let hour = ""
let minutes = ""
print(userName!)
databaseRef.child("users").child("\(userID!)").child("checkins").child("\(self.lastChildNameSegue)").observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.hasChild("\(userName!)"){
print("User already checked in")
}else{
print("user has not yet checked in")
self.posts.insert(checkInStruct(userName: userName, hour: hour, minutes: minutes, userNameFromAllUsers: userName) , at: 0)
self.tableView.reloadData()
}
})
})
}
In the if statement it will figure out if the user has or has not been checked in yet and then adds it to the struct if they have not been checked in.

Resources