I've been passively using this site for some time and have read the FAQ so I hope that I will be able to keep these at a minimum.
I've a question about Settings.bundle files in Objective-C. I've been using the following guides to implement one of those:
I'm using both Xcode 4.1 and the latest Xcode 3. I used version 3 to create the Settings.bundle and its plist as I found Xcode 4's plist editor to be somewhat buggy.
These are the contents of my 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>StringsTable</key>
<string>Root</string>
<key>PreferenceSpecifiers</key>
<array>
<dict>
<key>Type</key>
<string>PSGroupSpecifier</string>
<key>Title</key>
<string>Authentication</string>
</dict>
<dict>
<key>Type</key>
<string>PSTextFieldSpecifier</string>
<key>Title</key>
<string>User ID</string>
<key>Key</key>
<string>user_id_key</string>
<key>DefaultValue</key>
<string>1234</string>
<key>IsSecure</key>
<false/>
<key>KeyboardType</key>
<string>Alphabet</string>
<key>AutocapitalizationType</key>
<string>None</string>
<key>AutocorrectionType</key>
<string>No</string>
</dict>
</array>
</dict>
</plist>
I can access the settings in the iOS settings menu without a problem and also change the value of the single entry that I have so far. The value gets stored between sessions on the iOS simulator as well as on my physical iOS device (an iPhone 4).
I do however seem to be unable to access this value from my code. Let me show you how I tried to implement it (in the ViewController.m of my app):
- (void)viewDidLoad
{
[super viewDidLoad];
myDefaults = [NSUserDefaults standardUserDefaults];
// DEBUG
userID = [myDefaults stringForKey:@"user_id_key"];
// userID = [NSString stringWithString:@"1234"];
// DEBUG
jsonBackendURL = @"http://example.com/jsonBackend.php";
passphrase = @"secretPassphrase";
theURLConnectionController = [[URLConnectionController alloc] initWithCallerObject:self];
[theURLConnectionController getConnectionAndUpdate:NO];
}
Strangely enough when I try to run the app in Xcode 3 I neither get any error messages (like a SIGABRT or such) nor any debugger output. The screen of the iPhone Simulator goes black for a moment as it is trying the start the app and then I'm being taken back to the home screen.
Xcode 4 shows some rather odd behavior. It manages to show the UI of my application for a second or two and then takes me back to the home screen of the iPhone Simulator. It also indicates a SIGABRT some way farther down in my code where I try to get a value from a JSON response:
NSString *arrivalToday = [NSString stringWithString:[jsonResponse objectForKey:@"from"]];
As the JSON string is generated by a PHP script using GET parameters passed in by my app - one of these the user ID from the Settings.bundle - it is of little wonder that this shouldn't work. It shouldn't crash however as I catch this error earlier in my code but that's likely not connected to my settings problem. A real pain in the neck though is the fact that Xcode (kind of) crashes along with my app in the iPhone Simulator. First, Xcode keeps working normally except the fact that I can't abort the program which still appears to run. I can click the stop button in Xcode but to no effect. When I try to exit Xcode it asks me if it should terminate the running program and when I click yes it crashes for real and I have to force quit it.
Fortunately though, Xcode 4 gives me a call stack of my app:
2011-07-21 11:41:04.539 OneClickCheckIn[2098:b303] http://example.com/json.php?user=(null)&pass=secretPassphrase&getStartToday
2011-07-21 11:41:04.888 OneClickCheckIn[2098:b303] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSPlaceholderString initWithString:]: nil argument'
*** Call stack at first throw:
(
0 CoreFoundation 0x00de25a9 __exceptionPreprocess + 185
1 libobjc.A.dylib 0x00f36313 objc_exception_throw + 44
2 CoreFoundation 0x00d9aef8 +[NSException raise:format:arguments:] + 136
3 CoreFoundation 0x00d9ae6a +[NSException raise:format:] + 58
4 Foundation 0x007ca14c -[NSPlaceholderString initWithString:] + 105
5 Foundation 0x007d3266 +[NSString stringWithString:] + 72
6 OneClickCheckIn 0x00002b5c -[OneClickCheckInViewController updateInterfaceWithJsonResponse:] + 172
7 OneClickCheckIn 0x0000e82c -[URLConnectionControllerDelegate connectionDidFinishLoading:] + 364
8 Foundation 0x007de112 -[NSURLConnection(NSURLConnectionReallyInternal) sendDidFinishLoading] + 108
9 Foundation 0x007de06b _NSURLConnectionDidFinishLoading + 133
10 CFNetwork 0x030fa48e _ZN19URLConnectionClient23_clientDidFinishLoadingEPNS_26ClientConnectionEventQueueE + 220
11 CFNetwork 0x031c56e1 _ZN19URLConnectionClient26ClientConnectionEventQueue33processAllEventsAndConsumePayloadEP20XConnectionEventInfoI12XClientEvent18XClientEventParamsEl + 293
12 CFNetwork 0x031c59cf _ZN19URLConnectionClient26ClientConnectionEventQueue33processAllEventsAndConsumePayloadEP20XConnectionEventInfoI12XClientEvent18XClientEventParamsEl + 1043
13 CFNetwork 0x030f0c80 _ZN19URLConnectionClient13processEventsEv + 100
14 CFNetwork 0x030f0acf _ZN17MultiplexerSource7performEv + 251
15 CoreFoundation 0x00dc38ff __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 15
16 CoreFoundation 0x00d2188b __CFRunLoopDoSources0 + 571
17 CoreFoundation 0x00d20d86 __CFRunLoopRun + 470
18 CoreFoundation 0x00d20840 CFRunLoopRunSpecific + 208
19 CoreFoundation 0x00d20761 CFRunLoopRunInMode + 97
20 GraphicsServices 0x0101a1c4 GSEventRunModal + 217
21 GraphicsServices 0x0101a289 GSEventRun + 115
22 UIKit 0x00042c93 UIApplicationMain + 1160
23 OneClickCheckIn 0x00002609 main + 121
24 OneClickCheckIn 0x00002585 start + 53
25 ??? 0x00000001 0x0 + 1
)
terminate called throwing an exception(lldb)
That's all of the relevant information coming to my mind right now. I've done extensive research on Google and have also read some threads on this site but I couldn't find a solution.
I've finally been able to solve it thanks largely to the above mentioned guide: Adding a settings bundle to an iPhone App.
There it says:
[...] it is important to understand that until the user actually changes the value of the setting nothing is actually set. If you check for the setting in your application it will actually return nil unless you set a default value. To do that add the following to the applicationDidFinishLaunching (or didFinishLaunchingWithOptions) method:
// Set the application defaults
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *appDefaults = [NSDictionary dictionaryWithObject:@"YES" forKey:@"enableRotation"];
[defaults registerDefaults:appDefaults];
[defaults synchronize];
I wasn't aware of that. Moreover it seems to me that the values in the NSDictionary have to initialized in some form or later changes by the user are not going to take effect either (I tried time and again to change the values in the iPhone Simulator while I'd been looking for a solution and it didn't work). This might only affect the iPhone Simulator though, I haven't had the chance to test it on my physical device yet. My initialization code now looks like this (in the AppDelegate):
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys:
@"1234", @"user_id_key",
@"http://example.com/json.php", @"json_url_key",
@"secretPassphrase", @"passphrase_key",
nil];
[defaults registerDefaults:appDefaults];
[defaults synchronize];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
With that code I can easily access the NSUserDefaults values later in my code:
- (void) reloadPreferences {
myDefaults = [NSUserDefaults standardUserDefaults];
userID = [myDefaults objectForKey:@"user_id_key"];
jsonBackendURL = [myDefaults objectForKey:@"json_url_key"];
passphrase = [myDefaults objectForKey:@"passphrase_key"];
}
I also spent some time hunting down a bug concerning the order of the key-value pairs of the NSDictionary until it dawned on me that the value comes first followed by the key in the definition. Coming from a Perl background this is somewhat alien to me so that might be something to watch out for if you're getting nil values from your NSUserDefaults object.
This cost me about three days to fix so I hope that the solution might come in handy for some. :-)