Update Firebase data in tableView cell on tap - ios

I am using Firebase real time database to display posts in a tableView. I want to increase the number of likes of a specific post when the user double taps the corresponding cell.
I got the double tap working and am already printing out the correct indexPath.
override func viewDidLoad() {
super.viewDidLoad()
// double tap
let doubleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap(sender:)))
doubleTapGestureRecognizer.numberOfTapsRequired = 2
postTableView.addGestureRecognizer(doubleTapGestureRecognizer)
}
And here's what I tried according to the Firebase documentation to update the likes:
func handleDoubleTap(sender: UITapGestureRecognizer) {
let touchPoint = sender.location(in: postTableView)
if let indexPath = postTableView.indexPathForRow(at: touchPoint) {
print(indexPath)
let post = posts[indexPath.row]
let oldLikes = post.likes
let newLikes = oldLikes! + 1
let postUpdates = ["\(post.likes)": newLikes]
database.updateChildValues(postUpdates)
postTableView.reloadData()
}
}
It doesn't throw any errors but is not working.
This is the database structure:
And here's how I declared the database:
struct post {
let author : String!
let creationDateTime : String!
let content : String!
let likes : Int!
}
And in viewDidLoad
let database = FIRDatabase.database().reference()
This is how I create a post:
#IBAction func savePost(_ segue:UIStoryboardSegue) {
let addPostVC = segue.source as! AddPostViewController
let author = currentUser.displayName
let date = Date()
let formatter = DateFormatter()
formatter.dateFormat = "dd.MM.yyyy"
let dateResult = formatter.string(from: date)
let creationDateTime = "\(dateResult)"
let content = addPostVC.passTextContent
let likes = 0
let post : [String : AnyObject] = ["author" : author as AnyObject,
"content" : content as AnyObject,
"creationDateTime" : creationDateTime as AnyObject,
"likes" : likes as AnyObject]
database.child("Posts").childByAutoId().setValue(post)
}
And this how I retrieve the data in viewDidLoad
database.child("Posts").queryOrderedByKey().observe(.childAdded, with: {
snapshot in
let postID = (snapshot.value as? NSDictionary)?["postID"] as? String ?? ""
let author = (snapshot.value as? NSDictionary)?["author"] as? String ?? ""
let content = (snapshot.value as? NSDictionary)?["content"] as? String ?? ""
let creationDateTime = (snapshot.value as? NSDictionary)?["creationDateTime"] as? String ?? ""
let likes = (snapshot.value as? NSDictionary)?["likes"] as? Int ?? 0
self.posts.insert(post(postID: postID, author: author, creationDateTime: creationDateTime, content: content, likes: likes), at: 0)
self.postTableView.reloadData()
})

You have a problem with your database reference. When you do let database = FIRDatabase.database().reference() then you are refering to the main node in your database structure. This means that you will be working with the structure under the root in your database Json. The only child of it will be the Posts key.
When you do
let postUpdates = ["\(post.likes)": newLikes]
database.updateChildValues(postUpdates)
you are trying to update the value under the root node, which clearly does not exist. The only reference it can find is the key Posts.
In order to perform the update in the correct place, you could get child references from your main reference, especially one to the post you are interested in updating.
For example, you could do the following:
let postReference = database.child("Here goes the post Id").
Then, you will be able to use updateChildValues correctly on this new reference, since it will be updating the specific post.
Another thing that may be used wrong is the dictionary that is being sent to the updateChildValues. The structure of the dictionary that you have to provide is the following:
["key that you want to update": new value]
So in your case, instead of providing the previous like count and the new like count, you should provide a dictionary as the following:
let postUpdates = ["likes": newLikes]

Related

How to save tableView separation after restart when connected to Firebase

I am new to programming and currently trying things with firebase. I am building a newsfeed like app, which displays custom type cells in my tableView. I currently have the problem that after an app restart the separation is like the one in firebase. But I want the newest be first and stay there. I thought about saving the array in core data, but I don't like this option as much. So my question is: how do I sort the posts from newest to oldest?
var ref: DatabaseReference!
var posts = [String]()
var timeRef = [String]()
var userRef = [String]()
override func viewDidLoad() {
super.viewDidLoad()
addSlideMenuButton()
ref = Database.database().reference()
ref.child("posts").observe( .childAdded, with: { snapshot in
let newPost = snapshot.value as? NSDictionary
let post_title:String = newPost!["post_title"] as? String ?? "error"
let newPostTime = snapshot.value as? NSDictionary
let post_time:String = newPostTime!["post_time"] as? String ?? "error"
let newPostUser = snapshot.value as? NSDictionary
let post_user:String = newPostUser!["post_user"] as? String ?? "Team"
self.posts.insert(post_title, at: 0)
self.timeRef.insert(post_time, at: 0)
self.userRef.insert(post_user, at: 0)
self.newsfeedTableView.reloadData()
})
}
(Tell me if you need more code)
I'm not familiar with firebase but it might have something built in that might help you with that task as it's a common one.
Anyway, this is how you may do it with your method:
let isoFormatter = ISO8601DateFormatter()
// dateStrings will be fetched from your database
let dateStrings = [
"2018-12-07T09:21:42Z",
"2018-12-07T09:19:42Z",
"2018-12-07T09:20:42Z"
]
// here we're converting the strings to an actual Date types
let dates = dateStrings.compactMap { isoFormatter.date(from: $0) }
// sorting in descending order
let sortedDates = dates.sorted { $0 > $1 }
print(sortedDates)
/*
prints-
[
2018-12-07 09:21:42 +0000,
2018-12-07 09:20:42 +0000,
2018-12-07 09:19:42 +0000
]
*/
You can use isoFormatter.string(from: Date) to upload a date to your database

Access childAutoID to update selected child value in Firebase

In order to populate my tableView, I append items (created from a struct) to a local array:
func loadList() {
var newAnnotations: [AnnotationListItem] = []
if let uid = Auth.auth().currentUser?.uid {
uidRef.child(uid).child("annotations").queryOrderedByKey().observeSingleEvent(of: .value, with: {snapshot in
for item in snapshot.children {
let annotationItem = AnnotationListItem(snapshot: item as! DataSnapshot)
newAnnotations.append(annotationItem)
}
annotationList = newAnnotations
self.tableView.reloadSections([0], with: .fade)
})
}
}
When I click a specific row, I am taken to a DetailViewController where it is only a large UITextView (named notes). The UITextView.text displayed is based on the selected indexPath.row and the "notes" value is retrieved from the array. Now the user is able to type some text and when they are done, the textViewDidEndEditing function is called:
func textViewDidEndEditing(_ textView: UITextView) {
notes.resignFirstResponder()
navigationItem.rightBarButtonItem = nil
let newNotes = self.notes.text
print(newNotes!)
}
Now I'd like to updateChildValues to newNotes to the child node "notes" in my JSON:
"users" : {
"gI5dKGOX7NZ5UBqeTdtu30Ze9wG3" : {
"annotations" : {
"-KuWIRBARv7osWr3XDZz" : {
"annotationSubtitle" : "1 Cupertino CA",
"annotationTitle" : "Apple Infinite Loop",
"notes" : "Does it work?!",
}
How can I access the selected autoID so I can update the specific notes node. So far the best I have is:
guard let uid = Auth.auth().currentUser?.uid else { return }
uidRef.child(uid).child("annotations").(somehow access the specific childID).updateChildValues(["notes": newNotes])
Any help will be greatly appreciated. Thanks in advance
UPDATE
The annotationListItem struct is created:
struct AnnotationListItem {
let key: String?
var annotationTitle: String?
let annotationSubtitle: String?
let notes: String?
let ref: DatabaseReference?
init(key: String = "", annotationTitle: String, annotationSubtitle: String, notes: String) {
self.key = key
self.annotationTitle = annotationTitle
self.annotationSubtitle = annotationSubtitle
self.notes = notes
self.ref = nil
}
init(snapshot: DataSnapshot) {
key = snapshot.key
let snapshotValue = snapshot.value as! [String: AnyObject]
annotationTitle = snapshotValue["annotationTitle"] as? String
annotationSubtitle = snapshotValue["annotationSubtitle"] as? String
notes = snapshotValue["notes"] as? String
ref = snapshot.ref
}
init(Dictionary: [String: AnyObject]) {
self.key = Dictionary["key"] as? String
self.annotationTitle = Dictionary["annotationTitle"] as? String
self.annotationSubtitle = Dictionary["annotationSubtitle"] as? String
self.notes = Dictionary["notes"] as? String
self.ref = nil
}
func toAnyObject() -> Any {
return [
"annotationTitle": annotationTitle as Any,
"annotationSubtitle": annotationSubtitle as Any,
"notes": notes as Any
]
}
}
UPDATE
This is how the annotationListItem is created to be stored in Firebase:
// Using the current user’s data, create a new AnnotationListItem that is not completed by default
let uid = Auth.auth().currentUser?.uid
guard let email = Auth.auth().currentUser?.email else { return }
let title = placemark.name
let subtitle = annotation.subtitle
let notes = ""
// declare variables
let annotationListItem = AnnotationListItem(
annotationTitle: title!,
annotationSubtitle: subtitle!,
notes: notes)
// Add the annotation under their UID
let userAnnotationItemRef = uidRef.child(uid!).child("annotations").childByAutoId()
userAnnotationItemRef.setValue(annotationListItem.toAnyObject())
I think you only need to do this:(since you have declared the note as global)
guard let uid = Auth.auth().currentUser?.uid else { return }
uidRef.child(uid).child("annotations").(note.key).updateChildValues(["notes": newNotes])
inside the method where you change the notes
If I am not mistaken you are creating an array of a custom object?
var newAnnotations: [AnnotationListItem] = []
You could do something like: var newAnnotations: [(key: String, value: [String : Any])] = [] (Any only if you are going to have Strings, Integers, ect. If it'll only be String then specify it as a String.
Accessing the key would be: newAnnotations[indexPath.row].key in your cellForRowAtIndex of your tableView. Accessing values would be: newAnnotations[indexPath.row].value["NAME"].
You can have a separate array that holds the key and just append it at the same time as your population:
for item in snapshot.children {
guard let itemSnapshot = task as? FDataSnapshot else {
continue
}
let id = task.key //This is the ID
let annotationItem = AnnotationListItem(snapshot: item as! DataSnapshot)
newAnnotations.append(annotationItem)
}
Another thing you could do is go up one more level in your firebase call:
if let uid = Auth.auth().currentUser?.uid {
uidRef.child(uid).child("annotations").observeSingleEvent(of: .value, with: {snapshot in
if snapshot is NSNull{
//Handles error
} else{
if let value = snapshot.value as? NSDictionary{ //(or [String: String]
//set localDictionary equal to value
}
}
self.tableView.reloadSections([0], with: .fade)
})
}
And then when you select a row: let selectedItem = localDictionary.allKeys[indexPath.row] as! String //This is the ID you pass to your viewController.

How to retrieve certain childByAutoId() key

I have looked around for a while, with no luck. I need to get the string value of a certain dictionary key in firebase.
If you look at the image below,I need that key on top, and need to set it equal to a string, that I can segue to other viewcontrollers so when a user wants to make a post under the sharers for example, I go into the right value based on the key, then to sharers, where I can add values. Thanks, also I do not need all values, just once I have observedWithSingleEvent, I need to get the key of each page or dictionary.
my code:
let ref = FIRDatabase.database().reference()
ref.child("Notes").queryOrderedByKey().observeSingleEvent(of: .value, with: { snapshot in
if let pagers = snapshot.value as? [String : AnyObject] {
let numb = snapshot.key //what I want
for (_, val) in pagers {
if let useri = val["postUsername"] as? String {
if useri == FIRAuth.auth()?.currentUser?.uid {
let bindfl = Page()
if let title = val["title"] as? String, let descript = val["description"] as? String,
let sharers = val["sharers"] as? [String], let poster = val["postUsername"] as? String, let setter = val["setter"] as? String, let creatorName = val["creatorName"] as? String {
bindfl.title = title
bindfl.descriptions = descript
bindfl.setter = setter
bindfl.sharers = sharers
bindfl.usernameOfBinder = poster
bindfl.creatorName = creatorName
bindfl.theBit = numb
self.pages.append(bindfl)
Well you can do it like this, when you add data to the database
let key = ref.("Notes").childByAutoId().key
let notes = ["userName": userName,
"description": description,
"title": title,
"author": author,
"keyID": key]
let childUpdates = ["/Notes/\(key)": notes]
ref.updateChildValues(childUpdates)
After that you can, get the key like this
bindfl.title = title
bindfl.descriptions = descriptions
bindfl.userName = userName
bindfl.author= author
bindfl.keyID= keyID
Hope it worked.
ref.child("Notes"). observeSingleEvent(of: .childAdded, with: {snapshot in
print("\(snapshot.key)")
})
Will give you the autoID key of every entry in the table
ref.child("Notes").queryOrdered(byChild: "creatorName").queryEqual(toValue: "posterName").observeSingleEvent(of: .childAdded, with: {snapshot in
var postID = snapshot.key
//update post different post using retrieved ID
let infoToAdd = ["newPostStuff" : true]
FIREBASE_REF!.child("Posts/\(postID)").updateChildValues(infoToAdd)
})
Will give you the auto ID of every post by a given creator and then update values on a different table with the retrieved key

Firebase Sorting

Basically, I have an app on Firebase. The thing is, when Firebase sorts the data, instead of a chronological order, it muddles the data.
When I went online and search why, I found that it was because I was using the snapshot.value instead of snapshot.children.
However, I'm not completely sure how to change the code accordingly, could someone help?
Here is the code:
func retrieveChatLog() {
FIRDatabase.database().reference().child("chats").child(chatID).observe(.value, with: {(snapshot) in
let chats = snapshot.value as! [String : AnyObject]
self.messages.removeAll()
for (_, value) in chats {
if let sender = value["sender"], let message = value["message"], let senderID = value["senderUID"], let date = value["date"] {
let messageToShow = Message()
messageToShow.message = message as! String
messageToShow.sender = sender as! String
messageToShow.senderUID = senderID as! String
let formatter = DateFormatter()
formatter.dateFormat = "dd.MM.yyyy"
let curDate = formatter.date(from: date as! String)
messageToShow.date = curDate as! Date
if messageToShow.senderUID != "" {
self.messages.append(messageToShow)
}
}
}
self.tableView.reloadData()
})
FIRDatabase.database().reference().removeAllObservers()
}
The problem isn't because you use snapshot.value. You can use .value or .childAdded. The problem is, if you use .value, you don't want to cast the snapshots to a dictionary because dictionaries don't preserve order. Instead, you're going to want to cast to an array to preserve order. Here's one way you could resolve this:
FIRDatabase.database().reference().child("chats").child(chatID).observe(.value, with: {(snapshot) in
let chats = snapshot.children.allObjects as! [FIRDataSnapshot]
self.messages.removeAll()
for chat in chats {
if let value = chat.value as? [String: AnyObject] {
if let sender = value["sender"], let message = value["message"], let senderID = value["senderUID"], let date = value["date"] {
let messageToShow = Message()
messageToShow.message = message as! String
messageToShow.sender = sender as! String
messageToShow.senderUID = senderID as! String
let formatter = DateFormatter()
formatter.dateFormat = "dd.MM.yyyy"
let curDate = formatter.date(from: date as! String)
messageToShow.date = curDate as! Date
if messageToShow.senderUID != "" {
self.messages.append(messageToShow)
}
}
}
}
self.tableView.reloadData()
})
FIRDatabase.database().reference().removeAllObservers()
}
Change this line
FIRDatabase.database().reference().child("chats").child(chatID).observe(.value, with: {(snapshot) in...
To this
FIRDatabase.database().reference().child("chats").child(chatID).observe(.childAdded, with: {(snapshot) in
There is an accepted answer but I wanted to provide another solution that may be a bit tighter and more expandable.
It starts with a class to hold the messages. In this case I am keeping just the firebase key and timestamp but other vars could easily be added. You'll note the timestamp var that is read from Firebase in the same format: dd.mm.yy. This would be useful for sorting if needed. Also note that if you want to display a nicely formatted mm/dd/yyyy format, it's available though the formattedDate computed property.
The messagesArray is an array of MessageClass objects which can be used as a datasource for a tableView for example.
Finally, the loadMessages function to load in all of the messages. As mentioned in the other answer, casting the Snapshot to a dictionary loses ordering guarantee. However, if we iterate over the snapshot directly, the ordering stays intact.
class MessageClass {
var timestamp: String
var fbKey: String
var formattedDate: String {
let formatter = DateFormatter()
formatter.dateFormat = "dd.MM.yyyy"
let d = formatter.date(from: timestamp)
let outputFormattter = DateFormatter()
outputFormattter.dateFormat = "MM/dd/yyyy"
let finalDate = outputFormattter.string(from: d!)
return finalDate
}
init(withSnap: DataSnapshot ) {
let snapDict = withSnap.value as! [String: AnyObject]
self.timestamp = snapDict["timestamp"] as! String
self.fbKey = withSnap.key
}
}
var messagesArray = [MessageClass]()
func doButton0Action() {
let messagesRef = self.ref.child("messages")
messagesRef.observe(.value, with: { snapshot in
for child in snapshot.children {
let snap = child as! DataSnapshot
let m = MessageClass(withSnap: snap)
self.messagesArray.append(m)
}
for msg in self.messagesArray { //test code to print the array once loaded
print("key: \(msg.fbKey) date: \(msg.formattedDate)")
}
})
}
This code is very verbose and could be condensed considerably but it's very readable. (it needs guards and error checking as well).
I would strongly encourage you to store your timestamps in a
yyyymmddhhmmss
format in firebase. It lends itself to sorting / querying
Also, as a side-note, instead of relying on the date the node was created (by key) to keep your ordering, consider leveraging the timestamp when reading in the nodes using .order(by: timestamp). That will guarantee they are always in the correct order even if messages are changed around or the keys are modified.

how to retrieve child(array) inside another firebase child

I am trying to print array from the firebase. Actually if we tap a medication in a list(tableviewcontroller), it will show its specfic dosages. I got stucked to retrieve the dosages list. Here is my code to get data from firebase. Any help is appreciated. Thanks in advance. My firebase structure looks like this.. firebase img
func loadDataFromFirebase() {
databaseRef = FIRDatabase.database().reference().child("medication")
databaseRef.observeEventType(.Value, withBlock: { snapshot in
for item in snapshot.children{
FIRDatabase.database().reference().child("medication").child("options").observeEventType(.Value, withBlock: {snapshot in
print(snapshot.value)
})
}
})
You should take a look on firebase documentation https://firebase.google.com/docs/database/ios/read-and-write
but if I'm understanding your idea, you probably has a model class for your medications. So, to retrieve your data you should do like this for Swift 3.0:
func loadDataFromFirebase() {
databaseRef = FIRDatabase.database().reference().child("medication")
databaseRef.observe(.value, with: { (snapshot) in
for item in snapshot.children{
// here you have the objects that contains your medications
let value = item.value as? NSDictionary
let name = value?["name"] as? String ?? ""
let dossage = value?["dossage"] as? String ?? ""
let type = value?["type"] as? String ?? ""
let options = value?["options"] as? [String] ?? ""
let medication = Medication(name: name, dossage: dossage, type: type, options: options)
// now you populate your medications array
yourArrayOfMedications.append(medication)
}
yourTableView.reloadData()
})
}
Now that you have your array with all your medications, you just need to populate your tableView with this medications. When someone press an item on table you can just call prepareForSegue: and send your yourArrayOfMedications[indexPath.row].options to the next view
The solution is same as above but with a small change.
func loadDataFromFirebase() {
databaseRef = FIRDatabase.database().reference().child("medication")
databaseRef.observe(.value, with: { (snapshot) in
for item in snapshot.children{
// here you have the objects that contains your medications
let value = item.value as? NSDictionary
let name = value?["name"] as? String ?? ""
let dossage = value?["dossage"] as? String ?? ""
let type = value?["type"] as? String ?? ""
let options = value?["options"] as? [String : String] ?? [:]
print(options["first"]) // -> this will print 100 as per your image
// Similarly you can add do whatever you want with this data
let medication = Medication(name: name, dossage: dossage, type: type, options: options)
// now you populate your medications array
yourArrayOfMedications.append(medication)
}
yourTableView.reloadData()
})
}

Resources