How do you create and access array elements in settings bundle - objective-c

I want to save strings as array elements in a Settings.bundle Root.plist and be able to access those strings from the Setting bundle on subsequent launches of my app. I could not find a good example of this anywhere. In xcode, I have created the Root.plist and it looks something like:
Key
iPhone Setting Schema
Strings Filename
Preference Items
Item 0 (Title- My Title)
Type Title
Title My Title
Identifier My Identifier
Values
Item 0 my string 1
Item 1 my string 2
Item 3 my string 3
This produces xml as follows:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>StringsTable</key>
<string>Root</string>
<key>PreferenceSpecifiers</key>
<array>
<dict>
<key>Type</key>
<string>PSTitleValueSpecifier</string>
<key>Title</key>
<string>My Title</string>
<key>Key</key>
<string>My Identifier</string>
<key>Values</key>
<array>
<string>my string 1</string>
<string>my string 2</string>
<string>my string 3</string>
</array>
</dict>
I am using the following code to attempt an access of the Values from the Identifier key "My Identifier":
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *mystrings = [defaults arrayForKey:#"My Identifier"];
for (int i=0;i<3;i++) {
NSLog(#"%#", [mystrings objectAtIndex:i]);
}
The value of mystrings is 0 at runtime. Consequently, the Values of "my string 1", "my string 2", and "my string 3" are not getting printed by the NSLog.
Can someone help me with this. Also, what would be the correct way to update those values?

Part of your problem is mentioned under the heading "Specifying Default Values for Preferences":
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 are unavailable.
All the stuff in the Settings bundle doesn't get loaded for your app's defaults database until after the user opens the Settings Application. Does this seem stupid to you? Seems stupid to me. The result is that you need to register all those defaults initially yourself. I wrote this while I was trying to figure out what was going on; improvements can definitely be made, but it should get you started:
NSURL * settingsURL = [[NSBundle bundleWithURL:[[NSBundle mainBundle] URLForResource:#"Settings" withExtension:#"bundle"]]
URLForResource:#"Root" withExtension:#"plist"];
NSDictionary * settingsDict = [NSDictionary dictionaryWithContentsOfURL:settingsURL];
NSArray * settingsArr = [settingsDict objectForKey:#"PreferenceSpecifiers"];
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
for( NSDictionary * setting in settingsArr ){
NSString * key = [setting objectForKey:#"Key"];
if( !key ) continue; // Some objects don't have keys
if( ![defaults objectForKey:key] ){
[defaults setObject:[setting objectForKey:#"DefaultValue"]
forKey:key];
}
}
Another problem that you have is that you don't get an array for PSTitleValueSpecifier keys when you do [defaults objectForKey:#"myTitlePreferencesKey"]; you just get one string. A #"DefaultValue" key/value pair is also required to be part of that item's dictionary. Initially you will get that value, which must be a string. If you change the value, you'll get the string from the Titles array which has the same index as that object in the Values array. I'm pretty sure that if you have a Values array, you also have to have a Titles array.
For the second part of your question, if you want to change the values of any of the preferences, you simply do
[[NSUserDefaults standardUserDefaults] setObject:myValue
forKey:#"myKey"];

Related

How to edit a plist file by key type

I want to add into my plist file present in "prefPlist" variable some key with its relative type.
The example content must be:
<dict>
<key>App Version</key>
<string>Beta 1</string>
<key>Configuration Type</key>
<integer>7</integer>
<key>Creation Date</key>
<date>27 04 2013, 03:00</date>
<key>Want some milk?</key>
<true/>
</dict>
The first key must have the string type, the second the integer type, the third the date time type and the fourth a boolean value.
How can I do this?
<key>prefList</key>
<array>
<string>string</string>
<string>integer</string>
<string>date</string>
<string>boolean</string>
</array>
Matteo:
You should be able to read the NSDictionary and NSMutableDictionary messages supported HERE
In the meantime, it seems as though you want to use, as an example:
NSString *source = [[NSBundle mainBundle] pathForResource:#"FileName ofType:#"plist"];
myDictionary = [[[NSDictionary alloc] initWithContentsOfFile:source] mutableCopy];
This of course assumes that the plist is in your bundle. Use FileManager to your sandbox documents or elsewhere to get the right location before loading with dictionary.
Frank

How come reading from a plist fails in my iOS application?

I am trying to create an NSMutableDictionary containing a list of animals in my iOS app with this function.
// puts animals into dictionary
- (void) putAnimalsFromPlistToDictionary
{
NSBundle *bundle = [NSBundle mainBundle];
NSString *path = [bundle pathForResource:#"myAnimals" ofType:#"plist"];
myAnimalDictionary = [[NSMutableDictionary alloc] initWithContentsOfFile:path];
// I set a breakpoint here in XCode.
}
I dragged myAnimals.plist (which looks like the below) into my supporting files folder for my application.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<string>elephant</string>
<string>monkey</string>
<string>cat</string>
</array>
</plist>
However, when I reach the breakpoint (as listed in my comment), I see that myAnimalDictionary is nil. Why?
Perhaps it's because you have an array in your .plist but you're trying to instantiate a mutable dictionary instance.

Setting Bundle not outputting correct value

Here's the issue, I launch my app from Xcode and it gets up and running then I switch to settings.app and change a toggle from NO to YES and when I switch back to my app, the key outputs NO not YES.
i think i'm running up against the quote below, but not sure how to get around it, if the user launches the app, and goes to settings and changes the toggle, its now out of sync because Settings.app outputs on first launch to NO. Doesn't make sense that a user can't change the setting the first time they switch to settings.app
"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 are unavailable."
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *appDefaults = [NSDictionary dictionaryWithObject:#"NO" forKey:#"hideActionBar"];
[defaults registerDefaults:appDefaults];
[defaults synchronize];
and then the code i use to check it
- (void)applicationDidBecomeActive:(UIApplication *)application {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
BOOL hidden = [defaults boolForKey:#"hideActionBar"];
NSLog(#"%d",hidden);
if (hidden) {
viewController.actionButton.enabled = NO;
} else {
viewController.actionButton.enabled = YES;
}
}
and my settings Root.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Type</key>
<string>PSToggleSwitchSpecifier</string>
<key>Title</key>
<string>Hide Action Bar</string>
<key>Key</key>
<string>hideActionBar</string>
<key>DefaultValue</key>
<false/>
<key>TrueValue</key>
<true/>
<key>FalseValue</key>
<false/>
</dict>
</plist>
For BOOL variables you need not to set any value if want it to be NO. Because when you will try to access it first time it will return you the same. So remove registerDefaults code(all four lines) from your applicationDidFinishLaunchingWithOptions method.
Explanation:
When you change setting from NO to YES and launch your application, in applicationDidFinishLaunchingWithOptions this value is once changed to NO programmatically.
Note: Whenever you use settings bundle in your application, prior to registering a value(object value, because primitive types will return 0) check whether that value is nil(it means still unregistered), if the value for key is nil then only register with initial default values else you will end up in changing the values each time programmatically.
Thanks,

Getting (default) value from settings bundle

I have an IOS5 project.
I have added a settings bundle to my project and added some settings to it.
I set properties of 'host_ip':
<dict>
<key>AutocapitalizationType</key>
<string>None</string>
<key>AutocorrectionType</key>
<string>No</string>
<key>DefaultValue</key>
<string>http://localhost</string>
<key>IsSecure</key>
<false/>
<key>Key</key>
<string>host_ip</string>
<key>KeyboardType</key>
<string>URL</string>
<key>Title</key>
<string>Host</string>
<key>Type</key>
<string>PSTextFieldSpecifier</string>
</dict>
I try to read value like this:
NSUserDefaults *userDefaults =[NSUserDefaults standardUserDefaults];
NSString *host = [userDefaults stringForKey:#"host_ip"];
NSLog(#"%#",host);
It does not return the default value I set in host_ip, it returns nil.
How can I get my default value?
UPDATE
After a user edits a setting, I can retrieve it by the code above. My problem is getting the setting value, if it was not edited by the user yet. As I think in that case it should return the default I set in plist.
Did you register the defaults via NSUserDefaults?
See the registerDefaults: method on NSUserDefaults.

A smart way to retrieve allowed file types from plist

Scenario:
I like to define the allowed file types (content types) in the Info.plist file of my Cocoa application. Therefore, I added them like the following example shows.
# Extract from Info.plist
[...]
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>public.png</string>
<key>CFBundleTypeIconFile</key>
<string>png.icns</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSIsAppleDefaultForType</key>
<true/>
<key>LSItemContentTypes</key>
<array>
<string>public.png</string>
</array>
</dict>
[...]
Further, my application allows to open files using an NSOpenPanel. The panel allows to set the allowed file types through the following selector: setAllowedFileTypes:. The documentation states that UTI can be used.
The file type can be a common file extension, or a UTI.
A custom solution:
I wrote the following helper method to extract the UTI from the Info.plist file.
/**
Returns a collection of uniform type identifiers as defined in the plist file.
#returns A collection of UTI strings.
*/
+ (NSArray*)uniformTypeIdentifiers {
static NSArray* contentTypes = nil;
if (!contentTypes) {
NSArray* documentTypes = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleDocumentTypes"];
NSMutableArray* contentTypesCollection = [NSMutableArray arrayWithCapacity:[documentTypes count]];
for (NSDictionary* documentType in documentTypes) {
[contentTypesCollection addObjectsFromArray:[documentType objectForKey:#"LSItemContentTypes"]];
}
contentTypes = [NSArray arrayWithArray:contentTypesCollection];
contentTypesCollection = nil;
}
return contentTypes;
}
Instead of [NSBundle mainBundle] also CFBundleGetInfoDictionary(CFBundleGetMainBundle()) can be used.
Questions:
Do you know a smarter way to extract the content type information
from the Info.plist file? Is there a Cocoa-build-in function?
How do you deal with the definition of folders that can contained
there, e.g. public.folder?
Note:
Throughout my research, I found this article quite informative: Simplifying Data Handling with Uniform Type Identifiers.
Here is how I read information from a plist (it can be the info.plist or any other plist you have in you project provided you set the correct path)
NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
NSString *fullPath = [NSString stringWithFormat:#"%#/path/to/your/plist/my.plist", resourcePath];
NSData *plistData = [NSData dataWithContentsOfFile:fullPath];
NSDictionary *plistDictionary = [NSPropertyListSerialization propertyListFromData:plistData mutabilityOption:NSPropertyListImmutable format:0 errorDescription:nil];
NSArray *fileTypes = [plistDictionary objectForKey:#"CFBundleDocumentTypes"];