UserDefaults not loading first time - ios

I have a two step login process. On the first screen I save the FB user's details to UserDefaults.
When I load the second ViewController it is supposed to load the user's profile image and username (which I create using "fullname"). It actually works the second time I load the user but never the first time, is there some sort of delay? I already tried UserDefaults.standard.synchronize() but that does nothing. It even prints the details so I know it works...
let userName = fullName.replacingOccurrences(of: " ", with: "_")
UserDefaults.standard.setValue(userName, forKey: "username")
if let picture = result["picture"] as? NSDictionary , let data = picture["data"] as? NSDictionary, let url = data["url"] as? String {
UserDefaults.standard.setValue(url, forKey: "userFBPicURL")
let username = UserDefaults.standard.value(forKey: "username") as? String
let profileImage = UserDefaults.standard.value(forKey: "userFBPicURL") as? String
UserDefaults.standard.synchronize()
print ("Details are User:",username, "profileImage:",profileImage)
and in the new VC after self.performSegue(withIdentifier: "UsernameSegue", sender: self):
override func viewDidLoad() {
super.viewDidLoad()
if let username = UserDefaults.standard.value(forKey: "username") as? String {usernameTextfield.text = username}
if FBSDKAccessToken.current() != nil {loadFacebookProfileImage()}
}

I think you are setting the value to UserDefaults after performing segue or after the viewDidLoad gets called.
Steps to try find the issue:
Could you please try set breakpoint and see what goes first and what second?
Try printing from defaults right after you assign there.
Just try getting data not in ViewDidLoad but later. For example on button tap. Just to see if it's there for the 1st time too (but not only for the 2nd)
Question: where do you set the value to defaults?
PS: Also I wouldn't use UserDefaults for that, but yeah it's a different question.
Sorry, that can't provide an exact solution since it requires more info/code from your debugging. Please try the options above. Hope it helps.

Related

Database observer function not acting as observer in viewDidLoad, only viewDidAppear

I have a function that is in charge of observing the database. It sets some dictionaries upon first load, and then keeps an eye on changes thereafter and updates those dictionaries accordingly. When called in viewDidAppear, this works perfectly. But if I move it to viewDidLoad, it sets the values initially, but doesn't "observe" - in other words if I change one of the values, for example status, that change is not reflected until I leave the view and come back.
I need to have it in viewDidLoad for other reasons - why exactly is it only properly working as an observer if it's in viewDidAppear, and is there anything I can change to make it work as an observer in viewDidLoad?
This is the function:
func getParticipantInfo() {
let databaseRef = FIRDatabase.database().reference()
let groupRef = databaseRef.child("groups").child(currentRoomIdGlobal)
groupRef.observe(.childAdded, with: { snapshot in
if let snapDict = snapshot.value as? [String : AnyObject] {
for each in snapDict {
let uid = each.key
let avatar = each.value["profilePicture"] as! String
let gender = each.value["gender"] as! String
let handle = each.value["handle"] as! String
let name = each.value["name"] as! String
let status = each.value["status"] as! String
// Set those to the dictionaries [UID : value]
self.avatarDictionary.setValue(avatar, forKey: uid)
self.nameDictionary.setValue(name, forKey: uid)
self.genderDictionary.setValue(gender, forKey: uid)
self.handleDictionary.setValue(handle, forKey: uid)
self.statusDictionary.setValue(status, forKey: uid)
DispatchQueue.main.async {
// Nav bar
self.navCollectionView?.collectionView?.reloadData()
}
}
}
})
}
One reason I can see is that you are observing only .childAdded events. You'll have to subscribe to all the changes in order to trigger the event. try subscribing to .value event which will listen to all the changes on Database and reflect in app.
As far as viewDidLoad and viewDidAppear is concerned, make sure that you have setup all the class variables before attaching a subscriber to Firebase. viewDidLoad or viewDidAppear doesn't matter as far as the setup is right.

Access Data Outside of Closure, Firebase ObserveSingleEvent // SWIFT

When I first asked this question I hadn't really done my research. But after 20+ hours on this, I have structure exactly like in Firebase docs. But, I can't access any of the data outside of the closure. Here is the struct where the data should be written in to:
struct UserStruct {
let name : String!
let age : String!
}
And when it gets called, everything is written perfect in the database, inside the closure it doesn't print nil, it does print the actual value obviously. I have already tried
DispatchQueue.main.async {
}
But that didn't work either, somebody guide me! Any help is appreciated, this is my last issue with Firebase.
let currentUser = FIRDatabase.database().reference(withPath: "users").child((FIRAuth.auth()!.currentUser?.uid)!)
currentUser.observeSingleEvent(of: .value, with: { snapshot in
let value = snapshot.value as? NSDictionary
let name = value?["name"] as? String
let age = value?["age"] as? String
self.userAdded.insert(UserStruct(name: name, age: age), at: 0) // 1
let user = UserStruct.init(name: name, age: age) // 2
print("1 \(user.name)")
print("2 \(self.userAdded[0].name!)")
})
I wrote two ways of getting the data, number two(2) is the way Firebase suggests, but I can't even get a hold of user outside the closer like I can with the Struct.
Your user object that you create in the closure gets deallocated when the closure finishes what it has to do. As #Jay said in the comment, you need to store the data that you get in your closure in a variable outside of your closure. A quick and dirty way to test this out would be to create a variable in the class you're in and assign the user you create in your closure to that variable and print it out to see if it worked:
//add a property in your class to store the user you get from Firebase
var retrievedUser: UserStruct?
let currentUser = FIRDatabase.database().reference(withPath: "users").child((FIRAuth.auth()!.currentUser?.uid)!)
currentUser.observeSingleEvent(of: .value, with: { snapshot in
let value = snapshot.value as? NSDictionary
let name = value?["name"] as? String
let age = value?["age"] as? String
let user = UserStruct.init(name: name, age: age)
//assign the user retrieved from Firebase to the property above
self.retrievedUser = user
//print the local copy of the retrived user to test that it worked
print(retrievedUser)
})

UserDefault always return nil

UserDefaults are not working in my App. Please find the below code under AppDelegate file.
let sharingData = UserDefaults.init(suiteName: "group.macuser79.xxx");
sharingData?.set("vincenzo", forKey:"username");
sharingData?.synchronize();
In the InterfaceController of the app to Watch, to be able to retrieve the value so I did this:
override func awake(withContext context: Any?) {
let sharingData = UserDefaults.init(suiteName: "group.macuser79.xxx");
let username = sharingData?.object(forKey: "username");
print("Value username \(username)");
}
Please let me know, what I'm doing wrong!
In Swift3 UserDefaults made much smarter to obtained stored value. In below line, you are storing String value without specifying it!:
sharingData?.set("vincenzo", forKey:"username");
So, in order to get that, you need to write like below:
let username = sharingData?.string(forKey: "username");
print("Value username \(username)");
}
This is much more better context in getting values based on you Store.
Try this instead:
let defaults = UserDefaults.standard
defaults.set("group.macuser79.xxx", forKey:"username")
defaults.synchronize()
To access the store:
let defaults = UserDefaults.standard
let username = defaults.string(forKey: "username")
I admit that I haven't tried to init() my own UserDefaults instance before, but the standard instance of it, which is made into a singleton by iOS, is good practice to use.
Also, don't forget to unwrap the optional properly.
To set:
UserDefaults(suiteName: "group.macuser79.xxx")?.set("vincenzo", forKey: "username")
To access:
UserDefaults(suiteName: "group.macuser79.xxx")!.string(forKey: "username")

Save variable after my app closes (swift)

I'm trying to save a variable in Xcode so that it saves even after the app has closed, how ever when I access it I do it from a several different classes and files, and I change the value of the variable when I access it. Therefore similar threads do not completely apply, the value to be stored is a string and here is the code I have up until now:
var defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(Token, forKey: "") as! String
I believe this is the correct format, but I don't know how to call it to change it because when I try I get an error message saying expected declaration.
Anyway any help would be very much appreciated.
First of all you have to specify an unique key to store the variable (in the example MyKey ).
In AppDelegate > applicationDidFinishLaunching register the key with a default value.
The benefit is you can use a non-optional variable and you have a defined default value.
let defaults = UserDefaults.standard
let defaultValue = ["MyKey" : ""]
defaults.register(defaults: defaultValue)
Now you can from everywhere read the value for the key
let defaults = UserDefaults.standard
let token = defaults.string(forKey: "MyKey")
and save it
let defaults = UserDefaults.standard
defaults.set(token, forKey: "MyKey")
Swift 3
(thanks to vadian's answer)
In AppDelegate > applicationDidFinishLaunching :
let defaults = UserDefaults.standard
let defaultValue = ["myKey" : ""]
defaults.register(defaults: defaultValue)
to save a key:
let defaults = UserDefaults.standard
defaults.set("someVariableOrString", forKey: "myKey")
defaults.synchronize()
to read a key:
let defaults = UserDefaults.standard
let token = defaults.string(forKey: "myKey")
Let's say you have a string variable called token
To save/update the stored value on the device:
NSUserDefaults.standardUserDefaults().setObject(token, forKey: "mytoken")
NSUserDefaults.standardUserDefaults().synchronize()
In the code above I made sure to give the key a value ("mytoken"). This is so that we later can find it.
To read the stored value from the device:
let token = NSUserDefaults.standardUserDefaults().objectForKey("mytoken") as? String
Since the method objectForKey returns an optional AnyObject, I'll make sure to cast it to an optional String (optional meaning that it's either nil or has a value).
Add default.synchronize() after setting value.

How do I register a plist to NSUserDefaults?

What I'm trying to do is store my initial user defaults in a plist and then read them into NSUserDefaults when the application loads for the first time.
I've found a couple of posts which have helped me thus far however I can't seem to find an answer as to why I'm finding nil while unwrapping. Obviously I'm missing something, so my question is:
How do I properly register a plist to NSUserDefaults?
I've created a plist with my default settings. I'd like to read from the plist and register them into NSUserDefaults.
settings.plist
ViewController.swift
class ViewController: UIViewController {
let userDefaults = NSUserDefaults.standardUserDefaults()
override func viewDidLoad() {
super.viewDidLoad()
let prefs = NSBundle.mainBundle().pathForResource("Settings", ofType: "plist")
let dict = NSDictionary(contentsOfFile: prefs!)
if let dictionary = dict {
println("Contents of file unwrapped: \(dictionary)") // <---------
let defaults : NSDictionary! = dictionary.valueForKey("Root") as? NSDictionary
println("defaults: \(defaults)") // <---------
userDefaults.registerDefaults(defaults as! [NSObject : AnyObject])
userDefaults.synchronize()
}
if let unwrapDict = dict {
var myValue = unwrapDict.objectForKey("Boolean Switch 1") as! Bool
println("pulled from pList \(myValue)")
}
}
}
I'm sure you've noticed that I am looking for the key 'Root' from my plist...
let defaults : NSDictionary! = dictionary.valueForKey("Root") as? NSDictionary
I have also tried passing it other values like "Boolean Switch 1" and casting to different types. But nothing I try changes the outcome.
Here's my console output from the two println() logs.
After a day of messing with this code I was able to finally get my plist entered into NSUserDefaults. I wasn't satisfied with the final outcome because I noticed that the registerDefaults wasn't actually doing anything.
So I decided to post my working code on Code Review to see if there were any parts of my code that weren't necessary. It turns out that registerDefualts indeed was not necessary, here's an excerpt from the Alex's answer to my question.
This line:
userDefaults.registerDefaults(dict as! [NSObject : AnyObject]) does
not actually set the information on the NSUserDefaults storage file on
disk. All it does is tell NSUserDefaults the default values to be used
when a key doesn't yet exist on disk.
So having said all of that, I'll post the working code. I should note, as it was pointed out to me by nhgrif, that the ViewController is really not the place to put this code and a better place is in the appDelegate in application(_:didFinishLaunchingWithOptions:).
ViewController:
class ViewController: UIViewController {
let userDefaults = NSUserDefaults.standardUserDefaults()
override func viewDidLoad() {
super.viewDidLoad()
let prefs = NSBundle.mainBundle().pathForResource("Settings", ofType: "plist")
let dict = NSDictionary(contentsOfFile: prefs!)
userDefaults.setObject(dict, forKey: "defaults")
userDefaults.synchronize()
// this just toggles the aBool value
if let defaults = userDefaults.valueForKey("aBool") as? NSNumber {
if defaults as NSObject == 1 {
println("inside the conditional: \(defaults)")
userDefaults.setValue(0, forKey: "aBool")
userDefaults.synchronize()
println("Setting aBool to true")
} else {
userDefaults.setValue(1, forKey: "aBool")
userDefaults.synchronize()
println("setting aBool to false")
}
}
}
}
According to Apple document, If you don't change any value on settings, Values reading from NSUserDefaults are nil or 0. In your code, When you read a value from NSUserDefaults, you should set it a default value.

Resources