I have a view controller with three text fields. When the user uses the app for the first time, they enter some information in those text fields and press done, then the information goes to Firebase database:
FIRDatabase.database().reference().child("info").child(self.loggedInUser!.uid).setValue(saveObject)
However, the user also has the ability to edit those three text fields. As of now, when the user edits one text field and clicks done. The information of the non-edited text fields appear in their own text fields and then the user has to click the done button again for the edited text field to be sent to Firebase:
if City.text==""
{
FIRDatabase.database().reference().child("info").child(self.loggedInUser!.uid).observeSingleEvent(of: .value, with: { (snapshot:FIRDataSnapshot) in
if let postsDictionary = snapshot .value as? [String: AnyObject] {
let city = postsDictionary["City"] as? String ?? ""
self.City.text = city
}})
}
if Major.text==""
{
FIRDatabase.database().reference().child("info").child(self.loggedInUser!.uid).observeSingleEvent(of: .value, with: { (snapshot:FIRDataSnapshot) in
if let postsDictionary = snapshot .value as? [String: AnyObject] {
let major = postsDictionary["Major"] as? String ?? ""
self.Major.text = major
}})
}
if College.text==""
{
FIRDatabase.database().reference().child("info").child(self.loggedInUser!.uid).observeSingleEvent(of: .value, with: { (snapshot:FIRDataSnapshot) in
if let postsDictionary = snapshot .value as? [String: AnyObject] {
let college = postsDictionary["College"] as? String ?? ""
self.College.text = college
}})
}
if College.text=="" || Major.text=="" || City.text==""
{
FIRDatabase.database().reference().child("info").child(self.loggedInUser!.uid).observeSingleEvent(of: .value, with: { (snapshot:FIRDataSnapshot) in
})
}else{
if let City = City.text{
if let Major = Major.text{
if let College = College.text{
let saveObject: Dictionary<String, Any> = [
"uid": uid,
"City" : City,
"Major" : Major,
"College" : College
]
FIRDatabase.database().reference().child("info").child(self.loggedInUser!.uid).setValue(saveObject)
I don't think this is very user-friendly, I would like the user to edit whatever text field they want and I do not want the non-edited text field values to appear in their text fields and I would like the user to click on the done button once for the newly edited text field value to be sent to firebase.
Just create this func in viewdidload. This will load your current properties in firebase. You should adopt the UITextFieldDelegate and add the function textDidBegin which then you can make the textField (sender as! textField class) blank instead of having these loaded properties.
func reloadFirDatabase()
{ FIRDatabase.database().reference().child("info").child(self.loggedInUser!.uid).observeSingleEvent(of: .value, with: { (snapshot:FIRDataSnapshot) in
if let postsDictionary = snapshot .value as? [String: AnyObject] {
let city = postsDictionary["City"] as? String ?? ""
let major = postsDictionary["Major"] as? String ?? ""
let college = postsDictionary["College"] as? String ?? ""
if City.text.isEmpty {
self.City.text = city
{
if Major.text.isEmpty {
self.Major.text = major
}
if College.text.isEmpty {
self.College.text = college
}
}})
}
When you press done you should still load this in:
if let City = City.text{
if let Major = Major.text{
if let College = College.text{
let saveObject: Dictionary<String, Any> = [
"uid": uid,
"City" : City,
"Major" : Major,
"College" : College
]
FIRDatabase.database().reference().child("info").child(self.loggedInUser!.uid).setValue(saveObject)
I can guarantee you that doing it this way you only need to press the done button once.
Related
enter image description here
I have a userProfile file:
class UserProfile {
var uid:String
var email: String
var username:String
var photoURL:URL
init(uid:String, email:String, username:String, photoURL:URL) {
self.uid = uid
self.email = email
self.username = username
self.photoURL = photoURL
}
}
and a Post file
class Post {
var id:String
var author:UserProfile
var text:String
var timestamp:Date
init(id:String, author:UserProfile, text:String,timestamp:Double) {
self.id = id
self.author = author
self.text = text
self.timestamp = Date(timeIntervalSince1970: timestamp / 1000)//divided by 1000 because firebase stores dates as milliseconds
}
}
This is the way it shows in firebaseenter image description here
So what I'm trying to do is reuse the references(username, date, and urlimage) that the two files before uses.
Here the code thats used for the post file:
func oberseverRoomatePostFeed(){
let postRef = Database.database().reference().child("posts")
postRef.observe(.value, with: { snapshot in
var currentUserRoomatePose = [Post]()//temporary array
//array****************************
for child in snapshot.children {
if let roommateSnapshot = child as? DataSnapshot,
let dict = roommateSnapshot.value as? [String:Any],
let author = dict["author"] as? [String:Any],
let uid = author["uid"] as? String,
let photoURL = author["photoURL"] as? String,
let email = author["email"] as? String,
let username = author["username"] as? String,
let url = URL(string:photoURL),
let text = dict["text"] as? String,
let timeStamp = dict["timestamp"] as? Double{
let userP = UserProfile(uid: uid, email: email, username: username, photoURL: url)
let post = Post(id: roommateSnapshot.key, author: userP, text: text, timestamp: timeStamp)
currentUserRoomatePose.append(post)
}
}
self.posts = currentUserRoomatePose
self.tableView.reloadData()
})
}
And here is what I have so far
class User: NSObject {
var name: String?
var currentUser: UserProfile
var currentPost: Post
init(dictionary: [String: Any], currentUser:UserProfile, currentPost:Post) {
self.name = dictionary["name"] as? String ?? ""
self.currentUser = currentUser
self.currentPost = currentPost
}
}
and
func fetchUser() {
let postRef = Database.database().reference().child("users")
postRef.observe(.value, with: { snapshot in
var currentUsers = [User]() // temp array
for child in snapshot.children {
if let userSnapshot = child as? DataSnapshot,
let dict = userSnapshot.value as? [String: Any],
let author = dict["author"] as? [String:Any],
let uid = author["uid"] as? String,
let photoURL = author["photoURL"] as? String,
let email = author["email"] as? String,
let username = author["username"] as? String,
let url = URL(string:photoURL),
let text = dict["text"] as? String,
let timeStamp = dict["timestamp"] as? Double {
let userP = UserProfile(uid: uid, email: email, username: username, photoURL: url)
let user = User(dictionary: [String : Any], currentUser: userP, currentPost: Post)
}
}
})
}
enter image description here
enter image description here
func checkLogin() {
if Auth.auth().currentUser?.uid == nil {
perform(#selector(backButton), with:nil, afterDelay:0)
} else {
let uid = Auth.auth().currentUser?.uid
Database.database().reference().child("Users/profile/").child(uid!).observeSingleEvent(of: .value, with: {(snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
self.navigationItem.title = dictionary["username"] as? String
}
}, withCancel: nil)
}
}
"Users" : {
"kFjK5Kcrk7dLCnd3fQOnhBcPQHz1" : {
"Email" : "cmhughes95#gmail.com",
"Full Name" : "Cameron Hughes",
"Google UID" : "112185374105612274429",
"provider" : "Google"
},
"profile" : {
"0Ef8GJch5PPZ8yE9jLSXAS7fVoK2" : {
"email" : "teclarke#aggies.ncat.edu",
"password" : "Tecl6013",
"photoURL" : "https://firebasestorage.googleapis.com/v0/b/aggie-wallet.appspot.com/o/user%2F0Ef8GJch5PPZ8yE9jLSXAS7fVoK2?alt=media&token=62827fc7-38ec-47ae-9972-c078ef1d486e",
"username" : "tec95"
},
"EsqtPIFUWQbXh0ItLWK0W3qxOdI2" : {
"email" : "teclarke#aggies.ncat.edu",
"password" : "Tecl6013",
"photoURL" : "https://firebasestorage.googleapis.com/v0/b/aggie-wallet.appspot.com/o/user%2FEsqtPIFUWQbXh0ItLWK0W3qxOdI2?alt=media&token=40c82e6e-cc4d-4320-a0ab-434cc297567a",
"username" : "tyrek95"
},
Right off, there appears to be an issue with the users node. You've got what looks like a uid at the same level as a node called 'profile' which then contains other user id's. That's not going to work. The uid's should all be at the same level. The nodes also contain different child nodes so it's unclear what the purpose is. This would be a better structure:
users
uid_0
email: "test#test.com"
photoURL: "https://www.xxxxxx"
username: "tyrek95"
uid_1
email: "yipee#yipee.com"
photoURL: "https://www.yyyyy"
username: "someusername"
Based on comments, it appears you're trying to get a single user name to put in a titlebar - there's a whole lot of extraneous code in the question if that's the task. The checkLogin and fetchUser functions aren't called and while denormalization in the posts node is fine, it's unnecessary duplicate data - you don't need to have the email, photoURL duplicated as you know the uid and can get that from the users node
A better structure is
posts
post_0
author: "uid_0"
text: "Hello, World"
timestamp: "some time"
post_1
author: "uid_1"
text: "What's happening?"
timestamp: "some time"
To keep it simple, let's get one post and the associated user and print out what that user said in their post.
let usersRef = self.ref.child("users")
let postsRef = self.ref.child("posts")
let postNum = "post_0"
let postToGetRef = postsRef.child(postNum)
postToGetRef.observeSingleEvent(of: .value, with: { postSnap in
let postDict = postSnap.value as! [String: Any]
let uid = postDict["author"] as! String
let postText = postDict["text"] as! String
let userToGetRef = usersRef.child(uid)
userToGetRef.observeSingleEvent(of: .value, with: { userSnap in
let userDict = userSnap.value as! [String: Any]
let userName = userDict["username"] as! String
print("\(userName) said \(postText)") //here you put the name in the title bar
})
})
and the output is
tyrek95 said Hello, World
I did this for a single post but it could be easily expanded by using .value on the posts node, which will read in all of the posts, and then iterate over them in a for..loop to get the post information and the user for each post.
Note there's no error checking here for brevity.
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.
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
I need to make multiple observations, but I don't know how.
Here is my database structure:
"Posts" : {
"f934f8j3f8" : {
"data" : "",
"date" : "",
"userid" : ""
}
},
"Users" : {
"BusWttqaf9bWP224EQ6lOEJezLO2" : {
"Country" : "",
"DOB" : "",
"Posts" : {
"f934f8j3f8" : true
},
"Profilepic" : "",
"name" : "",
"phonenumber" : ""
}
I want to observe the posts and I write the code and it works great, but I also want to get the name of the user who posted this post but when I wrote save the name and use it it gives me null. Here is my code.
DataServices.ds.REF_POSTS.queryOrderedByKey().observe(.value,
with: { (snapshot) in
self.posts = []
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot {
if let postsDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let userID = "BusWttqaf9bWP224EQ6lOEJezLO2"
DataServices.ds.REF_USERS.child(userID).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let postusername = value?["name"] as? String ?? ""
})
print(" ------ User name : \(postusername) ------")
})
print(" ------ User name 2 : \(postusername) ------")
let post = Posts(postKey: key, postData: postsDict)
self.posts.append(post)
The first print statement prints the username, but the second one prints nothing.
Thanks in advance.
Firebase is asynchronous so you can't operate on a variable until Firebase populates it within it's closure. Additionally code is faster than the internet so any statements following a closure will occur before the statements within the closure.
The flow would be as follows
Query for the post {
get the user id from the post inside this closure
query for the user info {
create the post inside this second closure
append the data to the array inside this second closure
reload tableview etc inside this second closure
}
}
Something like this edited code
self.posts = []
myPostsRef.queryOrderedByKey().observe(.value, with: { (snapshot) in
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot {
if let postsDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let userID = "BusWttqaf9bWP224EQ6lOEJezLO2"
myUsersRef.child(userID).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let userName = value?["name"] as? String ?? ""
let post = Posts(postKey: key, postData: postsDict, name:userName)
self.posts.append(post)
})
}
}
}
})
You're not using the postusername inside the closure so I added that to the Posts initialization.
Also, the self.posts = [] is going to reset the posts array any time there's a change in the posts node - you may want to consider loading the array first, and then watch for adds, changes, or deletes and just update the posts array with single changes instead of reloading the entire array each time.
Edit:
A comment was made about the data not being available outside the loop. Here is a very simplified and tested version. Clicking button one populates the array from Firebase with a series of strings, clicking button 2 prints the array.
var posts = [String]()
func doButton1Action() {
let postsRef = ref.child("posts")
self.posts = []
postsRef.observe(.value, with: { (snapshot) in
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot {
let value = snap.value as! String
self.posts.append(value)
}
}
})
}
func doButton2Action() {
print(posts)
}
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()
})
}