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
}
}
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.
I'm searching for a way to get added/deleted/modified contacts to send them to the server ..
I used Realm since it's faster than coreData to save the contacts
and on each refresh or when user re-enter the app .. I'm comparing the Realm Database (backup) with the sim contacts to detect if there is a change (insertion - modification - deletion)..
the code is working fine
but it isn't fast enough ..
I tried using the ABAddressBookRegisterExternalChangeCallback in an objective C file but it wasn't handy since it's being called only when a user is changed while the app is in background and it doesn't give me anything useful ...
also I tried CnContactStoredidchange notification but it's useless .
here's the databaseRealmModel im using the fullname+phonenumber as a primaryKey to quickly fetch it from the database
#objcMembers class RealmModel:Object
{
dynamic var fullName: String = ""
dynamic var phoneNumber: String = ""
dynamic var firstName: String = ""
dynamic var lastName: String = ""
dynamic var middleName: String = ""
dynamic var identifier: String = ""
dynamic var primaryKey :String = ""
#objc dynamic var contactImage: Data? = nil
convenience init(fullName: String, phoneNumber: String,firstName:String,lastName:String,middleName:String,contactImage:Data?,identifier:String)
{
self.init()
self.fullName = fullName
self.phoneNumber = phoneNumber
self.firstName = firstName
self.lastName = lastName
self.middleName = middleName
self.contactImage = contactImage
self.identifier = identifier
self.primaryKey = identifier + phoneNumber
}
override class func primaryKey() -> String? {
return "primaryKey"
}
}
2) here's where i'm calling the method
DispatchQueue.global(qos: .userInteractive).async
{
let start = CFAbsoluteTimeGetCurrent()
Utilities.getCotacts { (added,deleted,modified) in
let diff = CFAbsoluteTimeGetCurrent() - start
print("Realm Took \(diff) seconds")
print( "Realm Added Contacts" + String(added.count))
print("Realm deleted Contacts" + String(deleted.count))
print("Realm modified Contacts" + String(modified.count))
}
3) here's the implementation of the function
class func requestForAccess(completionHandler: #escaping (_ accessGranted: Bool) -> Void) {
let authorizationStatus = CNContactStore.authorizationStatus(for: CNEntityType.contacts)
switch authorizationStatus {
case .authorized:
completionHandler(true)
case .denied, .notDetermined:
self.contactStore.requestAccess(for: CNEntityType.contacts, completionHandler: { (access, accessError) -> Void in
if access {
completionHandler(access)
}
else {
if authorizationStatus == CNAuthorizationStatus.denied {
completionHandler(false)
}
}
})
default:
completionHandler(false)
}
}
class func getCotacts(completionHandler: #escaping (_ addedcontacts: ([RealmModel]),_ deletedContacts: ([RealmModel]),_ modifiedContacts: [RealmModel]) -> Void)
{
var addedContacts = [RealmModel]()
var deletedContacts = [RealmModel]()
var modifiedContacts = [RealmModel]()
self.requestForAccess { (approved) in
if approved == true
{ //getting All contacts in database
let realm = try! Realm()
let Arr = realm.objects(RealmModel.self)
var initialArr = Array.init(Arr)
let keysToFetch = [
CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
CNContactEmailAddressesKey,
CNContactImageDataKey,
CNContactPhoneNumbersKey,CNContactImageDataAvailableKey] as! [CNKeyDescriptor]
let contactFetchRequest = CNContactFetchRequest(keysToFetch: keysToFetch)
do {
try! realm.write {
try self.contactStore.enumerateContacts(with: contactFetchRequest) {
contact, stop in
//enumerating through contact list
for number in contact.phoneNumbers
{
let firstName = contact.givenName
let lastName = contact.familyName
let middleName = contact.middleName
let fullName = String(format: "%#%#%#%#%#", arguments: [Utilities.isStringNull(string: contact.givenName) ? "" : contact.givenName, Utilities.isStringNull(string: contact.middleName) ? "" : " ", Utilities.isStringNull(string: contact.middleName) ? "" : contact.middleName, Utilities.isStringNull(string: contact.familyName) ? "" : " ", Utilities.isStringNull(string: contact.familyName) ? "" : contact.familyName])
let phoneNumber = number.value.value(forKey: "digits") as? String
var contactsModel = RealmModel(fullName: fullName, phoneNumber: phoneNumber!, firstName: firstName, lastName: lastName, middleName: middleName,contactImage:contact.imageData,identifier:contact.identifier)
let databaseContact = realm.object(ofType: RealmModel.self, forPrimaryKey:contactsModel.primaryKey)
// contact doesnt exist in database so its a new contact
if(databaseContact == nil) { addedContacts.append(contactsModel)
realm.add(contactsModel,update: false)
//it's new contact no need to set update true
}
else
{ // User exists in Database
if(databaseContact?.fullName != contactsModel.fullName)
{
// if full name has been changed
let indexesOfModifiedNumber = initialArr.indices.filter({ initialArr[$0].primaryKey == contactsModel.primaryKey })
if indexesOfModifiedNumber.count > 0
{
modifiedContacts.append(initialArr[indexesOfModifiedNumber.first!])
initialArr.remove(at:indexesOfModifiedNumber.first!)
databaseContact?.fullName = contactsModel.fullName
}
}
else
{ // No Change and contact is found
let indexesOfModifiedNumber = initialArr.indices.filter({ initialArr[$0].primaryKey == contactsModel.primaryKey })
if (indexesOfModifiedNumber.count > 0)
{
initialArr.remove(at: indexesOfModifiedNumber.first!)
}
}
}
}
}
if initialArr.count > 0
{
// deleted Contacts
deletedContacts = initialArr
for element in deletedContacts
{
realm.delete(element)
}
}
completionHandler(addedContacts,deletedContacts,modifiedContacts)
}
}
}
else
{
print("access not approved")
}
}
}
since Realm is quicker at insertion and fetching
I didn't find that big difference between it and coreData
I'm testing them with a phone with 6000 contacts ..
Core data needs 90 seconds to finish while realm needs 70 seconds
I am attempting to simply read into the database that is structured as stated below. I am attempting to read the user's "userType" and use it in the following if statements below. Any help is appreciated!
Swift Code:
// Create firebase reference and link to database
var dataRef : DatabaseReference?
dataRef = Database.database().reference()
let userID = Auth.auth().currentUser!.uid // Get the User's ID
// Gather user's type (Customer or Company)
/*Use this space to gather the user's type into some variable named currUserType*/
if (currUserType == "Customer"){
self.performSegue(withIdentifier: "LoginToCustomer", sender: self)
print("User: " + userID + " has been signed in!")
}
else if (currUserType == "Company"){
self.performSegue(withIdentifier: "LoginToHost", sender: self)
}
else{
self.showMessage(alertTitle: "Error",
alertMessage: "Please report the following error with a description of what lead to to the error.",
actionTitle: "Dismiss")
}
Database Structure:
"Users" : {
"ZFH0lFe1fIb5bwSO2Q95ektD33L2" : {
"email" : "cust#test.com",
"userType" : "Customer"
}
First take the ref like i have took below:
let dbRef = Database.database().reference().child("Users")
Then create model like i have created below:
class Users {
var email: String?
var userType: String?
init(email: String, userType: String) {
self.email = email
self.userType = userType
}
}
Then create completion Handler like i have created below:
func getUsersData(handler: #escaping (_ usersArray: [Users]) -> ()) {
var usersArray = [Users]()
dbRef.observe(.value) { (datasnapshot) in
guard let usersnapshot = datasnapshot.children.allObjects as? [DataSnapshot] else { return }
for user in usersnapshot {
let email = user.childSnapshot(forPath: "email").value as! String
let userType = user.childSnapshot(forPath: "userType").value as! String
let userObj = Users(email: email, userType: userType)
usersArray.append(userObj)
}
handler(usersArray)
}
}
simply call this function which returns the whole array of users.
Refrence https://firebase.google.com/docs/database/ios/read-and-write#reading_and_writing_data
I want to put the information I get from the API into the corresponding Label inside, I use the Alamofire to get the API information and put the corresponding Label inside, but I found that my Label text has not been changed, would like to ask this happen What's the problem? Who can answer me for me? Thank you
Here is my Information class:
import Foundation
import Alamofire
class Information {
var account:String?
var date:String?
var name:String?
var sex:String?
var born:String?
var phoneNumber:String?
var email:String?
init(account:String,date:String,name:String,sex:String,born:String,phoneNumber:String,email:String) {
self.account = account
self.date = date
self.name = name
self.sex = sex
self.born = born
self.phoneNumber = phoneNumber
self.email = email
}
typealias DownlaodComplete = () -> ()
func downlaodInformation(completion:#escaping DownlaodComplete) {
Alamofire.request("http://163.18.22.78:81/api/Information/A1001a").responseJSON { response in
print(response)
if let json = response.result.value as? Dictionary<String,Any> {
guard let account = json["Account"] as? String ,let date = json["Date"] as? String , let name = json["Name"] as? String , let sex = json["Sex"] as? String , let born = json["Born"] as? String , let phoneNumber = json["PhoneNumber"] as? String , let email = json["Email"] as? String else {
return
}
self.account = account
self.date = date
self.name = name
self.sex = sex
self.born = born
self.phoneNumber = phoneNumber
self.email = email
completion()
}
}
}
}
And here is my ViewController:
var information:Information?
override func viewDidLoad() {
super.viewDidLoad()
if let currentInformation = information {
currentInformation.downlaodInformation {
self.accountLabel.text = currentInformation.account
self.dateLabel.text = currentInformation.date
self.nameLabel.text = currentInformation.name
self.sexLabel.text = currentInformation.sex
self.bornLabel.text = currentInformation.born
self.phoneNumberLabel.text = currentInformation.phoneNumber
self.emailLabel.text = currentInformation.email
}
}
You need to use your completion block which will be called whenever Alamofire has finished the data request. You can also improve your code a bit by for example have a onCompletion block that passes an Information object and an onError block to display if you have any errors. Example below:
func downlaodInformation(parameterOne: String, parameterTwo: Int, onCompletion: #escaping (Information) -> Void, onError: #escaping(NSError) -> Void) {
Alamofire.request("http://163.18.22.78:81/api/Information/A1001a").responseJSON { response in
if let json = response.result.value as? Dictionary<String,Any> {
let account = json["Account"] as? String
let date = json["Date"] as? String
let name = json["Name"] as? String
let sex = json["Sex"] as? String
let born = json["Born"] as? String
let phoneNumber = json["PhoneNumber"] as? String
let email = json["Email"] as? String
let information = Information(account: account, date: date, name: name, sex: sex, born: born, phoneNumber: phoneNumber, email: email)
onCompletion(information)
} else {
onError(NSError(domain: "Error while getting data", code: 0, userInfo: nil))
}
}
}
Usage:
downlaodInformation(parameterOne: "someParam", parameterTwo: 123, onCompletion: { (currentInformation) in
print(currentInformation.account)
self.accountLabel.text = currentInformation.account
self.dateLabel.text = currentInformation.date
self.nameLabel.text = currentInformation.name
self.sexLabel.text = currentInformation.sex
self.bornLabel.text = currentInformation.born
self.phoneNumberLabel.text = currentInformation.phoneNumber
self.emailLabel.text = currentInformation.email
}) { (error) in
print(error.domain)
}
Here you declare information to be an Information optional
var information:Information?
But you don't give it an initial value, meaning that it is nil
In your viewDidLoad you do the right thing and check whether information has a value:
if let currentInformation = information
But I'm guessing it hasn't, because you haven't created an instance of it. Therefore you don't end up inside your if let loop and never calls downlaodInformation
So you need to create a new instance of Information before you can use it.
However
This leads to a problem with your Information class.
If I was to instantiate an Information object, I'd need to have:
account
date
name
sex
born
phoneNumber
email
Or..since you've created them as optionals, pass nil.
But that is not what you want, is it?
I'm guessing you'd like to do something along the lines of this in your ViewController:
let information = Information()
and then in viewDidLoad
information.downloadInformation( currrentInformation in
self.accountLabel.text = currentInformation.account
....
}
To do so you could change your Information to not take parameters to its constructor and then create another struct which would hold your data.
Something like:
struct Information {
var account:String?
var date:String?
var name:String?
var sex:String?
var born:String?
var phoneNumber:String?
var email:String?
}
class InformationLoader {
func downloadInformation(completion: (Information?) -> ()) {
Alamofire.request("http://163.18.22.78:81/api/Information/A1001a").responseJSON{ response in
print(response)
if let json = response.result.value as? Dictionary<String,Any> {
guard let account = json["Account"] as? String,
let date = json["Date"] as? String,
let name = json["Name"] as? String,
let sex = json["Sex"] as? String,
let born = json["Born"] as? String,
let phoneNumber = json["PhoneNumber"] as? String,
let email = json["Email"] as? String else {
completion(nil)
return
}
let information = Information(account: account, date: date, name: name, sex: sex, born: born, phoneNumber: phoneNumber, email: email)
completion(information)
}
}
}
And you'd need to change your code in the ViewController to:
let informationLoader:InformationLoader()
In viewDidLoad
informationLoader.downloadInformation{ currentInformation in
if let currentInformation = currentInformation {
//populate your textfields
self.accountLabel.text = currentInformation.account
....
}
}
Hope that makes sense and helps you.
Your code has a lot of mistakes, so here is a working variant. Better to call an updateUI or something like that from the closure. I hope this will help:
ViewController.swift:
class ViewController: UIViewController
{
#IBOutlet weak var accountLabel: UILabel!
#IBOutlet weak var dateLabel: UILabel!
#IBOutlet weak var nameLabel: UILabel!
var information: Information?
override func viewDidLoad()
{
super.viewDidLoad()
information = Information.init(account: "aaaa", date: "dddd", name: "nnnn", sex: "ssss", born: "bbbb", phoneNumber: "pppp", email: "eeee")
information?.downlaodInformation(completion:
{
self.updateUI()
})
}
func updateUI()
{
print("called")
self.accountLabel.text = information?.account
self.dateLabel.text = information?.date
self.nameLabel.text = information?.name
/*self.sexLabel.text = currentInformation.sex
self.bornLabel.text = currentInformation.born
self.phoneNumberLabel.text = currentInformation.phoneNumber
self.emailLabel.text = currentInformation.email*/
}
}
Information.swift:
class Information
{
var account:String?
var date:String?
var name:String?
var sex:String?
var born:String?
var phoneNumber:String?
var email:String?
typealias DownlaodComplete = () -> ()
init(account:String,date:String,name:String,sex:String,born:String,phoneNumber:String,email:String) {
self.account = account
self.date = date
self.name = name
self.sex = sex
self.born = born
self.phoneNumber = phoneNumber
self.email = email
}
func downlaodInformation(completion:#escaping DownlaodComplete) {
Alamofire.request("http://163.18.22.78:81/api/Information/A1001a").responseJSON { response in
print(response)
completion()
if let json = response.result.value as? Dictionary<String,Any>
{
print("Dictionary done")
guard
let account = json["Account"] as? String,
let date = json["Date"] as? String ,
let name = json["Name"] as? String else
{
print("Parse error!")
return
}
self.account = account
self.date = date
self.name = name
/*self.sex = sex
self.born = born
self.phoneNumber = phoneNumber
self.email = email*/
completion()
}
}
}
}
Tested, and got the following response:
SUCCESS: {
Account = A1001a;
Born = 841031;
CarParking = "";
Date = "0001/1/1 \U4e0a\U5348 12:00:00";
Email = "zxc#gmail.com";
Name = Ray;
Phone = 09361811111;
Sex = "\U7537"; } called Dictionary done called
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"