Empty value in NSUserDefaults - objective-c

I am developing a cocoa application. I created a user default object with this code:
NSUserDefaults *standard_user_defaults = [NSUserDefaults standardUserDefaults];
if (standard_user_defaults) {
[standard_user_defaults setObject:myString forKey:key];
[standard_user_defaults synchronize];
}
And then, I am getting the value with this code:
NSUserDefaults *standard_user_defaults = [NSUserDefaults standardUserDefaults];
NSString *val = nil;
if (standard_user_defaults)
val = [standard_user_defaults objectForKey:key];
The problem is that if I run this application in my computer is working fine, and I can get the user default value, but if another person runs the application is getting an empty value. Any ideas?

I assume by “another person” you mean another user on your computer. The reason they cannot read what you’ve written to your user defaults is because NSUserDefaults is meant for user-specific values. To save something that your application will be able to access from both your user account and others’, you need to place it in a file stored somewhere in a shared location on the system. One way to do this would be something like this:
NSString *basePath = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSLocalDomainMask, YES) objectAtIndex:0];
NSString *filePath = [basePath stringByAppendingPathComponent:#"someFileName"];
// write:
NSDictionary *dataToSave = [NSDictionary dictionaryWithObjectsAndKeys:theValue, #"someKey", nil];
if(filePath && [dataToSave writeToFile:filePath atomically:YES])
{
// success!
}
// read:
NSDictionary *dataToLoad = [NSDictionary dictionaryWithContentsOfFile:filePath];
if(dataToLoad)
{
// success!
NSString *theValue = [dataToLoad objectForKey:#"someKey"];
}

You should try and use valueForKey rather than objectForKey. Looking at your code, there is no reason it shouldn't work for one person and not another.
The one exception would be if you are using the defaults from the settings.bundle. Values stored into settings.bundle for user defaults are not initialized until the user opens the settings page for the first time.

Related

How exactly to save and retrieve last seen message timestamp - plist or NSUserDefaults

I have two UIViewControllers. In the FirstViewController, I download the groupsInfo data from the firebase and show the details(groupName,lastMessage) in the UITableView and have one more label "lastMessageTime"(this information will come from SecondViewController).Once I tap the UITableView cell, it goes to the detail page SecondViewController, here I download the messageInfo based on the tapped groupID. When I leave the SecondViewController, I want to store the last message time for that particular groupID in commonplace (either plist or NSUserDefaults) and retrieve the information in the FirstViewController or in some other ViewController(later stage). The saved data shouldn't get deleted and also I should be able to update(in case value already exist for particulat key) "lastMessageTime" for any particular groupID whenever there is new message.
Below is code I tried, but not sure if this is the best approach. Would be great if you can tell me the best approach to deal this requirement effectively.
SecondViewController:
-(void)viewWillDisappear:(BOOL)animated{
[self updateLastMessageSeenTime:groupID];
}
-(void)updateLastMessageSeenTime:(NSString*)groupID
{
FIRDataSnapshot *snapshot = [self.messageArray lastObject];
NSMutableDictionary *lastSeenMessageTime = [[NSMutableDictionary alloc]init];
NSString *lastMessageTime = snapshot.value[#"timeStamp"];
[lastSeenMessageTime setValue:lastMessageTime forKey:groupID];
NSUserDefaults *lastSeenMessageTimeUserDefaults = [NSUserDefaults standardUserDefaults];
[lastSeenMessageTimeUserDefaults setObject:lastSeenMessageTime forKey:#"lastMessageTimeArrayKey"];
[lastSeenMessageTimeUserDefaults synchronize];
}
Above method erases all the previous data. For Example, If visited the SecondViewController twice each for different groupID, previous groupID's lastMessageTime is removed and replaced with the new groupID's.
You need to get the dictionary from NSUserDefaults first and if it doesn't exist then init a new one. Try this.
-(void)updateLastMessageSeenTime:(NSString*)groupID
{
FIRDataSnapshot *snapshot = [self.messageArray lastObject];
NSUserDefaults *lastSeenMessageTimeUserDefaults = [NSUserDefaults standardUserDefaults];
NSString *key = #"lastMessageTimeArrayKey";
NSMutableDictionary *lastSeenMessageTime = [[lastSeenMessageTimeUserDefaults objectForKey: key] mutableCopy];
if (!lastSeenMessageTime) {
lastSeenMessageTime = [[NSMutableDictionary alloc] init];
}
NSString *lastMessageTime = snapshot.value[#"timeStamp"];
[lastSeenMessageTime setValue:lastMessageTime forKey:groupID];
NSUserDefaults *lastSeenMessageTimeUserDefaults = [NSUserDefaults standardUserDefaults];
[lastSeenMessageTimeUserDefaults setObject:lastSeenMessageTime forKey: key];
[lastSeenMessageTimeUserDefaults synchronize];
}

Check for first launch of my application

How would I check if it is the first launch of of my application using NSUserDefaults and running some code for the first time my app opens?
This should point you in the right direction:
static NSString* const hasRunAppOnceKey = #"hasRunAppOnceKey";
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
if ([defaults boolForKey:hasRunAppOnceKey] == NO)
{
// Some code you want to run on first use...
[defaults setBool:YES forKey:hasRunAppOnceKey];
}
The NSUserDefaults answer is the first thing that popped in my head, but upon reflection I will make another suggestion. A bit more work, but it's worth considering. The motive is: sometimes when troubleshooting an app, Apple recommends deleting that app's plist file. It's a fairly ubiquitous troubleshooting technique. I would recommend storing your boolean in your plist file instead of NSUserDefaults.
Disclaimer: I only do iOS development, so I'm not sure how NSUserDefaults and plists interact on the Mac, and I don't know what all is involved in getting your plist to live in ~/Library/Application\ Support/Preferences/com.mycompany.MyAppName.plist
Anyway, I imagine what this requires is having some code which can actually author a "fresh" plist (probably a copy from a template file in your bundle), and you app does this if it launches and does not see a plist. The default plist should not include the flag which lets your users skip the 'first time' code, but if they have opened the app before, and then delete the plist, they should get default behavior back.
This is an important behavior to support where possible, to aide our users if our app ever gives them trouble.
if (![[NSUserDefaults standardUserDefaults] boolForKey:#"hasBeenLaunched"]) {
// Run code on the first launch only ...
[defaults setBool:YES forKey:#"hasBeenLaunched"];
}
You can use NSUserDefaults to save bools, integers, objects into the program and have them available whenever you open it. You can use 'boolForKey' to set a flag called "hasBeenLaunched". By default, this value will be NO when not set. Once you change it to YES, the code in the if condition will never be executed again.
In your main controller class, implement something like this:
static NSString * const MDFirstRunKey = #"MDFirstRun";
#implementation MDAppController
+ (void)initialize {
NSMutableDictionary *defaults = [NSMutableDictionary dictionary];
[defaults setObject:[NSNumber numberWithBool:YES] forKey:MDFirstRunKey];
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
// the following if on Mac and is necessary:
[[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:defaults];
}
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
BOOL firstRun = [[[NSUserDefaults standardUserDefaults]
objectForKey:MDFirstRunKey] boolValue];
if (firstRun) {
// do something
[[NSUserDefaults standardUserDefaults] setObject:
[NSNumber numberWithBool:NO] forKey:MDFirstRunKey];
} else {
// do something else
}
}
#end
The +initialize class method is called before an instance of the class it's found in is created; in other words, it is called very early on, and is a good place to set up your default values.
See Preferences and Settings Programming Guide: Registering Your App's Default Preferences for more info.

How to save a string for later use?

I am currently working on having a file update from a remote server. I am able to download the file and save it to the documents directory. The file has a "Last-Modified" tag and I am using it to check if the file needs to be updated. But my question is, how do you save the string with the tag for later use? Later I want to compare the saved string with the another string with the current "Last-Modified" tag. If they are equal the file doesn't have to be updated but if they're not equal I will download the new file.
Sorry for bad English, correct me and any help is appreciated. Have been struggling with this for a while!
EDIT:
NSDictionary *metaData = [test allHeaderFields];
//NSLog(#"%#", [metaData description]);
lastModifiedString = [metaData objectForKey:#"Last-Modified"];
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
[standardUserDefaults setObject:lastModifiedString forKey:#"LastModified"];
[standardUserDefaults synchronize];
NSString *savedString = [[NSUserDefaults standardUserDefaults] stringForKey:#"LastModified"];
if (![lastModifiedString isEqualToString:savedString])
{
[self downloadNewFile];
}
Download link to files: Archive.zip
Use NSUserDefaults or Core Data to persist a value.
EDIT:
It is not working because you are saving the new value before retrieving it. You'll need to move
NSString *savedString = [[NSUserDefaults standardUserDefaults] stringForKey:#"LastModified"];
above
[standardUserDefaults setObject:lastModifiedString forKey:#"LastModified"];
Now you'll be comparing the new file value against the old user defaults value.
You are saying you want to compare the Last Modified dates to see if they are the same?
I think the best way to do this would be to save the date (as a string) in a Property List. If you are making an iPhone app you can create a property list with a string with the code below. This code checks if a file already exists and if it does, it reads from it, and if it doesn't, it creates one and writes to it.
// Get the path to the property list.
NSArray *pathArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *pathToPlist = [[pathArray objectAtIndex:0] stringByAppendingPathComponent:#"yourfilename.plist"];
// Check whether there is a plist file that already exists.
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:pathToPlist];
if (!fileExists) {
// There is no file so we set the file up and save it.
NSMutableDictionary *newDict = [NSMutableDictionary dictionaryWithCapacity:1];
[newDict setObject:yourStringWithTheLastModifiedDate forKey:#"lastModified"];
[newDict writeToFile:pathToPlist atomically:YES];
}
} else {
// There is already a plist file. You could add code here to write to the file rather than read from it.
// Check the value of lastModified.
NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithContentsOfFile:pathToPlist];
NSString *lastModifiedDate = [persistentNonResidentData objectForKey:#"lastModified"];
// Add your own code to compare the strings.
}
Or I may have misunderstood your question and that may not be what you are looking for at all lol.

Problems in loading from NSUserDefaults

I have a very strange problem while my app loads from NSUserDefaults.
This is the code in my appDelegate:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *user = [[NSString alloc]initWithString:[defaults objectForKey:#"_settingsUsername"]];
NSString *password = [[NSString alloc]initWithString:[defaults objectForKey:#"_settingsPassword"]];
NSString *instance = [[NSString alloc]initWithString:[defaults objectForKey:#"_settingsInstance"]];
The error is this: [NSPlaceholderString initWithString:]: nil argument'
It's very strange because i put in Settings.bundle -> Root.plist the default values for all the fields above.
Oddly that's how it's supposed to work -- this confused me too initially.
I guess the thing to remember is that you can use NSUserDefaults without the Settings.bundle, so it can't be the only way to set default values.
Apple provide sample code, AppPrefs, that shows how to copy the default value from the settings to NSUserDefaults.
Why don't you just use that?
NSUserDefaults *defaults = [NSUserDefaults standarduserDefaults];
NSString *user = [defaults objectForKey:#"_settingsUsername"];
NSString *password = [defaults objectForKey:#"_settingsPassword"];
NSString *instance = [defaults objectForKey:#"_settingsInstance"];
And make sure that there are objects for these keys.
hope it helps
I think the problem is that if the user hasn't gone into the settings for your app yet then all values returned from NSUserDefaults for those keys are nil. You need to handle that case and act accordingly - probably by going into the Settings.bundle and picking out the default value that you have put in there. I would just write a method that gets the value for a key and if NSUserDefaults returns nil it handles all the querying of the settings bundle for you.

problem with loading from NSUserDefaults, obj-c

I'm having a problem with NSUserDefaults. I'm saving a name, then trying to access it later in the code from another view controller, but it seems that my key is empty or not saved as when I display the string from the key my label is blank.
-(void) saveName {
NSString *name = nameField.text;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:name forKey:#"playersName"];
[defaults synchronize];
}
-(void) viewDidLoad // method called later on in code from different viewcontroller
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *name = [defaults objectForKey:#"playersName"];
playerName.text = name; // player name == myLabel
if ([[NSUsersDefaults standardUserDefaults] objectForKey:#"playersName"] == nil) {
playerName.text =#" blank";
}
}
My string doesn't seem to be saving to userdefaults as the #"blank" string keeps showing up with my label. I'm not too familiar with NSUserDefaults so any help is much appreciated.
A few things to note when using NSUserDefaults:
calls to syncrhonize method is not necesary and will only make it slower if your program is multi threaded.
Check if the key that you use is being used by other libraries in your project
Check if the value that you set to the key is not nil
Try to use stringForKey: or boolForKey: instead of objectForKey:
I had troubles a few times with NSUserDefaults but in the end it's usually my code that's problematic.
I would suggest you to check whether your '(void) saveName' method is being called or not... Put some breakpoint and see the result