I am observing strange behaviour of NSUserDefaults. During testing on iphone 4(32-bit) it is working fine. When i send the same app to my client using iPhone 5S(64-bit), NSUserDefault values are becoming nil. Is the behaviour of NSUserDefaults is changing on 32-bit and 64-bit ios versions? I have also used [[NSUserDefaults standardUserDefaults]synchronize] after saving values in NSUserDefaults.I am observing this kind of behaviour from ios 7.1 versions.
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//to stop updating locations in subsequent launches
if (![#"1" isEqualToString:[[NSUserDefaults standardUserDefaults]
objectForKey:#"aValue"]]) {
//Action here
NSLog(#"LOCATION IS ON");
locationManager = [[CLLocationManager alloc] init];
locationVC = [[LocationVC alloc]init];
[locationVC InitializeWith:_managedObjectContext LocationManager:locationManager];
//[locationVC InitializeWith:_managedObjectContext LocationManager:nil];
}
else {
NSLog(#"LOCATION IS OFF");
locationManager = [[CLLocationManager alloc] init];
locationVC = [[LocationVC alloc]init];
//[locationVC InitializeWith:_managedObjectContext LocationManager:locationManager];
[locationVC InitializeWith:_managedObjectContext LocationManager:nil];
}
}
LocationVC.m
LocationVC *locationVC;
-(void) InitializeWith:(NSManagedObjectContext *)context LocationManager:(CLLocationManager *)locationManager{
//to update location during first launch
if (![#"1" isEqualToString:[[NSUserDefaults standardUserDefaults]
objectForKey:#"aValue"]]) {
[[NSUserDefaults standardUserDefaults] setValue:#"1" forKey:#"aValue"];
[[NSUserDefaults standardUserDefaults] synchronize];
NSLog(#"LOCATION IS ON");
self->locationManager=locationManager;
self->locationManager.delegate = self;
self->locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self->locationManager.distanceFilter = kCLDistanceFilterNone;
[self->locationManager startUpdatingLocation];
_managedObjectContext=context;
}
//in subsequent launches
else{
NSLog(#"LOCATION IS OFF");
_managedObjectContext=context;
[self->locationManager stopUpdatingLocation];
}
}
In my client's iPhone 5S(64-bit), location icon is reappearing after 10 minutes eventhough i have called [self->locationManager stopUpdatingLocation]; in subsequent application lauches.
Is the behaviour of NSUserDefaults is changing on 32-bit and 64-bit ios versions?
No, NSUserDefaults (based on property lists) are designed to be platform independent. You can even copy a binary plist file from a Power PC Mac to an Intel machine, an iPhone 32 bit, or iPhone 64 bit and the file will always produce identical results.
The NSUserDefaults class is known to be very robust.
It's very likely that the error is in your code. If you need more help, please provide more information about how you detect and reproduce the error.
Edit: It seems that you used the wrong method to set #"aValue":
[[NSUserDefaults standardUserDefaults] setValue:#"1" forKey:#"aValue"];
The correct method to set preferences is -setObject:forKey:.
setValue:fortKey: is part of KVC and is not documented to do something specific in NSUserDefaults. While it seems that in current implementations (iOS 7.1) NSUserDefaults overrides setValue:forKey: that's an undocumented feature not guaranteed to work as expected.
Related
I am trying to logout a user when the application is terminated (since in this particular case the user data doesn't change very often there is no need to logout when going into background).
So here is my code:
- (void)applicationWillTerminate:(UIApplication *)application
{
User *user = [[User alloc]init];
[user logout];
}
I first tried to log the user out like so but it didn't work:
- (void)applicationWillTerminate:(UIApplication *)application
{
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:#"logged_in"];
}
Any ideas?
If you want the defaults to be updated immediately, you should sync after updating:
[[NSUserDefaults standardUserDefaults] synchronize];
As documented (khm, please look up the documentation), the NSUserDefaults class doesn't immediately persist the changes to disk, only once in a while.
I am trying to toggle user agent for a uiwebview between iPhone and iPad. So to have a button that will change from iPad user agent to iPhone user agent and backward.
I found this link: http://www.mphweb.com/en/blog/easily-set-user-agent-uiwebview
Unfortunately, the user agent can be changed only once, even if I recreate the uiwebview.
Does anyone has an idea about how to do it?
Btw, I also tried set the user agent in urlrequest header, without success.
Thanks
Me too, I have been able to set the UserAgent once only. Then I can't change it anymore. I tried on the simulator and on the device too. Same trouble.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSDictionary *dictionary = #{#"UserAgent" : #"MyOWnUserAgent_2"};
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];
}
I don't use web views. I just make calls like:
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:urlRequest] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
I cleaned, deleted the app from the device, I rebuilt, I quit, reboot, rebuilt... The problem is still there. I run XCode 7.3 (7D175) on OS X 10.11.5 (15F34) and build against Deploymen Target iOS 9.1, Base SDK iOS 9.3.
Any clue?
try this
static void appendUA(NSString *uaStr) {
if (!uaStr)
return;
UIWebView *tempWebView = [[UIWebView alloc] initWithFrame:CGRectZero];
NSString *secretAgent = [tempWebView stringByEvaluatingJavaScriptFromString:#"navigator.userAgent"];
secretAgent = [secretAgent stringByAppendingString:uaStr];
NSDictionary<NSString *, id> *domain = [[NSUserDefaults standardUserDefaults] volatileDomainForName:NSRegistrationDomain];
if (domain) {
NSMutableDictionary<NSString *, id> *newDomain = [domain mutableCopy];
[newDomain setObject:secretAgent forKey:#"UserAgent"];
domain = newDomain;
} else {
domain = [[NSDictionary alloc]
initWithObjectsAndKeys:secretAgent, #"UserAgent", nil];
}
[[NSUserDefaults standardUserDefaults] removeVolatileDomainForName:NSRegistrationDomain];
[[NSUserDefaults standardUserDefaults] setVolatileDomain:domain forName:NSRegistrationDomain];
[[NSUserDefaults standardUserDefaults] synchronize];
}
You should call this function BEFORE create WKWebview/UIWebView, and space is NOT automatically added.
appendUA(#" ====TEST_UA====");
WKWebView *wkWebView = [WKWebView new];
//...
I need to track the download of a certain iphone application. I tried a lot and found out that we could track it from the AppStore. But i need to track that from my application itself. So please help me to identify the method that fires when the application starts for the first time. Thanks.
There's no specific method that fires only on the 1st application launch. You can set a flag in user defaults on application start - so if the flag is not present then that will mean that application launched for the 1st time:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
if (![[NSUserDefaults standardDefaults] boolForKey:#"AlreadyLaunched"]){
// First launch logic
[[NSUserDefaults standardDefaults] setBool:YES forKey:#"AlreadyLaunched"];
[[NSUserDefaults standardDefaults] synchronize];
}
...
}
But i need to track that from my application itself.
No.
But if you really want to do this you could use something like this:
BOOL hasUsedSpyWareFunctions = [[NSUserDefaults standardUserDefaults] boolForKey:#"SpyWareKey"];
if (!hasUsedSpyWareFunctions) {
[self spyOnUser];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"SpyWareKey"];
}
if you are a Pro in spying you only set the key to YES if the method returned successfully (ie a network connection could be established)
There’s no such an event, at least not one that I know of. But what you want can be trivially done using NSUserDefaults. Simply check for some boolean flag and if it’s not there, it’s a first run and you can set the flag:
NSString *const AlreadyRunKey = #"already-run";
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
if (![prefs boolForKey:AlreadyRunKey]) {
[prefs setBool:YES forKey:AlreadyRunKey];
[prefs synchronize];
// do whatever else you want
}
I've got some settings saved in my Settings.bundle, and I try to use them in application:didFinishLaunchingWithOptions, but on a first run on the simulator accessing objects by key always returns nil (or 0 in the case of ints). Once I go to the settings screen and then exit, they work fine for every run thereafter.
What's going on? Isn't the point of using default values in the Settings.bundle to be able to use them without requiring the user to enter them first?
If I got your question right, in your app delegate's - (void)applicationDidFinishLaunching:(UIApplication *)application, set the default values for your settings by calling
registerDefaults:dictionaryWithYourDefaultValues
on [NSUserDefaults standardUserDefaults]
- (void)applicationDidFinishLaunching:(UIApplication *)application {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:3], #"SomeSettingKey",
#"Some string value", #"SomeOtherSettingKey",
nil];
[ud registerDefaults:dict];
}
These values will only by used if those settings haven't been set or changed by previous executions of your application.
As coneybeare said "You should detect if it is the first load, then store all your defaults initially."
On applicationDidFinishLaunching try to set default value in your preference.
Here is the sample:
NSUserDefaults *defaults =[NSUserDefaults standardUserDefaults];
if([defaults objectForKey:#"YOUR_KEY"] == nil)
{
[defaults setValue:#"KEY_VALUE" forKey:#"YOUR_KEY"];
}
When application will run second time it will come with KEY_VALUE for YOUR_KEY.
Thanks,
Jim.
Isn't the point of using default
values in the Settings.bundle to be
able to use them without requiring the
user to enter them first?
No. The point of the settings bundle is to give the user a place to edit all 3rd Party app settings in a convenient place. Whether or not this centralization is really a good idea is a User Experience issue that is off topic.
To answer your question, you should detect if it is the first load, then store all your defaults initially.
And while we are on the subject, I would also check out In App Settings Kit as it provides your app with a simple way to display your app settings in both places (in-app and Settings.app) with minimal code.
The values in the Settings.bundle are intended for the Settings app to able to fill in default values for your app. They are not used by your own app.
But you can set defaults yourself with the registerDefaults: method of NSUserDefaults. This will not actually set them on disk but just give "defaults for the defaults": they are used when no value has been set by the user yet.
Setting registerDefaults: must be done before any use of the default values. The "applicationDidFinishLaunching:" method that others suggested for this, is too late in most cases. By the time "applicationDidFinishLaunching:" is called, your views have already been loaded from the nib files, and their "viewDidLoad:" methods have been called. And they may typically read user defaults.
To guarantee that the defaults are set before first use, I use the following utility class, which loads the values from the Root.plist file and sets them with "registerDefaults:". You use this class to read user defaults instead of "[NSUserDefaults standardUserDefaults]". Use "[Settings get]" instead.
As a bonus, it also contains a registration method for user default change notifications, because I always forget how that is done.
#import "Settings.h"
#implementation Settings
static bool initialized = NO;
+ (void) setDefaults
{
NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
NSString *settingsBundlePath = [bundlePath stringByAppendingPathComponent:#"Settings.bundle"];
NSBundle *settingsBundle = [NSBundle bundleWithPath:settingsBundlePath];
NSString *settingsPath = [settingsBundle pathForResource:#"Root" ofType:#"plist"];
NSDictionary *settingsDict = [NSDictionary dictionaryWithContentsOfFile:settingsPath];
NSArray *prefSpecifierArray = [settingsDict objectForKey:#"PreferenceSpecifiers"];
NSMutableDictionary *appDefaults = [[NSMutableDictionary alloc] init];
for (NSDictionary *prefItem in prefSpecifierArray)
{
NSString *key = [prefItem objectForKey:#"Key"];
if (key != nil) {
id defaultValue = [prefItem objectForKey:#"DefaultValue"];
[appDefaults setObject:defaultValue forKey:key];
}
}
// set them in the standard user defaults
[[NSUserDefaults standardUserDefaults] registerDefaults:appDefaults];
if (![[NSUserDefaults standardUserDefaults] synchronize]) {
NSLog(#"Settings setDefaults: Unsuccessful in writing the default settings");
}
}
+ (NSUserDefaults *)get
{
if (!initialized) {
[Settings setDefaults];
initialized = YES;
}
return [NSUserDefaults standardUserDefaults];
}
+ (void) registerForChange:(id)observer selector:(SEL)method
{
[[NSNotificationCenter defaultCenter] addObserver:observer selector:method name:NSUserDefaultsDidChangeNotification object:nil];
}
+ (void) unregisterForChange:(id)observer
{
[[NSNotificationCenter defaultCenter] removeObserver:observer name:NSUserDefaultsDidChangeNotification object:nil];
}
I want to check if my iPhone app is running for the first time. I can create a file in the documents folder and check that file to see if this is the first time the app is running, but I wanted to know if there is a better way to do this.
I like to use NSUserDefaults to store an indication of the the first run.
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if (![defaults objectForKey:#"firstRun"])
[defaults setObject:[NSDate date] forKey:#"firstRun"];
[[NSUserDefaults standardUserDefaults] synchronize];
You can then test for it later...
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if([defaults objectForKey:#"firstRun"])
{
// do something or not...
}
Ok what confuses the hell out of me about User Defaults.
WHERE are they stored?
you dont care it varies per iOS/Mac.
you just getVALUE by KEY
setVALUE by KEY + synchronize
iOS/Mac does the rest.
This is the common use case:
Checking for the existence of a value e.g firstRun.
The first time it will NOT EXIST so usually followed by setting the value.
2nd Run
- on next loop it does exist and other use case/else stmt is triggered
---- .h
#interface MyAppDelegate : UIResponder <UIApplicationDelegate>
//flag to denote if this is first time the app is run
#property(nonatomic) BOOL firstRun;
------ .m
#implementation MyAppDelegate
#synthesize firstRun = _firstRun;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//==============
//Check to see if this is first time app is run by checking flag we set in the defaults
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if (![defaults objectForKey:#"firstRun"]){
//flag doesnt exist then this IS the first run
self.firstRun = TRUE;
//store the flag so it exists the next time the app starts
[defaults setObject:[NSDate date] forKey:#"firstRun"];
}else{
//flag does exist so this ISNT the first run
self.firstRun = FALSE;
}
//call synchronize to save default - where its saved is managed by iOS - varies by device and iOS/Mac
[[NSUserDefaults standardUserDefaults] synchronize];
//TO TEST: delete the app on the device/simulator
//run it - should be the first run
//close it - make sure you kill it and its not just in the background else didFinishLaunchingWithOptions wont be called
//just applicationDidBecomeActive
//2nd run it should self.firstRun = FALSE;
//=============
//NOTE IMPORTANT IF YOURE ROOTVIEWCONTROLLER checks appDelegate.firstRun then make sure you do the check above BEFORE setting self.window.rootViewController here
self.window.rootViewController = self.navController;
[self.window makeKeyAndVisible];
return YES;
}
---- USING THE FLAG
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
if (appDelegate.firstRun){
NSLog(#"IS FIRST RUN - Do something: e.g. set up password");
}else {
NSLog(#"FPMyMusicScreenViewController: IS NOT FIRST RUN - Prompt for password");
}
The examples above confused me a bit as they show how to check for it the first time but then mention how to 'check for it later' in the same comment.
The problem is when we find it doesnt exist we immediately create it and synchronize.
So checking for it late actually mean when you RESTART THE APP not in same run as first run.
In your app delegate register a default value:
NSDictionary *defaultsDict =
[[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithBool:YES], #"FirstLaunch", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:defaultsDict];
[defaultsDict release];
Then where you want to check it:
NSUserDefaults *sharedDefaults = [NSUserDefaults standardUserDefaults];
if ([sharedDefaults boolForKey:#"FirstLaunch"]) {
//Do the stuff you want to do on first launch
[sharedDefaults setBool:NO forKey:#"FirstLaunch"];
[sharedDefaults synchronize];
}
You can implement it with the static method below. I think it's better since you can call this method as many times as you like, unlike the other solutions. enjoy: (Keep in mind that it's not thread-safe)
+ (BOOL)isFirstTime{
static BOOL flag=NO;
static BOOL result;
if(!flag){
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"hasLaunchedOnce"])
{
result=NO;
} else
{
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"hasLaunchedOnce"];
[[NSUserDefaults standardUserDefaults] synchronize];
result=YES;
}
flag=YES;
}
return result;
}
You can use a custom category method isFirstLaunch with UIViewController+FirstLaunch.
- (BOOL)isFirstLaunch
{
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"kFirstLaunch"]) {
return YES;
}
else {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"kFirstLaunch"];
[[NSUserDefaults standardUserDefaults] synchronize];
return NO;
}
}
And when you need to use it in controller
BOOL launched = [self isFirstLaunch];
if (launched) {
//if launched
}
else {
//if not launched
}
Use NSUserDefaults. If the sharedDefault has a key for your app, its run before. Of course, you'll have to have the app create at least one default entry the first time the app runs.
Swift:
var isFirstLaunch: Bool {
get {
if (NSUserDefaults.standardUserDefaults().objectForKey("firstLaunchDate") == nil) {
NSUserDefaults.standardUserDefaults().setObject(NSDate(), forKey: "firstLaunchDate")
NSUserDefaults.standardUserDefaults().synchronize()
return true
}
return false
}
}
Another tip:
When using NSUserDefaults, these settings will be wiped if the app is ever deleted. If for some reason you require these settings to still hang around, you can store them in the Keychain.