Getting nil from standardUserDefaults, - iphone

I have created a Settings.bundle for my application, and I try to read the settings by the following code:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if(!defaults)
{
NSLog("can not get default bundle");
}
NSString *usr = [NSString stringWithFormat:#"%#",[defaults stringForKey:#"username"]];
NSLog(usr);
The output in the console is (null). I have used this before and it worked. This situation happened after I upgraded to the latest xcode for iOS. I am not sure if there is any change?
Here is the screen shot from my Settings.bundle.

stringForKey: is returning nil.
It's returning nil because it can't find a key for "username".
It can't find a key for "username" because the key doesn't exist in the user defaults for that app.
The key doesn't exist because either (A) it never existed, for that app, or (B) it existed but it was deleted.
(A) Can happen on the device or simulator if the app gets deleted via Springboard, or if the entire device (Simulator) gets reset. Are either of those two scenarios true?
(B) Can happen if you'd done something in your code to delete the key. Can you confirm that is not the case?

If you switch Simulators (new or regression SDK) or run different OS versions in Simulators, any session information, including defaults, might not carry between these different Simulator runs.

Related

Deleted app and reinstalled and user is still logged in? [duplicate]

I am using idandersen's scifihifi-iphone code for keychain and save password using
[SFHFKeychainUtils storeUsername:#"User" andPassword:#"123"
forServiceName:#"TestService" updateExisting:YES error:&error];
When I delete the application from the device, the password remains in the keychain.
I want to remove the password from the keychain when the user deletes the application from the device. How can I do this?
You can take advantage of the fact that NSUserDefaults are cleared by uninstallation of an app. For example:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//Clear keychain on first run in case of reinstallation
if (![[NSUserDefaults standardUserDefaults] objectForKey:#"FirstRun"]) {
// Delete values from keychain here
[[NSUserDefaults standardUserDefaults] setValue:#"1strun" forKey:#"FirstRun"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
//...Other stuff that usually happens in didFinishLaunching
}
This checks for and sets a "FirstRun" key/value in NSUserDefaults on the first run of your app if it's not already set. There's a comment where you should put code to delete values from the keychain. Synchronize can be called to make sure the "FirstRun" key/value is immediately persisted in case the user kills the app manually before the system persists it.
For users looking for a Swift 3.0 version of #amro's answer:
let userDefaults = UserDefaults.standard
if !userDefaults.bool(forKey: "hasRunBefore") {
// Remove Keychain items here
// Update the flag indicator
userDefaults.set(true, forKey: "hasRunBefore")
}
*note that synchronize() function is deprecated
There is no trigger to perform code when the app is deleted from the device. Access to the keychain is dependant on the provisioning profile that is used to sign the application. Therefore no other applications would be able to access this information in the keychain.
It does not help with you aim to remove the password in the keychain when the user deletes application from the device but it should give you some comfort that the password is not accessible (only from a re-install of the original application).
For those looking for a Swift version of #amro's answer:
let userDefaults = NSUserDefaults.standardUserDefaults()
if userDefaults.boolForKey("hasRunBefore") == false {
// remove keychain items here
// update the flag indicator
userDefaults.setBool(true, forKey: "hasRunBefore")
userDefaults.synchronize() // forces the app to update the NSUserDefaults
return
}
C# Xamarin version
const string FIRST_RUN = "hasRunBefore";
var userDefaults = NSUserDefaults.StandardUserDefaults;
if (!userDefaults.BoolForKey(FIRST_RUN))
{
//TODO: remove keychain items
userDefaults.SetBool(true, FIRST_RUN);
userDefaults.Synchronize();
}
... and to clear records from the keychain (TODO comment above)
var securityRecords = new[] { SecKind.GenericPassword,
SecKind.Certificate,
SecKind.Identity,
SecKind.InternetPassword,
SecKind.Key
};
foreach (var recordKind in securityRecords)
{
SecRecord query = new SecRecord(recordKind);
SecKeyChain.Remove(query);
}
Files will be deleted from your app's document directory when the user uninstalls the app. Knowing this, all you have to do is check whether a file exists as the first thing that happens in application:didFinishLaunchingWithOptions:. Afterwards, unconditionally create the file (even if it's just a dummy file).
If the file did not exist at time of check, you know this is the first run since the latest install. If you need to know later in the app, save the boolean result to your app delegate member.
#amro's answer translated to Swift 4.0:
if UserDefaults.standard.object(forKey: "FirstInstall") == nil {
UserDefaults.standard.set(false, forKey: "FirstInstall")
UserDefaults.standard.synchronize()
}
This seems to be the default behavior on iOS 10.3 based on behavior people have been witnessing in beta #2. Haven't found any official documentation about this yet so please comment if you have.
Just add an app setting bundle and implement a toggle to reset the keychain on app restart or something based on the value selected through settings (available through userDefaults)

Why are Settings default values not showing in iPhone app?

I’m loading default values into the apps Settings using a Root.plist file. The values seem to load fine, and I can see them from Settings on the device.
However, the app itself doesn’t seem to see those values, it only sees the values that have been manually typed into the device (via Settings).
The values appear the same in Settings, whether defaults or device-entered. I use the same code (obviously) for loading the values in the app, whether they are defaults or device-entered.
Anyone see what I'm missing?
Setting the default in Root.plist is quite misleading actually, it doesn't create default value. You have to register the defaults using
[NSUserDefaults standardUserDefaults] registerDefaults:aDictionaryHere];
aDictionaryHere is a NSDictionary object with the key and value you want to register as defaults, for example
[NSDictionary dictionaryWithObject:#"English" forKey:#"Language"];
This key and value will have to match whatever you set in your Root.plist

Where to initialize iPhone application defaults

I just started using the preferences pane to let users customize some settings in my iOS app, and it was pretty easy to create the Settings.bundle and access the information from it. My problem is that I read in the Apple docs that the settings should be programatically initialized:
It is recommended that you register any default preference values programmatically at launch time in addition to including them in your settings bundle property lists. For newly installed applications, default preference values from the application’s settings bundle are not set until the Settings application runs. This means that if the user runs your application before running Settings, the default values specified in your settings bundle will not be available. Setting such values programmatically at launch time ensures that your application always has appropriate values. To register default values programmatically, use the registerDefaults: method of the NSUserDefaults class.
Where in the app is this initialization done, and how can I be sure that I'm not overwriting a user-supplied value? Is this handled in some method of the App Delegate?
You should register your defaults before you try to access a value stored in your NSUserDefaults.
You could do it in - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions.
Registering your defaults is fast, so there is no need to optimize this. Just do it at the launch of the app.
I store my userdefaults in a plist and register the content of this list, like this:
NSDictionary *dictionary = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"DefaultUserDefaults" ofType:#"plist"]];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];
If you register your defaults like this you don't have to worry that you overwrite user supplied values.
NSUserdefaults uses "domains" where it stores it's values. If you register your defaults they are stored in the registration domain. If a User stores a value those values are stored in the application domain.
If you try to get a value from NSUserdefaults it looks if the value is present in the application domain, and if it's not there it takes the value from the registration domain.
Edit:
you would access those values (or better, the values that are stored in your nsuserdefaults, and those as a fallback if there are no user provided values) like this:
NSInteger minutesToWait = [[NSUserDefaults standardUserDefaults] integerForKey:#"MinutesToWait";
NSString *urlString = [[NSUserDefaults standardUserDefaults] stringForKey:#"DefaultURLStr"];
The plist is just another representation of a NSDictionary with keys and values. The key is the same key you use to access the userdefaults, and the value is your defaultvalue.
Pretty straight forward.
It doesn't matter how you create the dictionary. You can do it in code as well.
As #fluchtpunkt suggested, you can register the defaults using:
NSDictionary *dictionary = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"DefaultUserDefaults" ofType:#"plist"]];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];
Personally, I check for each value independently in my App Delegate.
#define kSettings [NSUserDefaults]
if([kSettings boolForKey:#"firstRun"] == nil){
//
// Set up the initial settings...
//
[kSettings setBool:NO forKey:#"firstRun"];
}
I write a "reset" method and then call it on first run. I prefer doing this in code, but you theoretically could use a plist. I just feel like it's one less place to go wrong.
You can use the same method inside your app delegate that you use to setup your initial window, didFinishLaunchingWithOptions:
However, you may also need some logic inside applicationWillEnterForeground:, because potentially your user could put your app into the background, change settings inside the settings app, then resume your app and expect those changes to have been applied.
#MatthiasBauch or #Moshe's answers will most likely work for most people, but for anyone who like me had their settings in a Root.plist file (I was using the InAppSettingsKit), you have to dig a bit deeper into that particular plist file to get the actual default values of the settings (since they're not at the top level of the plist, but nested under the key PreferenceSpecifiers). Here is a link to another answer here, containing the extra code that worked for me:
https://stackoverflow.com/a/10497898/381233

Can Settings bundle use values from application's Info.plist file?

is it possible to use values from application's Info.plist file as DefaultValue value for items in Settings.bundle? For example CFBundleVersion. I've tried entering it as ${CFBundleVersion} but it didn't work. I've also tried changing DefaultValue type but with no success. Any ideas?
The reasons behind are simple:
CFBundleVersion is known at compile
time, so I won't have to take its
value from application's mainBundle
and then apply that value to
NSUserDefaults.
Other reason is that just after
installing the app, but before
running it, Settings bundle values
are not in-sync as the code setting
NSUserDefaults did not have a
chance to execute itself... so it would be
boring to always remember that I have
to change my Settings bundle values
manually.
This is how I synchronize my app's version to use in my Settings.bundle
[defaults setObject:[[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleVersion"] forKey:#"kVersion"];
[defaults synchronize];
I place that piece of code in my didFinishLaunchingWithOptions: app delegate, so every time the app launches it makes sure that the correct version is displayed in my Settings.bundle. This takes the app version value from your info.plist

How can I reset the NSUserDefaults data in the iPhone simulator?

I've added NSUserDefaults data retrieval to my app, which is pretty nice. But for testing I would like to reset all the data I added to the defaults database, so that everything is in the state when the user launches the app the first time.
I tried to call:
[NSUserDefaults resetStandardUserDefaults];
but that doesn't do anything. The defaults are still saved and can be retrieved.
You want NSUserDefaults removePersistentDomainForName. This will remove all user defaults for the application:
NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier];
[[NSUserDefaults standardUserDefaults] removePersistentDomainForName:appDomain];
For more information on the NSUserDefaults class see the Apple docs.
Alternately, if all you are concerned with is data in the iOS Simulator, you can do that via iOS Simulator > Reset Content and Settings.
The easiest way is to remove the app from the simulator-- just like you'd remove it from a real phone, by tapping (clicking) and holding until the icons start vibrating. That removes all app data, and the next time you install from Xcode it's like the first time.
If you have other app data you need to keep, you have a couple of options.
One way would be to have some debug code that calls removeObjectForKey: on each of your defaults keys.
The other is to find the directory where the simulator copy is installed, and remove the file containing the preferences. Use this to find the app:
ls -ld ~/Library/Application\ Support/iPhone\ Simulator/User/Applications/*/*.app
The full path to your app will contain directory whose name is a UUID. In that directory, look in Library/Preferences for the preferences file. Remove that, and user preferences are gone.
You may find out what you have "written" to userdefaults for the app are all inside a file
delete this .plist file works:
user name/Library/Preferences/com.theAppYouAreHandling.plist
In Swift 2.0 the following 1 line will reset all the NSUserDefaults for an app:
NSUserDefaults.standardUserDefaults().removePersistentDomainForName(NSBundle.mainBundle().bundleIdentifier!)
Actually, this may not be suitable in every circumstance, but since I keep no information of value in the simulator, I just Reset Content and Settings from the iPhone menu, from within the simulator itself.
Here's the swift version:
let domainName = NSBundle.mainBundle().bundleIdentifier!
NSUserDefaults.standardUserDefaults().removePersistentDomainForName(domainName)
Til Xcode 6 and iOS 8 Simulator the location of the plist file changed.
I found the *.plist in the following directory:
[1] /Users/SOME-USERNAME/Library/Developer/CoreSimulator/Devices/SOME-DEVICE-ID/data/Library/Preferences/SP.UserDefaultsTest.plist
Deleting the located file from path [1] manually and the NSUserDefaults are gone.
In the simulator top menu:
Simulator -> Reset Content and Settings...
You can use removePersistentDomainForName method available with NSUserDefaults Class.
Syntax :
- (void)removePersistentDomainForName:(NSString *)domainName
Example :
NSString *strAppBundleId = [[NSBundle mainBundle] bundleIdentifier];
[[NSUserDefaults standardUserDefaults] removePersistentDomainForName:strAppBundleId];
If you're doing this in a unittest, you may want to keep the state of your app in the current simulator instead of inadvertently wiping it every time you also run your unittests. One way to do this is to simply hang on to the old values for your app domain in setUp() and then restore them in tearDown():
class MyClass_Tests: XCTestCase {
static let domainName = Bundle.main.bundleIdentifier!
static var oldUserDefaults: [String : Any]?
override class func setUp() {
super.setUp()
// Hang onto this so we don't inadvertently wipe the app's state while running tests.
oldUserDefaults = UserDefaults.standard.persistentDomain(forName: domainName)
}
override class func tearDown() {
// Restore the old values.
UserDefaults.standard.setPersistentDomain(oldUserDefaults!, forName: domainName)
super.tearDown()
}
override func setUp() {
super.setUp()
// Wipe the state for each test.
UserDefaults.standard.removePersistentDomain(forName: MyClass_Tests.domainName)
}
override func tearDown() {
super.tearDown()
}
}
You can find UserDefaults in following path in Finder, delete the .plist
~/Users/<USER>/Library/Developer/CoreSimulator/Devices/<DEVICE_ID>/data/Containers/Data/Application/<APP_ID>/Library/Preferences/<APP_BUNDLE_ID>.plist
Path components to replace:
1. <USER> = MAC user name
2. <DEVICE_ID> = Device/Simulator Identifier, e.g., 999271B8-FAA6-41DE-9864-4111F422ED12
3. <APP_ID> = Application identifier, e.g., 69928AEF-BCD5-413A-B06F-BC4A07080D62
4. <APP_BUNDLE_ID> = Your apps bundle identifier, e.g., com.company.appname.plist