I am posting some information to Firebase database
let bookObject: Dictionary<String, Any> = [
"uid": uid,
]
FIRDatabase.database().reference().child("posts").child(key).setValue(bookObject)
and I want to retreive the data in a tableView
func loadData(){
if (FIRAuth.auth()?.currentUser) != nil{
FIRDatabase.database().reference().child("posts").child(self.loggedInUser!.uid).observeSingleEvent(of: .value, with: { (snapshot:FIRDataSnapshot) in
let loggedInUserData = snapshot
if let postsDictionary = snapshot .value as? [String: AnyObject] {
for post in postsDictionary {
self.posts.add(post.value)
}
self.TableView.reloadData()
}})}
}
Which works, however, the posts get loaded to the TableView randomly, sometimes a new post would be posted to the top of the TableView, sometimes it would be posted to the bottom of the TableView. What do I do so that the new post shows on top of the previous cell.
You'll want to add a timestamp to each post and then sort the posts based on the timestamp. You didn't post how your TableView is built, but I recommend using a tableArray made of multiple posts that are a custom class Post that has all the properties of the post in Firebase. Then you can arrange the posts by doing tableArray.sort({$0.date > $1.date}), where date is one of the variables in Post containing a String of the timestamp when it was created. You probably want to sort the tableArray right before where you reload the TableView's data.
You can then populate your TableView with
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = //dequeueResuableCell
//retrieve data from the cell's corresponding post
let postCreator = tableArray[indexPath.row].creatorUID
let postContent = tableArray[indexPath.row].content
...
//populate the cell using the above data
...
}
and since your posts in the tableArray are in chronological order, the posts in your TableView will also be in chronological order.
Have a look at the section called 'Sort data' on this page of Firebase's documentation.
https://firebase.google.com/docs/database/ios/lists-of-data
It basically allows you to order the result of your query by a child of your choice. In your case you should order by 'date' or 'timestamp' depending on what you store on your Firebase Real-Time database.
Example:
// My top posts by number of stars
let myTopPostsQuery = (ref.child("user-posts").child(getUid())).queryOrdered(byChild: "starCount")
If when you order by child you still see the new entries coming at the bottom of your tableView, you can simply reverse your array to ensure the newest will come at the top.
yourArray.reversed()
Hope this helps.
Related
I have a UITableView and I want to delete the sections and rows. I am trying to delete the whole section with its rows inside it. Following are the cases which is confusing for me.
When I try to delete section from top to bottom or from bottom to top in order all sections gets deleted without error
When I try to delete sections in random i.e first I delete top first section and then when I try to delete 4th or 8th or last section the app crashes its own
Crash Log
Fatal error: Index out of range
here is how I am doing it:
func onHeaderSwippedRight(ClickedSection: IndexPath) {
dataSource.remove(at: ClickedSection.section)
// use the UITableView to animate the removal of this row
tableView.beginUpdates()
tableView.deleteSections(IndexSet.init(integer : ClickedSection.section), with: .automatic)
tableView.endUpdates()
}
The Index path that I am passing into this function onHeaderSwippedRight(ClickedSection: IndexPath) is passwed by delegated method. Bcz In my Class I am passing it while setting up my data of section
A raw guess of mine is that after removing the section tableview must update the indices of the section, because as I am removing data from data source it must not get Index out of bound issue if sections gets update themselves
please help and tell me what I am doing wrong
Update;
public class ListModel : Object {
var Id : String = ""
var UserId : Int64 = 0
var Name : String? = ""
var Items : [ListDetailServiceModel]? = []}
This is my model, and I am having list of this like so
let dataSource :[ListModel] = []
I have a chat feature in an iOS app. the chat previews are presented in a tableView and I am using a class for the TableView cells themselves. I want to listen for new messages so I can put a "new Message" label on the cells. The relevant parts of the tableView Cell are:
var chat: Chat! {
didSet {
self.updateUI()
self.observeNewMessages()
}
}
extension ChatTableViewCell {
func observeNewMessages() {
let chatMessageIdsRef = chat.ref.child("messageIds")
chatMessageIdsRef.observe(.childAdded, with: { snapshot in
let messageId = snapshot.value as! String
DatabaseReference.messages.reference().child(messageId).observe(.childAdded, with: { snapshot in
//let message = Message(dictionary: snapshot.value as! [String : Any])
print("looks like there is a new message") //works
self.messageLabel.text = "New Message!"
//self.delegate?.newMessage()
})
})
}
}
The print statement is there to test the listener. The problem I am having is when the table view loads and the cells get created, observeNewMessages() is executing the print statement regardless of whether there were new messages. It is just reading the old children that are already there first? Obviously this is going to cause the message label to always read "New Message." I'm not too good with Firebase so is there a better Firebase function to use or a better way to do this?
*I am using 2 queries because the Message dictionary will allow me to see if the current user made the message or not. The delegate is there because my next step will be to reload the tableview -- as it is now, this will also cause an infinite print statement loop...
I am using a Segmented controller to control how data is presented in my table view. One of the views should be chronologically which is easy because I use firebase so the query is effectively already chronological. However, I want to sort by highest rated which is a computed value (the "number of likes" is the count of a child which is an array of users who clicked "like". the table view controller instantiates an array:
var media = [Media]()
and a function fetches the media and ends like this:
let ref = DatabaseReference.media.reference()
let query = ref.queryOrdered(byChild: "uid").queryEqual(toValue: (value))
query.observe(.childAdded, with: { snapshot in
let media = Media(dictionary: snapshot.value as! [String : Any])
if !self.media.contains(media) {
self.media.insert(media, at: 0)
I think I need to make 2 different arrays and use a switch statement on segmented controller before the last 2 lines to populate them, then use switch statements in my TableView Data Source to read the associated arrays. Is this the right idea? If so, how do I sort snapshots?
I'm able to successfully update my entries in Firebase, for it shows up on the console, but not the table view. If I restart my app the changes will then show, but I want them to show immediately.
My approach is to edit the array that populates the tableview as soon possible under the "ChildChanged" notification.
ref.observeEventType(.ChildChanged, withBlock: { (snapshot) in
print("One of the entries were changed so we're reloading the table view")
if let firstname = snapshot.value?.objectForKey("firstname"), lastname = snapshot.value?.objectForKey("lastname")
{
let fullname = "\(firstname) \(lastname)"
names[I_Need_This_Index] = fullname
dispatch_async(dispatch_get_main_queue())
{
self.tableView.reloadData()
}
}
}, withCancelBlock: nil)
As you can see, I just need to locate index of what I need, for I already have the "fullname" value which is the edited value that I wish to update the table view with.
How would I go about doing that?
You need to lookup the index using the key of the changed child. Therefore a dictionary could be set up initially to map the keys for all children to the index of your tableview cell when receiving all values.
Edit:
If you need the old value itself you have to obtain the initial change snapshot yourself.
Solved:
Phillip Mills' solution has solved the issue presented in this post, as outlined below.
Currently, when a user searches for a beer in the application, the beer shows up in a UITableViewCell subclass, which displays the name of the beer and the brewery name in UILabel instances. The cell also contains four buttons below the labels: likeButton, tryButton, dislikeButton, and deleteButton.
When a user searches the API database for a beer, the user can then save a beer to a specific category in Core Data by using one of the buttons in the cell. These saved beers will then show up in a UITableView elsewhere in the app. I am able to successfully save and delete beers from the cells, and they do show up in the correct category's UITableView instance. However, if a returned beer result is not saved in Core Data, I want the deleteButton to not be shown in the cell that is populated from the JSON of the API because the app is set up for the user to save a beer, change a beer's category, or delete a beer from the search results UITableView instance.
I have the saving, changing of a beer's category, and deleting of a beer working correctly in both the category UITableView instances and in the search results UITableView. My issue arises when displaying the buttons in the results UITableView.
When results are returned from he API, I only want the deleteButton to be displayed if there is a beer saved in Core Data that matches the returned result. I'm guessing that I need to perform this comparison in cellForRowAtIndexPath:, but I feel like I am going about it incorrectly because I can get the deleteButton to either be visible or be hidden in all cells, regardless of whether the beer is saved in Core Data or not.
Here is my current implementation:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if scopeButtonIndex == 0 {
let beerCell = tableView.dequeueReusableCellWithIdentifier("foundBeerResultCell", forIndexPath: indexPath) as! BeerTableViewCell
let beer = foundBeerResults[indexPath.row]
beerCell.beerNameLabel.text = beer.name
beerCell.breweryNameLabel.text = beer.brewery
beerCell.delegate = self
for savedBeer in savedBeers {
if beer.name == savedBeer.beerName && beer.brewery == savedBeer.beerBrewery {
beerCell.deleteButton.hidden = false
} else if beer.name != savedBeer.beerName && beer.brewery != savedBeer.beerBrewery {
beerCell.deleteButton.hidden = true
}
}
return beerCell
}
}
Currently savedBeers in an array of the saved beers in Core Data. As you can see, I am taking each beer that is returned from the API and saved in foundBeerResults, which populates the results UITableView instance. I am then looping through savedBeers to see if each returned beer in foundBeerResults matches the saved beer. If the information matches, I want the deleteButton to be visible so that the user can delete the saved beer directly from the search results. If a returned beer result does not match a currently saved beer, I want the deleteButton to be invisible.
Am I incorrect by assuming that I should not be iterating through arrays in cellForRowAtIndexPath:? It seems inefficient. However, I am not sure of how to solve this problem.
Looping might or might not be a performance problem. You can measure for that but let's assume "not" for the moment since it seems like that would be a fairly small array you're using.
I think your problem is that you're not stopping the loop once you get a right answer.
How about:
beerCell.deleteButton.hidden = true
for savedBeer in savedBeers {
if beer.name == savedBeer.beerName && beer.brewery == savedBeer.beerBrewery {
beerCell.deleteButton.hidden = false
break
}
}