Sorting cells instead of table - swift

My App checks which POI/ Feed/ Group the user is following. Based on that it loads comments with the content of the comment and the user name/picture. Those are loaded separately. My only issue is sorting by time.
If I use this code: self.table = self.table.sorted(by: { $0.userTime ?? 0 > $1.userTime ?? 0 }) the sorting of the comments is correct. But the matching of the profile names and pictures is totally wrong.
If I remove that code above the matching is correct but the sorting is totally wrong. How can I solve this? Instead of sorting the table I have to sort the cell itself?
func loadFollowedPoi() {
myFeed.myArray1 = []
let userID = Auth.auth().currentUser!.uid
let database = Database.database().reference()
database.child("user/\(userID)/abonniertePoi/").observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children.allObjects as! [DataSnapshot] {
myFeed.myArray1.append(child.key)
}
self.postsLaden()
})
}
func postsLaden() {
dic = [:]
let neueArray: [String] = []
for groupId in myFeed.myArray1[0..<myFeed.myArray1.count] {
let placeIdFromSearch = ViewController.placeidUebertragen
ref = Database.database().reference().child("placeID/\(groupId)")
ref.observe(DataEventType.childAdded, with: { snapshot in
guard let dic = snapshot.value as? [String: Any] else { return }
let newPost = importPosts(dictionary: dic, key: snapshot.key)
guard let userUid = newPost.userID else { return }
self.fetchUser(uid: userUid, completed: {
self.table.insert(newPost, at: 0)
self.table = self.table.sorted(by: { $0.userTime ?? 0 > $1.userTime ?? 0 })
self.tableView.reloadData()
})
}
)}
}
func fetchUser(uid: String, completed: #escaping () -> Void) {
ref = Database.database().reference().child("user").child(uid).child("userInformation")
ref.observe(.value) { (snapshot) in
guard let dic = snapshot.value as? [String: Any] else { return }
let newUser = UserModel(dictionary: dic)
self.users.insert(newUser, at: 0)
completed()
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.layoutMargins = UIEdgeInsets.zero
cell.post = table[indexPath.row]
cell.user = users[indexPath.row]
return cell
}
class importPosts {
var userID: String?
var userGroup: String?
var userComment: String?
var userTime: Int?
var userLikes: Int?
var commentId: String?
var placeID: String?
var kommentarCount: Int?
var id: String?
var likeCount: Int?
var likes: Dictionary<String, Any>?
var isLiked: Bool?
init(dictionary: [String: Any], key: String) {
userID = dictionary["userID"] as? String
userComment = dictionary["userComment"] as? String
userGroup = dictionary["userGroup"] as? String
userTime = dictionary["userTime"] as? Int
userLikes = dictionary["userLikes"] as? Int
commentId = dictionary["commentId"] as? String
placeID = dictionary["placeID"] as? String
kommentarCount = dictionary["kommentarCount"] as? Int
id = key
likeCount = dictionary["likeCount"] as? Int
likes = dictionary["likes"] as? Dictionary<String, Any>
ViewComments.commentIDNew = commentId!
if let currentUserUid = Auth.auth().currentUser?.uid {
if let likes = self.likes {
isLiked = likes[currentUserUid] != nil
}
}
}
}

As suggested in the comments create a parent struct which contains one user an one post respectively
struct UserData {
let user: UserModel
let post: importPosts
}
Side note: Please name structs/classes always uppercase and why not simply User and Post?
Create the datasource array
var users = [UserData]()
Modify fetchUser to pass the new user in the completion handler
func fetchUser(uid: String, completed: #escaping (UserModel) -> Void) {
ref = Database.database().reference().child("user").child(uid).child("userInformation")
ref.observe(.value) { (snapshot) in
guard let dic = snapshot.value as? [String: Any] else { return }
let newUser = UserModel(dictionary: dic)
completed(newUser)
}
}
And modify also postsLaden to assign the post and the associated user to the model
func postsLaden() {
//dic = [:]
//let neueArray: [String] = [] seems to be unused
for groupId in myFeed.myArray1[0..<myFeed.myArray1.count] {
let placeIdFromSearch = ViewController.placeidUebertragen
ref = Database.database().reference().child("placeID/\(groupId)")
ref.observe(DataEventType.childAdded, with: { snapshot in
guard let dic = snapshot.value as? [String: Any] else { return }
let newPost = importPosts(dictionary: dic, key: snapshot.key)
guard let userUid = newPost.userID else { return }
self.fetchUser(uid: userUid, completed: { user in
self.users.insert(UserData(user: user, post: newPost), at: 0)
self.users.sort($0.user.userTime ?? 0 > $1.user.userTime ?? 0)
self.tableView.reloadData()
})
}
)}
}
Finally modify cellForRow
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.layoutMargins = UIEdgeInsets.zero
let user = users[indexPath.row]
cell.post = user.post
cell.user = user.user
return cell
}
Yet another side note: Sorting and reloading the table view multiple times inside the loop is unnecessarily expensive. You could add DispatchGroup to sort and reload the data once on completion. Regarding expensive: In the database isn't it possible that Post can hold a full reference to the user to avoid the second fetch? For example Core Data can.

Related

Reloading tableView after liking a button in cell

My tableView shows some user comments loaded via Firebase Database. The tableView contains also a prototype cell. First of all loadPosts() gets executed. It loads every post and also goes to the user and takes the profile picture which is up to date and at last it sorts them after time.
Now the user has the option to like a post. In the extension UITableViewDelegate I created a cell.buttonAction. Here it opens into the users account and looks if he already liked the post with a specific id. If not it adds +1. If it did already it takes one -1.
The code itself works. But I have huge performance issues. Sometime the tableView gets updated immidiately. Sometimes I have to manually refresh it. As you can see right now I use observeSingleEvent. Should I better use observe instead? But then it crashes sometimes if someone posts and I have the same tableView opened. I also don't want it to reload every time the whole tableView if someone liked a post.
I also created a Video. Maybe it helps. I really do not know how I can solve that problem.
func loadPosts() {
let placeIdFromSearch = ViewController.placeidUebertragen
ref = Database.database().reference().child("placeID/\(placeIdFromSearch)")
ref.queryOrdered(byChild: "userTime").queryLimited(toLast: 10).observeSingleEvent(of: DataEventType.value, with: {(snapshot) in
self.table.removeAll()
for video in snapshot.children.allObjects as! [DataSnapshot] {
let Object = video.value as? [String: AnyObject]
let timestampDate = NSDate(timeIntervalSince1970: Double(Object?["userTime"] as! NSNumber)/1000)
...
let time = dateFormatter.string(from: timestampDate as Date)
...
let userTimeForArranging = Object?["userTime"]
ViewComments.commentIDNew = commentId as! String
getProfilePicture()
func getProfilePicture(){
self.ref = Database.database().reference()
self.ref.child("user/\(userID!)").observe (.value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let userImage = value?["picture"] as? String ?? ""
let userName = value?["firstname"] as? String ?? ""
let video = importComment(userName: userName as! String, userGroup: userGroup as! String, userComment: userComment as! String, userTime: userTime as! String, userLikes: userLikes as! Int, commentId: commentId as! String, placeID: placeID as! String, kommentarCount: kommentarCount as! Int, userImage: userImage as! String, userTimeForArranging: userTimeForArranging as! Int)
self.table.insert(video, at: 0)
self.table = self.table.sorted(by: { $0.userTimeForArranging! > $1.userTimeForArranging! })
self.tableView.reloadData()
})
}
}
})
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TableViewCell
let video: importComment
video = table[indexPath.row]
cell.layoutMargins = UIEdgeInsets.zero
...
cell.userImage.image = UIImage(url: URL(string: video.userImage!))
cell.buttonAction = {
getClickedRow(arg: true, completion: { (success) -> Void in
if success {
self.loadPosts()
} else {
}
})
func getClickedRow(arg: Bool, completion: #escaping (Bool) -> ()) {
let selectedIndexPath = self.table[indexPath.row].commentId!
ViewSubComments.subCommentIdNew = selectedIndexPath
completion(arg)
}
}
return cell
}
From Prototype cell:
#IBAction func buttonTapped(_ sender: UIButton) {
buttonAction?()
let ref = Database.database().reference()
let userID = Auth.auth().currentUser!.uid
ref.child("user/\(userID)/places/\(ViewController.placeidUebertragen)/\(ViewComments.commentIDNew)").observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists(){
getLikeAndMinusOne ()
let database = Database.database().reference()
database.child("user/\(userID)/places/\(ViewController.placeidUebertragen)/\(ViewComments.commentIDNew)").removeValue()
}else{
getLikeAndAddOne ()
let database = Database.database().reference()
let object: [String: Any] = [
"like": "true",]
database.child("user/\(userID)/places/\(ViewController.placeidUebertragen)/\(ViewComments.commentIDNew)").setValue(object)
}
})
func getLikeAndAddOne () {
let database = Database.database().reference()
database.child("placeID/\(ViewController.placeidUebertragen)/\(ViewComments.commentIDNew)").observeSingleEvent(of: .value, with: { (snapshot) in
let userDict = snapshot.value as! [String: Any]
let userLikes = userDict["userLikes"] as! Int
var userLikesNeu = Int(userLikes)
userLikesNeu = Int(userLikes) + 1
database.child("placeID/\(ViewController.placeidUebertragen)/\(ViewComments.commentIDNew)").updateChildValues(["userLikes": userLikesNeu])
})}
func getLikeAndMinusOne () {
let database = Database.database().reference()
database.child("placeID/\(ViewController.placeidUebertragen)/\(ViewComments.commentIDNew)").observeSingleEvent(of: .value, with: { (snapshot) in
let userDict = snapshot.value as! [String: Any]
let userLikes = userDict["userLikes"] as! Int
var userLikesNeu = Int(userLikes)
userLikesNeu = Int(userLikes) - 1
database.child("placeID/\(ViewController.placeidUebertragen)/\(ViewComments.commentIDNew)").updateChildValues(["userLikes": userLikesNeu])
})}
}

Fetch data from multiple node in Firebase in Swift

I built my app to have news feed like Facebook. My problem is that I don't know how to fetch child images in Post and show it in a collectionView. Please show me how to do it. Appreciate any help.
Here is the db structure:
Posts
d7j3bWMluvZ6VH4tctQ7B63dU4u1:
20181112101928:
avatar: "https://platform-lookaside.fbsbx.com/platform/p..."
content: "Funny image"
images:
-LR4vaEIggkGekc-5ZME:
"https://firebasestorage.googleapis.com/v0/b/hon..."
-LR4vaENC-IsePibQYxY:
"https://firebasestorage.googleapis.com/v0/b/hon..."
name: "Thành Trung"
time: 1541992768776.3628
type: "Funny"
Here is my code:
func getDataFromPostFirebase() {
let getPostData = databaseReference.child("Posts")
getPostData.observe(.childAdded) { (snapshot) in
getPostData.child(snapshot.key).observe(.childAdded, with: { (snapshot1) in
getPostData.child(snapshot.key).child(snapshot1.key).observe(.value, with: { (snapshot2) in
self.arrayImageUrl = [String]()
if let dict = snapshot2.value as? [String : Any] {
guard let avatar = dict["avatar"] as? String else {return}
guard let content = dict["content"] as? String else {return}
guard let name = dict["name"] as? String else {return}
guard let time = dict["time"] as? Double else {return}
guard let type = dict["type"] as? String else {return}
if let images = dict["images"] as? [String : String] {
for image in images.values {
self.arrayImageUrl.append(image)
}
let newPost = Post(avatarString: avatar, contentString: content, nameString: name, timeDouble: time, typeString: type)
self.arrayPost.append(newPost)
DispatchQueue.main.async {
self.feedTableView.reloadData()
}
} else {
let newPost = Post(avatarString: avatar, contentString: content, nameString: name, timeDouble: time, typeString: type)
self.arrayPost.append(newPost)
DispatchQueue.main.async {
self.feedTableView.reloadData()
}
}
}
})
})
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return arrayImageUrl.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell", for: indexPath) as! TrangChu_CollectionViewCell
cell.imgContent.layer.cornerRadius = CGFloat(8)
cell.imgContent.clipsToBounds = true
cell.imgContent.layer.borderWidth = 2
cell.imgContent.layer.borderColor = #colorLiteral(red: 0.4756349325, green: 0.4756467342, blue: 0.4756404161, alpha: 1)
let url = URL(string: arrayImageUrl[indexPath.row])
cell.imgContent.sd_setImage(with: url, completed: nil)
return cell
}
Model object
import Foundation
class Post {
var avatar : String
var content : String
var images : [String]?
var name : String
var time : Double
var type : String
init(avatarString : String, contentString : String, nameString : String, timeDouble : Double, typeString : String) {
avatar = avatarString
content = contentString
// images = imagesString
name = nameString
time = timeDouble
type = typeString
}
}
As what I've said your db is not well structured. I suggest you re structure it like this.
Posts
d7j3bWMluvZ6VH4tctQ7B63dU4u1:
avatar: "https://platform-lookaside.fbsbx.com/platform/p..."
content: "Funny image"
images:
-LR4vaEIggkGekc-5ZME: "https://firebasestorage.googleapis.com/v0/b/hon..."
-LR4vaENC-IsePibQYxY: "https://firebasestorage.googleapis.com/v0/b/hon..."
name: "Thành Trung"
time: 1541992768776.3628
type: "Funny"
timestamp: 1540276959924
I removed the timestamp node and transferred it along the children node. Now you can fetch the posts with this.
ref.child("Posts").observe(.childAdded) { (snapshot) in
var post = Post()
let val = snapshot.value as! [String: Any]
post.name = val["name"] as? String
self.ref.child("Posts").child(snapshot.key).child("images").observeSingleEvent(of: .value, with: { (snap) in
post.imagesString = [String]()
for image in snap.children.allObjects as! [DataSnapshot] {
post.imagesString?.append(image.value as! String)
print("images \(image.value)")
}
list.append(post)
print("post \(post)")
})
If you want to order the posts you can achieve it using queryOrderedByChild("timestamp")
Add this to access your images:
guard let images = dict["images"] as? [[String: Any]] { return }
let imagesString: [String] = []
for imageDict in images {
for key in imageDict.keys {
if let imageName = imageDict[key] as? String else {
// here you access your image as you want
imagesString.append(imageName)
}
}
}
Then when creating the post object you use imagesString that we created:
let newPost = Post(avatarString: avatar, contentString: content, imagesString: imagesString, nameString: name, timeDouble: time, typeString: type)
You can fetch the images values using this
ref.child("Posts").observe(.childAdded) { (snapshot) in
self.ref.child("Posts").child(snapshot.key).observe(.childAdded, with: { (snapshot1) in
self.ref.child("Posts").child(snapshot.key).child(snapshot1.key).child("images").observe(.childAdded, with: { (snap) in
let post = new Post()
for rest in snap.children.allObjects as! [DataSnapshot] {
//append images
post.imagesString.append(rest.value)
}
post.avatarString = snapshot1.value["avatar"] as? String
...
})
})
I suggest you change the structure of your db because its nested. Refer here

UISearchBar and Firebase Database

struct postStruct {
let title : String!
let author : String!
let bookRefCode : String!
let imageDownloadString : String!
let status : String!
let reserved : String!
let category : String!
let dueDate : String!
}
'Above is where i set up the structure for the post, and below, is how i reference and retrieve the data from the firebase database.
My problem is that when you set up the searcher, i do not know how to get it to search based off of the title of the post.'
class DirectoryTableView: UITableViewController {
var posts = [postStruct]()
override func viewDidLoad() {
let databaseRef = Database.database().reference()
databaseRef.child("Books").queryOrderedByKey().observe(.childAdded, with: {
snapshot in
var snapshotValue = snapshot.value as? NSDictionary
let title = snapshotValue!["title"] as? String
snapshotValue = snapshot.value as? NSDictionary
let author = snapshotValue!["author"] as? String
snapshotValue = snapshot.value as? NSDictionary
let bookRefCode = snapshotValue!["bookRefCode"] as? String
snapshotValue = snapshot.value as? NSDictionary
let status = snapshotValue!["status"] as? String
snapshotValue = snapshot.value as? NSDictionary
let reserved = snapshotValue!["reserved"] as? String
snapshotValue = snapshot.value as? NSDictionary
let category = snapshotValue!["category"] as? String
snapshotValue = snapshot.value as? NSDictionary
let dueDate = snapshotValue!["dueDate"] as? String
snapshotValue = snapshot.value as? NSDictionary
self.posts.insert(postStruct(title: title, author: author, bookRefCode: bookRefCode, status: status, reserved: reserved, category: category, dueDate: dueDate) , at: 0)
self.tableView.reloadData()
})
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "cell")
let databaseRef = Database.database().reference()
let label1 = cell?.viewWithTag(1) as! UILabel
label1.text = posts[indexPath.row].title
let label2 = cell?.viewWithTag(2) as! UILabel
label2.text = posts[indexPath.row].author
let label3 = cell?.viewWithTag(3) as! UILabel
label3.text = posts[indexPath.row].bookRefCode
let label4 = cell?.viewWithTag(4) as! UILabel
label4.text = posts[indexPath.row].status
let label5 = cell?.viewWithTag(5) as! UILabel
label5.text = posts[indexPath.row].category
let image1 = cell?.viewWithTag(6) as! UILabel
image1.text = posts[indexPath.row].imageDownloadString
let label6 = cell?.viewWithTag(7) as! UILabel
label6.text = posts[indexPath.row].reserved
let label9 = cell?.viewWithTag(9) as! UILabel
label9.text = posts[indexPath.row].dueDate
return cell!
}
'Also, does anyone know how to sort the tableview cells (posts in this case) alphabetically?'
You can get all data already ordered alphabetically
databaseRef.child("Books").queryOrdered(byChild: "title").observe(.childAdded, with: { snapshot in
var snapshotValue = snapshot.value as? NSDictionary
let title = snapshotValue!["title"] as? String
snapshotValue = snapshot.value as? NSDictionary
....
}
or sort your array before reload the tableView
var sortedArray = swiftArray.sorted { $0.title.localizedCaseInsensitiveCompare($1.title) == ComparisonResult.orderedAscending }
Sample structure
for sorting data according to searchBar I had used an dictionary that having all my snapshot and I compared my searchBar text in that dict and after sorting reloaded tableView here is code that you can have a look at
//method to get all user Details in a dict
func getEmail() {
let databaseRef = Database.database().reference().child("users")
databaseRef.observe(.value, with: { (snapshot) in
if snapshot.exists(){
self.postData = snapshot.value as! [String : AnyObject]
let dictValues = [AnyObject](self.postData.values)
self.sarchDict = dictValues
}
})
}
//search bar delegate
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if self.mySearchBar.text!.isEmpty {
// set searching false
self.isSearching = false
}else{
// set searghing true
self.isSearching = true
self.names.removeAll()
self.uidArray.removeAll()
self.imageUrl.removeAll()
for key in self.sarchDict {
let mainKey = key
//I am making query against email in snapshot dict
let str = key["email"] as? String
//taking value of email from my dict lowerCased to make query as case insensitive
let lowercaseString = str?.lowercased()
//checking do my any email have entered letter or not
if(lowercaseString?.hasPrefix(self.mySearchBar.text!.lowercased()))!{
//here I have a check so to remove value of current logged user
if ((key["uID"] as! String) != (Auth.auth().currentUser?.uid)!){
//If value is found append it in some arrays
self.imageUrl.append( key["profilePic"] as! String )
self.names.append( key["name"] as! String )
self.uidArray.append( key["uID"] as! String )
//you can check which values are being added from which key
print(mainKey)
}
}
}
//reload TableView here
}
}
//TableView
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
cell = self.myTableView.dequeueReusableCell(withIdentifier: "Cell")!
if self.isSearching == true {
let imageView = (cell.viewWithTag(1) as! UIImageView)
imageView.setRounded()
if imageUrl[indexPath.row] != "" {
self.lazyImage.showWithSpinner(imageView:imageView, url:imageUrl[indexPath.row])
}
else{
imageView.image = UIImage(named: "anonymous")
}
(cell.contentView.viewWithTag(2) as! UILabel).text = self.names[indexPath.row]
}
else {
}
return cell
}
I'm sure this will be helpful to some using FireStore. Here I'm just setting my reference to point to the right collection. "name" is my field I wish to search by and is greater than will be checked chronologically on my string. The further they type the more defined the search results are.
static func searchForProgramStartingWith(string: String) {
let programsRef = db.collection("programs")
programsRef.whereField("name", isGreaterThan: string).limit(to: 10).getDocuments { (snapshot, error) in
if error != nil {
print("there was an error")
} else {
let shots = snapshot?.documents
for each in shots! {
let data = each.data()
let name = data["name"]
print("The name is \(name!)")
}
}
}
}

populate tableview based on returned queryEqual(toValue)

I am having trouble populating a tableView based on snapshot keys. The console is printing all (nodeToReturn) values from the parent node and children that are called from the queryEqual(toValue: locationString), so I know I am querying them correctly. But for some reason my tableView keeps populating all the User dictionaries from my Database.database().reference().child("Travel_Experience_Places"). I just want the tableView to display the snapshot data from the "nodeToReturn" values, so not every parent node from my "Travel_Experience_Places" database reference - only the parent nodes that have the same value of "locationString" in its children. Hopefully that makes sense. Thanks in advance!
// model object
class User: SafeUserObject {
var id: String?
var name: String?
var email: String?
var profileImageUrl: String?
var place: String?
init(dictionary: [String: AnyObject]) {
super.init()
self.id = dictionary["fromId"] as? String
self.name = dictionary["addedBy"] as? String
self.email = dictionary["email"] as? String
self.profileImageUrl = dictionary["profileImageUrl"] as? String
self.place = dictionary["place"] as? String
setValuesForKeys(dictionary)
}
}
// JSON structure
"Travel_Experience_Places" : {
"-Ks0Ms4fEQZxBytI-vu_" : {
"addedBy" : "Daniel Meier",
"place" : "Barcelona, Spain",
"profileImageUrl" : "https://firebasestorage.googleapis.com/v0/b/travelapp-255da.appspot.com/o/profile_images%2F21673E85-4F58-480D-B0A9-65884CADF7B3.png?alt=media&token=9e111014-d1d7-4b3b-a8c2-3897032c34cc",
"timestamp" : 1.503261589872372E9
},
// tableview to return firebase queryEqual(toValue: locationString) in tableview
var experiencePlaceUsers = [User]()
func fetchTravelingUserCell() {
let databaseRef = Database.database().reference().child("Travel_Experience_Places")
databaseRef.queryOrdered(byChild: "place").queryEqual(toValue: locationString).observe(.value, with: { snapshot in
if snapshot.exists(){
let allKeys = snapshot.value as! [String : AnyObject]
let nodeToReturn = allKeys.keys
print(nodeToReturn) // prints parent and child values that have locationString as a child value in its data structure
}
})
databaseRef.observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = User(dictionary: dictionary)
self.experiencePlaceUsers.append(user)
self.tableView.reloadData()
}
}, withCancel: nil)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return experiencePlaceUsers.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! UserCell
cell.user = experiencePlaceUsers[indexPath.item]
return cell
}
I believe you need to reload the tableview in your block where you get nodeToReurn
var experiencePlaceUsers = [User]()
func fetchTravelingUserCell() {
let databaseRef = Database.database().reference().child("Travel_Experience_Places")
databaseRef.queryOrdered(byChild: "place").queryEqual(toValue: locationString).observe(. childAdded, with: { snapshot in
if snapshot.exists(){
if let allKeys = snapshot.value as? [String: AnyObject] {
let singleUser = User(dictionary: allKeys)
self.experiencePlaceUsers.append(singleUser)
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}
})
/*
databaseRef.observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = User(dictionary: dictionary)
self.experiencePlaceUsers.append(user)
self.tableView.reloadData()
}
}, withCancel: nil)
*/
}

Default value for UITableView Cell if Firebase snapshot is nil

I have looked around and I cannot figure this out.
I am trying to set a default value of a tableView Cell if a Firebase snapshot returns nil
Example:
A snapshot is made to show all the event names from my Firebase Database
in a tableView using a dequeReusableCell.
But if the snapshot returns nil, the tableView returns with 1 cell with a label saying "Sorry, there are no events."
Here is my firebase snapshot code. This code does currently handle if the snapshot does return nil with a print() statement.
func populateTableView(){
let uid = Auth.auth().currentUser?.uid
ref = Database.database().reference()
ref.child("events").child(uid!).child(currentDate).observeSingleEvent(of: .value, with: { (snapshot) in
self.events = []
if let snapshot = snapshot.children.allObjects as? [DataSnapshot] {
for snap in snapshot {
//print("SNAP: \(snap)")
if let postDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let event = Event(postKey: key, postData: postDict)
self.events.append(event)
//print(self.events)
}
}
}
if !snapshot.exists() {
self.eventStatus = false
self.tableView.reloadData()
print("No Event here")
} else {
self.eventStatus = true
self.tableView.reloadData()
}
})
}
The firebase Objects get stored into the Event class and are stored a dictionary. I don't think this code is needed, but here is the event class code for more context.
import Foundation
import Firebase
class Event {
var ref: DatabaseReference!
private var _description: String!
private var _imageUrl: String!
private var _eventTitle: String!
private var _eventType: String!
private var _eventTime: String!
private var _eventStartDate: String!
private var _eventEndDate: String!
private var _monthlyRepeat: String!
private var _weeklyRepeat: String!
private var _eventColor: String!
private var _postKey: String!
private var _postRef: DatabaseReference!
var description: String {
return _description
}
var imageUrl: String {
return _imageUrl
}
var eventTitle: String {
return _eventTitle
}
var eventType: String {
return _eventType
}
var eventTime: String {
return _eventTime
}
var eventStartDate: String {
return _eventStartDate
}
var eventEndDate: String {
return _eventEndDate
}
var monthlyRepeat: String {
return _monthlyRepeat
}
var weeklyRepeat: String {
return _weeklyRepeat
}
var eventColor: String {
return _eventColor
}
var postKey: String {
return _postKey
}
init(postKey: String, postData: Dictionary<String, AnyObject>) {
self._postKey = postKey
if let description = postData["description"] as? String {
self._description = description
}
if let imageUrl = postData["event_Image_URL"] as? String {
self._imageUrl = imageUrl
}
if let eventTitle = postData["event_Title"] as? String {
self._eventTitle = eventTitle
}
if let eventType = postData["event_Type"] as? String {
self._eventType = eventType
}
if let eventTime = postData["event_Time"] as? String {
self._eventTime = eventTime
}
if let eventStartDate = postData["start_Date"] as? String {
self._eventStartDate = eventStartDate
}
if let eventEndDate = postData["end_Date"] as? String {
self._eventEndDate = eventEndDate
}
if let monthlyRepeat = postData["monthly_Repeat"] as? String {
self._monthlyRepeat = monthlyRepeat
}
if let weeklyRepeat = postData["weekly_Repeat"] as? String {
self._weeklyRepeat = weeklyRepeat
}
if let eventColor = postData["color"] as? String {
self._eventColor = eventColor
}
let uid = Auth.auth().currentUser?.uid
ref = Database.database().reference()
let eventRef = ref.child("events").child(uid!).child("Monday May, 29")
_postRef = eventRef.child(_postKey)
}
}
The simplest way to solve this is to add a title UILabel to your ViewcController and change the text when snapshot is not available.
Or if that doesn't work for you for some reason you could try this:
I did not check this, but I might get you on track.
First you will need to change your populateTableView method so that an events array is created even when snapshot has no results. This way the events array count will be 1 (and one row will be added to your tableView) even if snapshot had no result.
populateTableView(){
let uid = Auth.auth().currentUser?.uid
ref = Database.database().reference()
ref.child("events").child(uid!).child(currentDate).observeSingleEvent(of: .value, with: { (snapshot) in
self.events = []
if let snapshot = snapshot.children.allObjects as? [DataSnapshot] {
for snap in snapshot {
//print("SNAP: \(snap)")
if let postDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let event = Event(postKey: key, postData: postDict)
self.events.append(event)
//print(self.events)
}
}
}
else{ // Snapshot does not exist
let postDict: Dictionary<String, AnyObject> // Add an empty Dictionary
let key = -1 // Or what ever value you could not possibly expect
let event = Event(postKey: key, postData: postDict)
self.events.append(event)
self.tableView.reloadData()
print("No Event here")
}
})
}
Notice that when snapshot is not valid or available you add an empty Dictionary with an unique key value to your events array.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return events.count
}
You need to create two custom cells with unique identifiers.
Now you can "actually" populate your tableView similar to this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let event = events.[indexPath.row]
let conditionKey = event.key
if(conditionKey == -1){ // or whatever value you gave in populateTableView to indicate that snapshot did not exist
let cell = tableView.dequeueReusableCell(withIdentifier: "identifierCellNotSoGood", for: indexPath) as! CustomCellNotSoGood
cell.noSnapShotLabel1.text = "Sorry, there are no events."
return cell
}
else{
let cell = tableView.dequeueReusableCell(withIdentifier: "identifierCellAllGood", for: indexPath) as! CustomCellAllGood
cell.yourCustomLabel1.text = event.key // Or whatever data you are displaying
cell.sourCustomLabel2.text = event.event // Or whatever data you are displaying
return cell
}
return UITableViewCell
}
If you need to handle the selection of a table cell you can do this:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// get rid of the ugly highlighting
tableView.deselectRow(at: indexPath, animated: false)
let event = events.[indexPath.row
let conditionKey = event.key
if(conditionKey == -1){ // or whatever value you gave in populateTableView to indicate that snapshot did not exist
// Do what you need or not
}
else{
// Do something meaningful with your database
doSomething(withEventData: event)
}
}