Save (Private) Application Settings on iOS? - iphone

I'm aware of NSUserDefaults for saving/restoring user preferences. What is the equivalent class for an application? For example, the application may have a "last run" field; or it may have a field for a unique identification of the device for use at the application level.
My intention is to keep the application's settings (not user's settings) out of the Settings Application, and not backup those settings in iTunes, Time Machine, {whatever}.
I'm getting a lot of noise for Java and C#, but not much for iOS/iPhone/iPad.

NSUserDefaults can be used for what you're asking.
if (![[NSUserDefaults standardUserDefaults] boolForKey:#"shownPrompt"]) {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"shownPrompt"];
// Show your prompt or whatever
}
That's a working code snippet. If the key is false, it sets it to true and shows the prompt. The next time this code runs, the key will already by true, so the prompt won't be shown.
NSUserDefaults is specific to the current app on the current device, and is similar to an NSMutableDictionary in that it's a key-value system, with the difference that instead of instantiating your own, there's a universal shared instance for your whole app, that doesn't get erased when the app exits.
NSUserDefaults is perfect for saving things like whether something has been shown, the date of last run, etc. Read the docs here: https://developer.apple.com/documentation/foundation/userdefaults
Don't be put off by the 'user preferences' part. You can use it to save anything you want (as long as it is or can be converted to an NSObject which implements <NSCoding>, which basically means NSString, NSDictionary, NSArray, NSNumber, UITextField, int, float, bool, etc.).
Just to clarify, stuff you put in NSUserDefaults will not, under any circumstances, automagically turn up in the Settings app. It will be kept completely private and hidden. For something to appear in Settings, you need to add a Settings bundle to your app, and manually add keys to it for each and every value that you want to be visible in the Settings app.

if you can store value by NSUserDefaults, then it is good to store application preferences too.
or add settings.plist on your project and read that (what you are not changing later)
and you can use like.,
+ (NSDictionary*)getBundlePlist:(NSString *)plistName
{
NSString *errorDesc = nil;
NSPropertyListFormat format;
NSString *plistPath = [[NSBundle mainBundle] pathForResource:plistName ofType:#"plist"];
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
NSDictionary *temp = (NSDictionary *)[NSPropertyListSerialization
propertyListFromData:plistXML
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:&format errorDescription:&errorDesc];
return temp;
}
+ (id) getPropValue:(NSString *)PropertyName
{ // I am supposing you had add your app preferences on settings.plist.
return [[Property getBundlePlist:#"settings"] objectForKey:PropertyName];
//here Property is my class name, then you can use value by
//NSString *value = [Property getPropValue:#"setting1"];
}

It's hard to tell what you're asking. It sounds like NSUserDefaults is what your looking for, but you claim that you're already aware of it. So what's your problem?

Related

Is it possible to read the structure of plist file?

I know that NSUserDefaults can read the key and value from plist file.
Is it possible to read the structure of a key?
For example:
Key 'Count' is an integer and has option
1,2,3,4,5
the codes below can get the value 'Count'
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
if([defaults objectForKey:#"Count"]!=nil)
{
NSString *s=[[NSString alloc] initWithString: [defaults objectForKey:#"Count"]];
NSInteger v=[s intValue];
[s release];
}
But I prefer to get all options and store to a NSArray or a better storage structure.
Is it Possible?
I read bundle settings the following way:
// Get path to Root.plist file in settings bundle and retrieving its contents
NSString* tPath = [[NSBundle mainBundle] pathForResource:#"Settings" ofType:#"bundle"];
settingsBundle = [[NSBundle bundleWithPath:tPath] retain];
NSDictionary* tSetDict = [NSDictionary dictionaryWithContentsOfFile:[tPath stringByAppendingPathComponent:#"Root.plist"]];
// Get array of preference dictionaries
NSArray* prefs = [tSetDict objectForKey:#"PreferenceSpecifiers"];
// Iterate through dictionaries to find required value
for (NSDictionary* setDict in prefs){
NSString* type = [setDict objectForKey:#"Type"];
if (![type isEqualToString:#"PSMultiValueSpecifier"]){
// Get possible preference values for PSMultiValueSpecifier case
// You may need to know value type in advance - not sure about that
NSArray* values = [setDict objectForKey:#"Values"];
}
}
I'm not exactly sure I understand your question but you can't directly access the user defaults system save through its defined methods. You can't read them out in one big chunk.
The defaults system isn't actually a means of reading plist files, its an more of an API for accessing a database maintained by the OS itself. Although, you don't see it much on the iPhone, its actually a very large and complex system for managing preferences not only for individual apps but also for users, groups of users, computers and networks. It seems trivial on iOS because you don't have the flexibility of configuration and use that you have on MacOS proper.
It would be impossible to read out the entire defaults because they are huge and much larger than you would expect even on iOS.
Instead of jumping through the hoops in the code you showed in the answer, you should access the data in the key using one of the dedicated methods for the type of data stored. In this case:
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
NSInteger v=[defaults integerForKey:#"Count"];
Even if you did read the defaults out in one chunk, you would just find yourself using the same type of calls and code to access the data in the alternate data structure as you would in using the defaults in the first place. You might as well use the defaults system.

iPhone App Localization - English problems?

I have an app that I am translating to a bunch of different languages. The problem is that the app will have a few different values in Australia than will in New Zealand, which are both English speaking countries.
I have created an en_AU and an en_NZ language file, but they're both using the standard English file. I deleted the English language file, but it continues to happen...
Any ideas on how I can get this to work?
Thank you,
--d
iPhone localisations (or is that localizations?) do not take any notice of the Region the user sets (ie, UK, Aus, NZ). There is only one "English" language translation available by default. However, you can hack around with things to force it to use a different translation setting - I've just done this for choosing between "English" (US) and "en_GB" (British english).
In your main.m file, alter it so it looks something like below (put in your own tests for NZ or AU)
int main(int argc, char *argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Set up the locale jiggery pokery
NSString *language = [[NSLocale preferredLanguages] objectAtIndex:0];
NSString *locale = [[NSLocale currentLocale] objectForKey: NSLocaleCountryCode];
if ([language isEqualToString:#"en"] && [locale isEqualToString:#"GB"]) {
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:#"en_GB", #"en", nil] forKey:#"AppleLanguages"];
}
int retVal = UIApplicationMain(argc, argv, nil, nil);
[pool release];
return retVal;
}
This pops the users language (eg "en") into the language NSString, and the users locale (eg, NZ, GB, AU) into the locale NSString. If they (in my case) match en and GB, then I set the users default language preference settings to be "en_GB", then "en".
Then, in your application delegates application:didFinishLaunchingWithOptions method you want to remove that NSUserDefaults setting you just set with the code
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"AppleLanguages"];
It's safe to remove at this point because all the bundle initialisation has been completed. Your app should now be using a Localization.strings file within the en_GB.lproj directory.
It's a bit of a horrible, hacky solution, but it works for me.
I've come up with what I think is a slightly improved version of rickerbh's accepted answer. The first thing to realize is that user defaults are organized into domains, and the #"AppleLanguages" key comes not from the app's domain, but from some domain higher up the hierarchy of domains. This means it is completely safe to remove it from user defaults:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults removeObjectForKey:#"AppleLanguages"];
After calling this code, you'll notice that calling [defaults objectForKey:#"AppleLanguages"] still returns a value. So, rather than deleting #"AppleLanguages" at some point later, which could be problematic depending upon the complexity of your app, you want to do the opposite: delete #"AppleLanguages" immediately. Essentially, this resets it back to its default value and captures any changes to it the system has made, if, for instance, the user has changed her preferred language.
Here's what I do:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults removeObjectForKey:#"AppleLanguages"];
NSMutableArray *appleLanguages = [[defaults objectForKey:#"AppleLanguages"] mutableCopy];
NSString *region = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];
NSArray *languages = [appleLanguages filteredArrayUsingPredicateFormat:#"not (self contains '-')"];
for (NSString *language in languages) {
NSString *languageAndRegion = [NSString stringWithFormat:#"%#-%#", language, region];
[appleLanguages removeObject:languageAndRegion];
}
for (NSString *language in languages) {
NSString *languageAndRegion = [NSString stringWithFormat:#"%#-%#", language, region];
NSInteger index = [appleLanguages indexOfObject:language];
[appleLanguages insertObject:languageAndRegion atIndex:index];
}
[defaults setObject:appleLanguages forKey:#"AppleLanguages"];
(Note that filteredArrayUsingPredicateFormat: is an extension method I wrote. It's not rocket science to figure out what it does or how it works.)
This creates localizations for every language in the list combined with the user's region. E.g., if the original list was es en en-GB and the user's region is AU, we'll get es-AU es en-AU en en-GB. Note that es-AU doesn't exist, but it makes no difference. Since the app finds no associated localizations or resources, it just ignores it.
Apple documents this missing iOS feature here.
Important: In iOS, the bundle
interfaces do not take dialect or
script information into account when
looking for localized resources; only
the language designator code is
considered. Therefore if your project
includes language-specific project
directories with both a language and
region designator, those directories
are ignored. The bundle interfaces in
Mac OS X do support region designators
in language-specific project
directories.
I had the same problems with German and think I found the "right" solution. The mistake was, that originally my base language was only "German (de)", when adding "German/Austria (de_AT)" localizations, the files were ignored. When changing the base language to "German/Germany (de_DE)" the austrian translations were not ignored.
Worth noting here that XCode is very misleading - you can go to Project (not Target), Info, Localizations, press the + button that appears under the list of languages, then scroll down to the bottom of the list of languages that appear in the popup until you get to "Other" (with right arrow next to it), this will open a nice big list which includes regional variants for languages. However, these regional variants do not work on the iPhone - you don't get anything (as documented by Apple and referenced in another answer that appears here). Evidently one of the code-based solutions listed above is necessary.

Store preferences in a file and import values to initialize variables

I want to store the content of some variables in a file. The user nor the application should change the value. It would be great if the user cannot read the content of this file. On startup the application reads the file and initialize some (global?) variables with the content.
The file should contain some values like server URL and so on. This could change, but I only want to manage a preference file rather than updating the source code. On an update for example only the preference file will get exchanged.
How can I manage that?
NSUserDefaults is not intended for such issues I think. Should I use a a plist or a normal txt file?
How would the access to the content of the file look like?
Cheers
Sounds to me like NSUserDefaults is exactly what you need. It will let you store URLs (as strings) and other basic types of variables.
Why do you think NSUserDefaults is not the right solution here?
Give it a try! It's easy to use and reliable.
Use a plist. You can load it with -[NSDictionary initWithContentsOfFile:] (you can save a dictionary to a plist just as easily with -[NSDictionary writeToFile:atomically:], though it doesn't sound like you need to do that).
So the solution I'll use is that I load a plist file as default value for my NSUserDefaults:
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"Settings" ofType:#"plist"];
NSDictionary *settingsDict = [NSDictionary dictionaryWithContentsOfFile:filePath];
[[NSUserDefaults standardUserDefaults] registerDefaults:settingsDict];
NSUserDefaults *settings = [NSUserDefaults standardUserDefaults];
NSString *serverURL = [settings stringForKey:#"ServerURL"];
NSLog(#"%#", serverURL);
Taken from iPhone App : Where do I put a config file?
So I'll use both plists and NSUserDefaults. settings I'll define as global variable in the main.m. One problem remains:
How to differentiate between user defaults and system defaults?

Saving application data state on exit

I have an NSMutableArray with 24 strings.
I need to save this data if a user gets a call or quits the application.
I have been looking into many examples but for some reason can’t seem to determine the best way to save the data.
The 24 strings correspond to 24 buttons and their state. When a button is clicked, it displays the corresponding Array info for that buttons tag (0 – 23). What I would need to retain is if 10 buttons where clicked and their data shown, how/what would be the best way of retaining this data so it can be reloaded when the app starts?
I am thinking I would need to store:
Button Tag,
Buttons corresponding Array value,
Button state (whether it has clicked and value is show or not)
I would store this data on exit of the application and then when app is started again, I would determine if this stored data exists, if so populate the array and examine the button states to determine if it had already been shown and if so, set it accordingly. Then when this file was loaded, I would delete the stored data (.DAT file if stored this way). This way if a user quits gracefully, on next start up, it would start a new game.
I have looked at several examples where they store data into a .DAT file but am having problem implementing this….and wondering if this is even the best way.
Any help or thoughts on this is greatly appreciated.
Geo…
you should be able to store it in NSUserDefaults
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:buttonState forKey:#"buttonState"];
[defaults synchronize];
// Then to get the state back
NSMutableArray* buttonState = [[[NSUserDefaults standardUserDefaults] arrayForKey:#"buttonState"] mutableCopy];
You could save the data to a plist in the Documents directory. If the data is there, load it up, and if not, it would suggest a clean run.
To load the data:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documents = [paths objectAtIndex:0];
NSString *filePath = [documents stringByAppendingPathComponent:#"buttonState.plist"];
NSMutableDictionary *buttonState = [[NSMutableDictionary alloc] initWithContentsOfFile:filePath];
To save the data:
[buttonState writeToFile:filePath atomically: YES];
I save my data by using NSKeyedArchiver/NSKeyedUnarchiver, since your NSMutableArray already supports the NSCoding protocol, you can just use
yourArray = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
[NSKeyedArchiver archiveRootObject:yourArray toFile:file];
Edit:
I suppose NSArray's own methods work also
[NSArray writeToFile:]
[NSArray initWithContentsOfFile:]

Changing the language for NSLocalizedString() in run time

I have an iPhone application which has a button to change display language in run time. I have looked at NSLocalizedString() which will return the appropriate strings according to system preference. What are my options rather than hard coding all the display strings and return according to user language selection in run time? Any pointers will be much appreciated.
Based on the post by the user "object2.0", I've put together some sample code you can use in your application to change the UI language on the fly.
The main localization class that does the hard work:
-(NSString *) localized:(NSString *) key
{
GameInfo *gameInfo = [GameInfo sharedInstance];
// langCode should be set as a global variable somewhere
NSString *path = [[NSBundle mainBundle] pathForResource:langCode ofType:#"lproj"];
NSBundle* languageBundle = [NSBundle bundleWithPath:path];
return [languageBundle localizedStringForKey:key value:#"" table:nil];
}
Assuming you have this function in a global class called utils, call this function with the following code (for example to output the word "Settings".
NSLog( [utils localized:#"Settings"] );
To change the language:
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:langCode, nil] forKey:#"AppleLanguages"];
Use to set language order by force
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:#"en",#"de",..., nil] forKey:#"AppleLanguages"];
[[NSUserDefaults standardUserDefaults] synchronize];
then use
NSLocalizedString();
to show localized string...
The trick to use specific language by selecting it from the app is to force the NSLocalizedString to use specific bundle depending on the selected language ,
here is the post i have written for this http://learning-ios.blogspot.com/2011/04/advance-localization-in-ios-apps.html
and here is the code of one sample app https://github.com/object2dot0/Advance-Localization-in-ios-apps
The correct "User experience" is for the user to select their language via the system preference panel; not your app (or your app's settings panel, etc.). There is no way to override this per-app and you wouldn't want any app changing the system wide setting.