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)
}
}
Related
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.
I retrieve data from MySql via PHP file to get users information and scores to load them in a table. I need to get the value of the first 3 users and put them in a Label outside the Table, it is like game leaders list. I attached an image to explain the idea.
Here is the structure code:
import Foundation
protocol HomeModelProtocol: AnyObject {
func itemsDownloaded(items: NSArray)
}
class HomeModel: NSObject, URLSessionDataDelegate {
weak var delegate: HomeModelProtocol!
let urlPath = "https://mywebsite.com/folder/callUserList.php" //this will be changed to the path where service.php lives
func downloadItems() {
let url: URL = URL(string: urlPath)!
let defaultSession = Foundation.URLSession(configuration: URLSessionConfiguration.default)
let task = defaultSession.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Failed to download data")
}else {
print("Data downloaded")
self.parseJSON(data!)
}
}
task.resume()
}
func parseJSON(_ data:Data) {
var jsonResult = NSArray()
do{
jsonResult = try JSONSerialization.jsonObject(with: data, options:JSONSerialization.ReadingOptions.allowFragments) as! NSArray
} catch let error as NSError {
print(error)
}
var jsonElement = NSDictionary()
let users = NSMutableArray()
for i in 0 ..< jsonResult.count
{
jsonElement = jsonResult[i] as! NSDictionary
let user = UsersModel()
//the following insures none of the JsonElement values are nil through optional binding
if let name = jsonElement["name"] as? String,
let email = jsonElement["email"] as? String,
let phoneNumber = jsonElement["phone"] as? String,
let userImage = jsonElement["image"] as? String
{
user.name = name
user.email = email
user.phoneNumber = phoneNumber
user.userImage = userImage
}
users.add(user)
}
DispatchQueue.main.async(execute: { () -> Void in
self.delegate.itemsDownloaded(items: users)
})
}
}
Here is the model:
import Foundation
class UsersModel: NSObject {
//properties
var name: String?
var email: String?
var phoneNumber: String?
var userImage: String?
//empty constructor
override init()
{
}
//construct with #name, #address, #latitude, and #longitude parameters
init(name: String, email: String, phoneNumber: String, userImage: String) {
self.name = name
self.email = email
self.phoneNumber = phoneNumber
self.userImage = userImage
}
//prints object's current state
override var description: String {
return "Name: \(String(describing: name)), Email: \(String(describing: email)), Phone Number: \(String(describing: phoneNumber)), User Image: \(String(describing: userImage))"
}
}
Here is the code in the TableView controller:
var feedItems: NSArray = NSArray()
override func viewDidLoad() {
super.viewDidLoad()
let homeModel = HomeModel()
homeModel.delegate = self
homeModel.downloadItems()
}
func itemsDownloaded(items: NSArray) {
feedItems = items
self.listTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of feed items
return feedItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Retrieve cell
let cellIdentifier: String = "BasicCell"
let myCell: WinnerTableCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as! WinnerTableCell
// Get the location to be shown
let item: UsersModel = feedItems[indexPath.row] as! UsersModel
// Get references to labels of cell
myCell.lbTextName!.text = item.name
return myCell
}
The data shows in the Table but I have no idea how to fill the 3 label with the 3 first users from the Table.
How can I get these values from the table and pass it to a label in the same ViewController?
Thanks
When adding this code:
if feedItems.count >= 3 {
lblFirstWinner.text = feedItems[0].name // 1st winner
lblSecondWinner.text = feedItems[1].name // 2nd winner
lblThirdWinner.text = feedItems[2].name // 3rd winner
}
it shows error: Value of type 'Any' has no member 'name'
Change itemsDownloaded method as
func itemsDownloaded(items: NSArray) {
feedItems = items
self.listTableView.reloadData()
for (index, user) in items.enumerated() {
let user = user as! UserModel
switch index {
case 0: // 1st winner
lblFirstWinner.text = user.name
case 1: // 2nd winner
lblSecondWinner.text = user.name
case 2: // 3rd winner
lblThirdWinner.text = user.name
}
}
}
OR
Change your HomeModelProtocol method and feedItems type to [UsersModel]
protocol HomeModelProtocol: AnyObject {
func itemsDownloaded(items: [UsersModel]) // Changed
}
var feedItems =[UsersModel]() // Changed
override func viewDidLoad() {
super.viewDidLoad()
let homeModel = HomeModel()
homeModel.delegate = self
homeModel.downloadItems()
}
func itemsDownloaded(items: [UsersModel]) {
feedItems = items
self.listTableView.reloadData()
if feedItems.count >= 3 {
lblFirstWinner.text = feedItems[0].name // 1st winner
lblSecondWinner.text = feedItems[1].name // 2nd winner
lblThirdWinner.text = feedItems[2].name // 3rd winner
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of feed items
return feedItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Retrieve cell
let cellIdentifier: String = "BasicCell"
let myCell: WinnerTableCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as! WinnerTableCell
// Get references to labels of cell
myCell.lbTextName!.text = feedItems[indexPath.row].name // Changed
return myCell
}
Just you need to add a few lines in the below function and your solution will be done.
func itemsDownloaded(items: NSArray) {
feedItems = items
self.listTableView.reloadData()
if feedItems.count >= 3 {
lblFirstWinner.text = feedItems[0].name // 1st winner
lblSecondWinner.text = feedItems[1].name // 2nd winner
lblThirdWinner.text = feedItems[2].name // 3rd winner
}
}
Let me know... is it working for you? and please also refer to #vadian comment on your question.
I want to get the following structure (screenshot of Firebase Database):
In chats I have the id of the chat. There are the users with the child userid and the values of id and name.
At first I look for the chats which a user have and want to get then the details of the chatId (users with their id and name)
I have the following class in Swift:
class Chat {
var chatId: String!
var userIds: [String]!
var userNames: [String]!
}
I have the following code to get the details, but I get not the userIds or userNames from the chatId:
func getChatsFromFirebase() {
self.ref = Database.database().reference()
self.ref?.child("users").child(userdefaults.getUserId()).child("chats").observe(.childAdded, with: { (snapshot) in
let chat = Chat()
chat.chatId = snapshot.key
chat.userIds = []
chat.userNames = []
//print(chat.chatId)
for i in 0..<self.chats.count {
let usersRef = self.ref.child("chats").child(self.chats[i].chatId).child("users").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
for userid in value!.allKeys as! [String] {
let usersdetailsRef = self.ref.child("chats").child(self.chats[i].chatId).child("users").child(userid).queryOrdered(byChild: "name").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
//print(value)
let id = value?["id"] as? String ?? ""
let name = value?["name"] as? String ?? ""
//print( id + ": " + name)
chat.userIds.append(id)
chat.userNames.append(name)
})
}
})
}
self.chats.append(chat)
self.tableView.reloadData()
})
}
I am very new to the Firebase topic. Can someone help me here?
Thanks.
Well You need to change your datamodel first. You dont need to store id value in , 12345 in this case. you can already fetch the key. Also, in /users/chats, you just can just save the chat id as either chat1 : IBDrbfku887BLIY or IBDrbfku887BLIY : true. You can always fetch them through value or the key respectively.
And in your chat document, you just need to reference the user id, i.e just get them and store them as user1 and user2. You can add more users if your usecase requires more.
Reconfigure your Data Model as follows.
Now You need 2 Objects Users and Chats as follows :
Users.swift
class User : NSObject {
private var _name: String!
private var _username: String!
private var _userid: String!
private var _userRef: DatabaseReference!
var name: String! {
get {
return _name
} set {
_name = newValue
}
}
var username : String! {
get {
return _username
} set {
_username = newValue
}
}
var userid: String! {
get {
return _userid
} set {
_userid = newValue
}
}
var userRef: DatabaseReference! {
get {
return _userRef
} set {
_userRef = newValue
}
}
init(userid: String, userData: Dictionary<String, Any>){
self._userid = userid
_userRef = Database.database().reference().child(_userid)
if let username = userData["username"] as? String {
self._username = username
}
if let name = userData["name"] as? String {
self._name = name
}
}
}
Chats.swift
class Chat : NSObject {
private var _chatid: String!
private var _user1: String!
private var _user2: String!
private var _chatRef: DatabaseReference!
var user1: String! {
get {
return _user1
} set {
_user1 = newValue
}
}
var user2 : String! {
get {
return _user2
} set {
_user2 = newValue
}
}
var chatid: String! {
get {
return _chatid
} set {
_chatid = newValue
}
}
var chatRef: DatabaseReference! {
get {
return _chatRef
} set {
_chatRef = newValue
}
}
init(chatid: String, chatData: Dictionary<String, Any>){
self._chatid = chatid
_chatRef = Database.database().reference().child(_chatid)
if let user = chatData["users"] as? Dictionary<String, Any> {
if let user1 = user["user1"] as? String {
self._user1 = user1
}
if let user2 = user["user2"] as? String {
self._user2 = user2
}
}
}
}
The major issue/or an overlooked issue here is the type of the data. In the /users, you id 12345 will be of type String. But when you fetch the same from /chats, it returns as Int. This downloads the value but never converts it. Always take care while seeding/testing your data.
To fetch the user's credentials just reference that through another query. This is what you can do :
var allUsers = [User]()
var allChats = [Chat]()
func viewDidLoad() {
super.viewDidLoad()
fetchAllChats()
}
func getUser(from userId: String, completion: #escaping (User) -> Void) {
Database.database().reference().child("users").child(userId).observeSingleEvent(of: .value, with: { snapshot in
if let datasnap = snapshot.value as? Dictionary<String, Any> {
let user = User(userid: userId, userData: datasnap)
completion(user)
}
})
}
func fetchAllChats() {
Database.database().reference().child("chats").observeSingleEvent(of: .value, with: { snapshot in
allChat.removeAll()
if let snapshot = snapshot.value as? Dictionary<String, Any> {
for snap in snapshot {
if let chatd = snap.value as? Dictionary<String, Any> {
let chat = Chat(chatid: snap.key, chatData: chatd)
self.allChats.append(chat)
}
}
}
// collectionview.reloadData() <--------- only if required.
})
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let chatData = allChats[indexPath.row]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CellId, for: indexPath) as! Cell
getUser(from: chatData.user1) { user in
cell.label.text = user.usernme
}
return cell
}
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!)")
}
}
}
}
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)
*/
}