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
Related
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)
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 implemented local notification in my app but I am just wondering is there a way to play a sound that is not part of the main bundle of iPhone App.
Basically in my app, I want user to record a sound that gets played when the local notification is generated instead of playing a pre-recorded or default sound.
As far as i know this can be implementable because i have seen 2-3 App in app store which is doing the same thing which i want to do
- (void)alertSelector:(NSString *)AlertTitle WithFiringTime:(NSDate *)date
{
UILocalNotification *localNotification = [[[UILocalNotification alloc] init] autorelease];
[localNotification setFireDate:date];
[localNotification setTimeZone:[NSTimeZone defaultTimeZone]];
NSDictionary *data = [NSDictionary dictionaryWithObject:date forKey:#"payload"];
[localNotification setUserInfo:data];[localNotification setAlertBody:AlertTitle];
[localNotification setAlertAction:#"View"]; [localNotification setHasAction:YES];
localNotification.soundName=#"voice.aif";
if (!localNotification)
return;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
}
My guess is your sound isn't formatted appropriately to play by the OS. Make sure your sound is formatted in IMA4 format. Also, make sure your file is in your application bundle, and you should be good to go.
For more reference and similar question, see this SO question.
Choose custom sound for Local Notifications
Here is a link for Apple's Local and Push Notification Guide. It explains the sounds in detail and the default sounds that can be used.
From the description of the soundName property -
For this property, specify the filename (including extension) of a
sound resource in the application’s main bundle or
UILocalNotificationDefaultSoundName to request the default system
sound.
It doesn't seem that sounds other than those present in the bundle can be used. Are you sure that the apps are using local notifications with their custom sounds? Maybe they are playing sounds with alerts or something like that.
HTH,
Akshay
notif.soundName = #"sound.caf";
That should work. Make sure the sound is actually in your app’s bundle, is in the correct format (linear PCM or IMA4—pretty much anywhere that explains how to convert sounds for iOS will tell you how to do that), and is under 30 seconds.
You can convert from wav and mp3 using terminal :
afconvert -f caff -d LEI16#44100 -c 1 in.wav sound.caf
'in.wav' is input sound which u want to convert and 'sound.caf' is output file ,change directory to where u want to have the out put file
Make sure your sound file is added to the "Copy Bundle Resources" list under Project Settings->(Target)->Build Phases.
This fixed the issue for me. At first I only got the default sound, even though I specified a custom file name (and the file was added to the project). But after I manually added the file to this list it started working.
I used a system sound from my mac to test with. I converted it using this command in a console:
afconvert /System/Library/Sounds/Basso.aiff ~/Downloads/alert2.caf -d ima4 -f caff -v
Well I was experiencing same Issue , It sounds like this was not supported before or not doable to play custom sound for local notification that is not included in App Bundle. But later this is supported and you can assign custom sounds from App container specifically in Library/Sounds directory . And it worked with me . you can either download sounds from server to this directory or test it by copying a .caf(Apple said other extensions may work) file from bundle to Library/sounds directory and then remove reference for the file from bundle so that you can make sure that if sounds played it is from directory out of bundle . This all works very well , the main issue with me if user force quoted the app it stops working for local notification while it still works with push kit notifications , Im scheduling local one though when push kit push arrives.
Here is the code for testing playing sound from directory :
func copyFileToDirectoryFromBundle()
{
// get reference to library directory
let LibraryDirectoryPaths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.LibraryDirectory, NSSearchPathDomainMask.UserDomainMask, true)
// see what is in there
for path in LibraryDirectoryPaths
{
print("di=\(path)")
}
// prepare for sound directory
let libraryDirectoryPath: AnyObject = LibraryDirectoryPaths[0]
let soundsDirectoryPath = libraryDirectoryPath.stringByAppendingPathComponent("Sounds")
// create sounds directory under library if not there already
if !NSFileManager.defaultManager().fileExistsAtPath(soundsDirectoryPath)
{
do {
try NSFileManager.defaultManager().createDirectoryAtPath(soundsDirectoryPath, withIntermediateDirectories: false, attributes: nil)
print("sounds library created,,,")
} catch let error as NSError {
print("creating sounds directory error = \(error.localizedDescription)");
}
}
else
{
print("Sounds directory is there already under Library")
}
do {
// sounds directory should have been added under library
let directoryContents = try NSFileManager.defaultManager().contentsOfDirectoryAtURL( NSURL(string: libraryDirectoryPath as! String)!, includingPropertiesForKeys: nil, options: [])
print("Library directs=\(directoryContents)")
// prepare for adding subdirectory with file name from bundle
// here is where you should save files if downloaded from server
// here im just moving one from bundle
let subDirectoryPath = directoryContents.last!
if let pathFromBundle = NSBundle.mainBundle().pathForResource("notification", ofType:"caf") {
let myFilePathUnderSounds = "\(subDirectoryPath.path!)\("/notification.caf")"
if !NSFileManager.defaultManager().fileExistsAtPath(myFilePathUnderSounds)
{
do {
print("bundle path=\(pathFromBundle)and datacontainer path=\(subDirectoryPath.path!)")
try NSFileManager.defaultManager().copyItemAtPath(pathFromBundle, toPath:myFilePathUnderSounds )
print("item copied")
} catch let error as NSError {
print(error.localizedDescription);
}
}
else
{
print("Your file from bundle path = \(pathFromBundle) is already there under Sounds")
}
}
else
{
print("Item not found in bundle")
}
// see item there after you add it
let subDirectories = try NSFileManager.defaultManager().contentsOfDirectoryAtURL( subDirectoryPath, includingPropertiesForKeys: nil, options: [])
print("files under sounds")
for fileDirectory in subDirectories
{
print(fileDirectory)
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
After making sure .caf file is under Library/Sounds directory . All you have to do is merely assigning file name with extension to local notification soundName property like if the file is in bundle.
are you cleaning the old notifications with?
UIApplication* app = [UIApplication sharedApplication];
NSArray* oldNotifications = [app scheduledLocalNotifications];
if ([oldNotifications count] > 0) {
[app cancelAllLocalNotifications];
}
else you can, delete the app from the phone (maybe need a reboot too)
the it should work :-)
beside that the code mentioned above should be correct.
and this line localNotification.soundName=#"voice.aif"; should be enough to play custom sounds.
but if you had notification tested first with no sound-file added, the default sound file (UILocalNotificationDefaultSoundName) is registered! and to re-register you have to cancel the previous notification (cancelAllLocalNotifications) or delete the app, to be able to "re"-register your notfication,
beside that, the apple doc says:
Sounds that last longer than 30 seconds are not supported. If you
specify a file with a sound that plays over 30 seconds, the default
sound is played instead.
hope it helps now :-)
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 am trying to detect the existence of a plist file on application start. When my data class (descendant of NSObject) is initialized, it loads the plist file.
Before it loads, it checks to see if the file exists, and if it doesn't, it sets the data = nil instead of trying to process the array of data in the plist file.
However, every time it starts it indicates the file exists. Even when I uninstall the app in the organizer and when wiping the simulator directory (iPhone Simulator/3.0/Applications).
I have the following static method in a library class named FileIO:
+(BOOL) fileExists:(NSString *)fileName {
NSString *path = [FileIO getPath:fileName]; // gets the path to the docs directory
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) return YES;
else return NO;
}
In my data class:
//if( [FileIO fileExists:kFavorites_Filename] ) [FileIO deleteFile:kFavorites_Filename];
if ([FileIO fileExists:kFavorites_Filename]) {NSLog(#"exists");
The "exists" is logged every time unless I un-comment the method to delete the file.
How can the file exist if I've deleted the app? I've deleted the simulator directory and uninstalled on the device but both say it still exists.
What gives???
This makes sense since plists stores preferences and things. Well when you restore you iPhone you delete all apps. But, if the plists are saved then your preferences are saved once you re-sintall the apps back on your iPhone.