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.
Related
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.
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
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.
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.
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.