I am trying to display the user data in screen. But I always get an empty value. I don't know why.
var profileData = Profile(usrObj: [String:String]())
#IBOutlet weak var userName: UILabel!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
self.userName.text = profileData.FirstName
print(profileData.FirstName)
}
My print statement and my label value are empty. Please help me out with any mistake I am making.
My model class :
class Profile {
var FirstName: String
init(usrObj : [String: AnyObject]) {
self.FirstName = (usrObj["FirstName"] ?? "") as! String
}
var ProfileObject: [String:AnyObject] {
return ["FirstName" : self.FirstName]
}
In your LoginViewController save your data in NSUserDefaults
#IBAction func loginWithUserNamePassword(){
KRProgressHUD.show(progressHUDStyle: .White, message: "Loading...")
loginWithMailAndPassword((username.text?.trimWhiteSpace)!, password: (password.text?.trimWhiteSpace)!) { (user, error) in
if error != nil{
KRProgressHUD.dismiss()
SCLAlertView().showError("Login Error", subTitle: error!.localizedDescription)
}
else {
if user!.emailVerified
{
currentUser = user
fireBaseRef.child("Users").child(currentUser!.uid).child("UserProfile").observeSingleEventOfType(.Value, withBlock: { (snapshot) in
if let data: [String : AnyObject] = snapshot.value as? [String : AnyObject] {
let userDefaults = NSUserDefaults.standardUserDefaults()
userDefaults.setObject(data, forKey: "userdata")
userDefaults.synchronize()
enableSync()
self.navigateToNextScreen()
}
else{
}
})
}
else
{
SCLAlertView().showError("Login Error", subTitle: "This email is has not been verified yet")
}
}
}
}
and use that data in UserStaticDataViewController
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
self.profileDetailsExists = true
let userdata : NSDictionary = NSUserDefaults.standardUserDefaults().valueForKey("userdata") as! NSDictionary
print(userdata["City"] as? String)
self.userName.text = userdata["FirstName"] as? String
self.userCity!.text = userdata["City"] as? String
self.userCountry!.text = userdata.valueForKey("Country") as? String
self.userState.text = userdata.valueForKey("State") as? String
self.userMobileNo.text = userdata.valueForKey("Mobile") as? String
self.userGmail.text = userdata.valueForKey("Email") as? String
self.userDob.text = userdata.valueForKey("DateOfBirth") as? String
}
Output:
Just approve my answer and give vote.
Happy coding.
From the fact that your print statement is empty, it means that the userObject you pass into the following equation
var profileData = Profile(usrObj: [String:String]())
probably does not have a string value for the key "firstName".
You can try to verify this by changing your code
self.FirstName = (usrObj["FirstName"] ?? "") as! String
to
self.FirstName = (usrObj["FirstName"] ?? "Hello world") as! String
and see if "Hello world" is printed out in your console.
If yes, then you just have to make sure that the "user" object you pass into the init function of Profile class should be a dictionary where a value is stored for the key "FirstName"
Related
I know this has been asked on stack overflow several times but I cannot seem to find the answer I am looking for. I am trying to store data from a firebase database (using the observeSingleEvent(snapshot)) method in a global variable. See below code for details.
I've tried adding a completion handler and followed steps online, but doing so, the observeSingleEvent request stops working.
What I had before:
class ItemsTableViewController: UITableViewController {
let listToUsers = "ListToUsers"
var user: User!
let ref = Database.database().reference()
override func viewDidLoad() {
super.viewDidLoad()
let defaultUser = User()
ref.child("users").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
defaultUser.uid = uid
defaultUser.email = value?["email"] as? String ?? ""
defaultUser.name = value?["name"] as? String ?? ""
defaultUser.grad = value?["grad"] as? Int ?? 0
defaultUser.number = value?["number"] as? String ?? ""
defaultUser.image = UIImage(named: value?["image"] as? String ?? "")!
completion(defaultUser)
}) { (error) in
print("hello")
print(error.localizedDescription)
}
self.user = defaultUser
}
}
What I tried after and still did not work:
class ItemsTableViewController: UITableViewController {
// MARK: Constants
let listToUsers = "ListToUsers"
// MARK: Properties
var user: User!
let ref = Database.database().reference()
override func viewDidLoad() {
super.viewDidLoad()
let use = Auth.auth().currentUser?.uid
self.getUserData(uid:use!) { (user) -> () in
self.user = user
}
}
func getUserData(uid:String , completion: #escaping (User) -> ()) {
ref.child("users").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let defaultUser = User()
defaultUser.uid = uid
defaultUser.email = value?["email"] as? String ?? ""
defaultUser.name = value?["name"] as? String ?? ""
defaultUser.grad = value?["grad"] as? Int ?? 0
defaultUser.number = value?["number"] as? String ?? ""
defaultUser.image = UIImage(named: value?["image"] as? String ?? "")!
completion(defaultUser)
}){ (error) in
print("hello")
print(error.localizedDescription)
}
}
Firebase is asynchronous and the self.user = defaultUser in the first code block is going to be called way before Firebase has a chance to return data.
Data is ONLY valid within the closure following the Firebase call. So here's how to fix it. Oh, and remove that error code as it isn't needed.
Assume a user structure like this
users
uid_0
name: "some name"
email: "some email"
and the code to read in uid_0 and populate a global (i.e. class var) user
class MyUser {
var uid = ""
var name = ""
var email = ""
init(aUid: String, aName: String, aEmail: String) {
self.uid = aUid
self.name = aName
self.email = aEmail
}
}
var user: MyUser!
func readOneUser() {
let uid = "uid_0"
let thisUserRef = self.ref.child("users").child(uid)
thisUserRef.observeSingleEvent(of: .value, with: { snapshot in
//let uid = snapshot.key if you don't know the uid, it will be the key to the node
let name = snapshot.childSnapshot(forPath: "name").value as? String ?? "No Name"
let email = snapshot.childSnapshot(forPath: "email").value as? String ?? "No Email"
self.user = MyUser(aUid: uid, aName: name, aEmail: email)
print(self.user.name) //do something with the user here
})
}
and this line
defaultUser.image = UIImage(named: value?["image"] as? String ?? "")!
needs to be addressed but I don't know what the intention is. It could end up being nil if this happens
defaultUser.image = UIImage(named: "")!
so you may want to populate it with a default image of some kind or provide error handling in case it is nil.
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'm new to iOS development and I understand that allowing optional values when an object is initialized is not a 'good citizen' technique. That being said, I've read that it is good practice to always have values set, like this:
class Item{
var name: String
var color: String
init(name: String, color: String) {
self.name = name
self.color = color
}
}
This looks nice and tidy but how can I do something like that working with Firebase? Look what I've got so far:
private func loadPosts(){
databaseHandle = ref.child("users/\(self.user.uid)/posts").observe(.value, with:{(snapshot) in
var newPosts = [Post]()
for itemSnapShot in snapshot.children {
let post = Post(snapshot: itemSnapShot as! FIRDataSnapshot)
newPosts.append(post!)
}
self.posts = newPosts
self.tableView.reloadData()
})
}
This guy is placed in my PostsViewController where I have my table view. This is my model:
class Post {
var ref: FIRDatabaseReference?
var title: String?
var answer: String?
var contentUrl: String?
var photoUrl: String?
var createdAt: String?
var feeling: String?
var kind: String?
var text: String?
var uid: String?
var measurements: Dictionary<String, String>?
//MARK: Initialization
init?(snapshot: FIRDataSnapshot){
ref = snapshot.ref
let data = snapshot.value as! Dictionary<String, Any>
title = data["title"]! as? String
answer = data["answer"] as? String
contentUrl = data["content_url"] as? String
photoUrl = data["photo_url"] as? String
createdAt = data["created_at"] as? String
feeling = data["feeling"] as? String
kind = data["kind"] as? String
text = data["text"] as? String
uid = data["uid"] as? String
measurements = data["measurements"] as? Dictionary<String, String>
}
}
I don't know exactly why but those question marks doesn't feel quite right and now and then I get some nil pointer error, which I think I should be able to avoid by using the 'good citizen' technique.
So, does anybody know how can I use Firebase following Swift best practices?
Either you wish to allow the properties of your Post class to be nil or you don't.
If you do, that's fine. The code you posted allows any of them to be nil. You just need to safely access each property every time you need it.
If you don't, then don't make them optional. Then in your init you need to ensure none of the properties are set to nil by giving each a default if there is no value in the snapshot.
class Post {
var ref: FIRDatabaseReference
var title: String
var answer: String
var contentUrl: String
var photoUrl: String
var createdAt: String
var feeling: String
var kind: String
var text: String
var uid: String
var measurements: [String : String]
//MARK: Initialization
init?(snapshot: FIRDataSnapshot) {
if let data = snapshot.value as? [String : Any] {
self.ref = snapshot.ref
title = data["title"] as? String ?? ""
answer = data["answer"] as? String ?? ""
contentUrl = data["content_url"] as? String ?? ""
photoUrl = data["photo_url"] as? String ?? ""
createdAt = data["created_at"] as? String ?? ""
feeling = data["feeling"] as? String ?? ""
kind = data["kind"] as? String ?? ""
text = data["text"] as? String ?? ""
uid = data["uid"] as? String ?? ""
measurements = data["measurements"] as? [String : String] ?? [:]
} else {
return nil
}
}
}
Note how this ensures there is a proper snapshot. Note how a default value is set to each property if there is no value in the snapshot. Obviously you can assign any default you wish. I use the empty string as an example.
Even if you want to allow the properties to be nil, you should at least update your code to check for a valid snapshot like in the code above.
Of course you can have a combination where some properties can't be nil and some can. That's up to your needs.
First it is fine for you to have optionals in your data model, as long as you assign value to it later on in the future.
I would recommend to use ObserveSingleEvent() and you should make use of completion handler to make it easy. If you don't know completion handler: Link
I recommend:
• not to put database ref in your class model, and instead of using Dictionary<String, String>? just use [String: AnyObject]?
• make your post array public so that it can be accessed into the tableview.
Here's example:
class func getPosts(uid: String, _ completion: #escaping (_ posts: [Post]?, _ error: Error?) -> Void) {
//update inside users node
var posts = [Post]()
Firebase.databaseRef.child("users").child(uid).child("posts").observeSingleEvent(of: FIRDataEventType.value, with: { (dataSnapshot) in
guard let postsDictionary = dataSnapshot.value as? [String: AnyObject] else {
completion(nil, nil)
return
}
let n = postsDictionary.count
for postDictionary in postsDictionary {
let post = Post()
post.userID = uid
if let content = postDictionary.value["content"] as? String {
post.content = content
}
if let imageURL = postDictionary.value["imageURL"] as? String {
post.imageURL = imageURL
}
if let timeStamp = postDictionary.key as String! {
if let date = timeStamp.convertToDate() {
post.timeStamp = date
}
post.postIdentifier = timeStamp
}
posts.append(post)
if posts.count == n {
// Sort the array by the newest post
let sortedPosts = posts.sorted(by: { $0.timeStamp.compare($1.timeStamp) == .orderedDescending })
completion(sortedPosts, nil)
}
}
}) { (error) in
completion(nil, error)
}
}
Assigning to tableview be like:
getPosts(uid: Current.user.userID!) { (posts, error) in
guard error == nil else {
print(error.debugDescription)
return
}
cell.label.text = posts[indexPath.item].content
I'm trying to get access to the values stored in firebase dashboard to use them in different functions and methods in the class.
I used this method in this question
I have tried to print their values, the whole app has crashed and it gave me that their nil!
They are not nil actually!
I used a similar method in viewDidLoad and I could retrieve the values to labels!
let refer = FIRDatabase.database().reference().child("UserDevices")
var globalEmail : String!
var globalPhone : String!
var globalImageUrl: String!
override func viewWillAppear(_ animated : Bool){
super.viewWillAppear(animated)
retrieveUserData{(email,phone,ImageUrl) in
self.globalEmail = email
self.globalPhone = phone
self.globalImageUrl = ImageUrl
}
}
func retrieveUserData(_ completionBlock : #escaping ((_ email : String?, _ phone : String?, _ ImageUrl: String?)->Void)){
refer.child(byAppendingPath: self.strUserid as String).observe(.value , with: {snapshot in
if let userDict = snapshot.value as? [String:AnyObject] {
completionBlock(userDict["email"] as! String, userDict["phone"] as! String, userDict["ImageUrl"] as! String)
}
})
}
var strUserid : NSString!
override func viewDidLoad() {
super.viewDidLoad()
print(globalEmail)
print(globalImageUrl)
print(globalPhone)
self.navigationController?.navigationBar.tintColor = UIColor.white
print("idis \(self.strUserid)")
let ref = FIRDatabase.database().reference().child("UserDevices")
self.navigationController?.navigationBar.tintColor = UIColor.white
ref.child(byAppendingPath: self.strUserid as String).observe(.value, with: { snapshot in
if let dict = snapshot.value as? NSMutableDictionary{
print("dict is \(dict)")
if let Provider = dict["name"] as? String
{
self.DeviceDetailsProvider.text = Provider
// self.navigationItem.title = Provider
}
if let name = dict["DeviceName"] as? String
{
self.DeviceDetailsName.text = name
self.navigationItem.title = name
}
if let ShortDescription = dict["Description"] as? String
{
self.DeviceDetailsDescription.text = ShortDescription
}
if let City = dict["city"] as? String
{
self.DeviceDetailsCity.text = City
}
}
})
self.DeviceDetailsImageView.downloadedFrom(link: globalImageUrl)
}
Why I'm getting a crash here!
Change ref.child(byAppendingPath: self.strUserid as String)
To:-
ref.child(self.strUserid)
Also remove let refer = FIRDatabase.database().reference().child("UserDevices").
You can not initialise your reference globally outside a scope because you don't know in which order your classes are being initialised, and probable scenario is that your FIRDatabase hasn't even been initialised yet when you try to initialise let refer.
Instead of refer in retrieveUserData use
FIRDatabase.database().reference().child("UserDevices")
You see in a viewController's LIFE CYCLE, viewdidLoad is called before than viewWillAppear:
So what you need is:-
override func viewDidLoad() {
super.viewDidLoad()
..
retrieveUserData{(email,phone,ImageUrl) in
self.globalEmail = email
self.globalPhone = phone
self.globalImageUrl = ImageUrl
self.DeviceDetailsImageView.downloadedFrom(link: globalImageUrl)
// .. Do your stuff...
}
}
Read: Viewcontroller's Lifecycle
When a user signs in, their UID is set to standardUserDefaults(). Also, the users' profile data is saved under a child named their uid.
When the user creates a post, I would like to attach their username/display name to the post.
I've set up a function to fetch the current user's username, but whenever I submit a post, it seems as though the closure is not being executed.
The post model:
class PostModel {
var postBody = String()
var creationDate = String()
var postUID = String()
var userName = String()
init(postBody: String) {
self.postBody = postBody
let dateObject = NSDate()
let formatDate = timeToString(dateObject)
self.creationDate = formatDate
let userID = NSUserDefaults.standardUserDefaults().valueForKey("uid") as! String
self.postUID = userID
self.userName = getUsername(userID)
}
// Used to convert the model to json compatible before saving
func postToDictionary() -> NSDictionary {
let jsonBody = postBody
let jsonDate = creationDate
let jsonUID = postUID
let jsonUsername = userName
let postAsDictionary = ["Body": jsonBody, "Timestamp": jsonDate, "UID": jsonUID, "Display Name": jsonUsername]
return postAsDictionary
}
}
and the function to get the username:
func getUsername(withUID: String) -> String {
var userName = String()
DataService.ref.userRef.childByAppendingPath(withUID).observeSingleEventOfType(.Value, withBlock: { snapshot in
userName = snapshot.value.objectForKey("Display Name") as! String
})
return userName
}
I set up my login function to get the current user's display name and set it to the standardUserDefaults which worked. I believe this is my solution unless someone has a better suggestion
#IBAction func loginButton(sender: AnyObject) {
if emailField != nil && passwordField != nil {
let emailAttempt = emailField.text!
let passwordAttempt = passwordField.text!
DataService.ref.baseRef.authUser(emailAttempt, password: passwordAttempt) {
error, authData in
if error != nil {
print("error in data check")
} else {
let returnUID = authData.uid
NSUserDefaults.standardUserDefaults().setValue(returnUID , forKey: "uid")
DataService.ref.userRef.childByAppendingPath(returnUID).childByAppendingPath("Display Name").observeSingleEventOfType(.Value, withBlock: { snapshot in
let UserDisplayName = snapshot.value as! String
NSUserDefaults.standardUserDefaults().setValue(UserDisplayName, forKey: "displayName")
self.performSegueWithIdentifier("loginSuccessSegue", sender: sender)
})
}
}
} else {
print("error")
return
}
}