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)
Related
I am trying to use NSUserDefaults to access an object I save in my NSUserDefaults.
I have looked everywhere but I must be missing something. Hoping anyone can help me out.
Heres what I have done.
I use Xcode 7.1
My bundle identifier is "com.mycompany.myapp"
I added a watchkit app (new target)
Bundle identifier for watch target is "com.mycompany.myapp.watchkitapp"
Bundle identifier for extension target is "com.mycompany.myapp.watchkitapp.watchkitextension"
I enabled App groups in all 3 targets.
App Group ID for all 3 targets is: "group.com.mycompany.myapp"
All 3 steps are checkmarked in the App Groups section
I use NSUserDefaults.standardUserDefaults() in all cases but the one where I need the object to be accessible from the watch.
In this case I use:
if let userDefaults = NSUserDefaults.init(suiteName: "group.com.mycompany.myapp")
{
userDefaults.setObject(myDate, forKey: UserDefaults.DateKey.rawValue)
userDefaults.synchronize()
}
I can reload this data using this code:
let mydefaults = NSUserDefaults(suiteName: "group.com.mycompany.myapp")
mydefaults!.synchronize()
let myDict = mydefaults!.dictionaryRepresentation()
for entry in myDict
{
print("ENTRY: \(entry)")
}
Works just fine, but executing the same code in awakeWithContext in my watch extensions InterfaceController just prints a few of the default keys. Not the one I set myself.
I basically just need to access a date stored in the iphone app.
Any idea what I am doing wrong?
You can no longer use app groups sharing with watchOS 2. At least I haven't found any solution yet. It is my understanding that you now have to use WatchConnectivity.
https://forums.developer.apple.com/thread/3927
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
Having some trouble with NSUserDefaults here.
Here's how I'm creating it:
NSString *theCity = #"Test City";
[[NSUserDefaults standardUserDefaults] setObject:theCity forKey:#"SavedCity"];
Here's how I'm trying to retrieve it:
if ([[NSUserDefaults standardUserDefaults] objectForKey:#"SavedCity"])
{
NSLog(#"Key exists! %#",[[NSUserDefaults standardUserDefaults] objectForKey:#"SavedCity"]);
}
else {
NSLog(#"No city saved!");
}
The problem I have is that even if there IS a key for "SavedCity" (I check the pref file in the Simulator directory), it always displays "No city saved". Am I doing something wrong?
Thanks!
Two things you could try.
1) Try synchronizing the user defaults after settings the string. [[NSUserDefaults standardUserDefaults] synchronize]
2) Try retrieving the string using -stringForKey:
I ran into a similar problem myself recently. Here's what fixed it for me.
From the iOS Application Programming Guide:
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.
What you should add is:
if ([[NSUserDefaults standardUserDefaults] objectForKey:#"SavedCity"] != nil)
Because you want to check if you've saved something.
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.
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