NSUserDefaults - How should I fix this code? - iphone

I'm trying to make some sort of setup wizard that remembers a specific view however I have some problems with my AppDelegate.m code since I'm using ARC.
Does anyone know how to fix this, because If I compile the app crashes at the splash screen..
My AppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSString *controllerName = [[NSUserDefaults standardUserDefaults] objectForKey:#"WIZARD_VIEW"];
if ([controllerName length]) {
Class controllerClass = NSClassFromString(controllerName);
UIViewController *controller = [[controllerClass alloc] init];
// Override point for customization after application launch.
return YES;
}
To be more clear, in the viewcontroller files I added the following code as a suggestion:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[NSUserDefaults standardUserDefaults] setObject:[[self class] description] forKey:#"WIZARD_VIEW"];
NSLog(#"ViewWillAppear Done.");
[[NSUserDefaults standardUserDefaults] synchronize];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"WIZARD_VIEW"];
NSLog(#"viewWillDisappear");
[[NSUserDefaults standardUserDefaults] synchronize];
}
Crashlog while compiling:
/Users/Tim/Documents/XCODE_DEV/App/AppDelegate.m:17:33: No visible #interface for 'NSUserDefaults' declares the selector 'forKey:'
/Users/Tim/Documents/XCODE_DEV/App/AppDelegate.m:20:27: Unused variable 'controller'

The first time you have to register the defaults.To register the defaults isn't enough to call the setObject:forKey method, you have to use the registerDefaults method:
- (void)registerDefaults:(NSDictionary *)dictionary;
Documentation:
NSUserDefaults
So in your case:
NSUserDefaults* standard=[NSUserDefaults standardUserDefaults];
[standard registerDefaults: #{ #"WIZARD_VIEW" : [[self class]description]} ];
You better place these lines in the initialize method, which gets called before any non-static method.If the defaults are already registred there's no problem, this will not overwrite the defaults that you have if is the 2nd+ time that the application starts.

Looking at the compiler warnings, I'm going to guess that in your AppDelegate.m, on line 17 you are calling forKey: on NSUserDefaults.
NSUserDefaults doesn't have a forKey: method, so it throws an exception.
It's probably just a square bracket in the wrong place.

Related

Root View Controller not changing on BOOL

I am trying to change the Root View controller if the application has been opened once. Here is what im doing. The boolean is fully functional and i know because it prints 'Welcome Back'. Here is what im trying to do.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
if ([[NSUserDefaults standardUserDefaults] boolForKey:#"HasLaunchedOnce"])
{
// App Already launched
RootMenuController *viewController = [[RootMenuController alloc] init];
[UIApplication sharedApplication].keyWindow.rootViewController = viewController;
}
else
{
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"HasLaunchedOnce"];
[[NSUserDefaults standardUserDefaults] synchronize];
// This is the first launch ever
}
[self.window makeKeyAndVisible];
return YES;
}
It seems like you are not setting/updating application window properly. I have created an sample project which might help. https://github.com/deepthit/UpdateRootViewController.git

How to run something once when the app is launched?

I'm trying to play an intro movie when my app is getting launch, but am totally driven crazy already, I did a lot of testing in my code, along with trying to use this
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
NSLog(#"App started to launch");
[self.window makeKeyAndVisible];
[self.window addSubview:_intro.view];
return YES;
}
but that's make my video run, even if I'm coming from the background.
If like pressing the middle button, then double pressing the middle button and pressing app icon, I get the movie to play again.
I forget to mention that this is my ViewDidLoad
- (void)viewDidLoad
{
[super viewDidLoad];
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
BOOL launchedBefore = [userDefaults boolForKey:#"hasRunBefore"];
NSLog(#"value of hasRunBefore is %d",launchedBefore);
if(!launchedBefore)
{
[userDefaults setBool:1 forKey:#"hasRunBefore"];
launchedBefore = [userDefaults boolForKey:#"hasRunBefore"];
NSLog(#"value of hasRunBefore is %d",launchedBefore);
[self playvideo];
}
}
NSUserDefaults does not commit the changes to disk until you send the synchronize message.
Try adding doing this:
[userDefaults setBool:YES forKey:#"hasRunBefore"];
[userDefaults synchronize];

iPhone didFinishLaunchingWithOptions Method

So i'm trying to use this method to load a certain theme based on what the user chose in the settings bundle. When I insert NSLog it will load the default them (Modern Theme), but it will never change to the Pink Theme.
Is this method loaded every time the app is launched, even if the app is still running in the background.
Otherwise, where else could I do this, if I want to use the settings bundle.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSDictionary *userDefaultsDefaults = [NSDictionary dictionaryWithObjectsAndKeys: #"Modern Theme", #"theme", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:userDefaultsDefaults];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
theme =[defaults objectForKey:#"theme"];
NSLog(#"%#", theme);
if ([theme isEqualToString:#"Modern Theme"]) {
viewController = [[viewTwo alloc] initWithNibName:#"viewTwo" bundle:nil];
}
else {
viewController = [[viewOne alloc] initWithNibName:#"viewOne" bundle:nil];
}
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
Have you tried putting your code in applicationDidBecomeActive:? That one is guaranteed to be called whether on the initial launch or resuming from the background.
Reference docs.
The method
- (void)applicationWillResignActive:(UIApplication *)application
is fired when the app is resumed from the background.
I'd nearly load the theme in the ViewController's
-(void)viewDidAppear:(BOOL)animated
method.

NSUserDefaults proper usage and Detecting Change

I'm a bit of a beginner, and building an iOS Universal app which makes use of NSUserDefaults at various stages of the application lifecycle - including an in-App page to set these variables. I find that my UserDefaults are often out-of-sync with the UI, and it gets tedious to write code to check for these preferences.
As you can see below, I'm checking to see if the "Use Camera" preference is set an aweful lot; there must be an easier way...?
Get's worse when we load the viewController that allows in-app editing of user preferences:
Is there some way to eliminate some of the amount of code (and therefore, minimize debugging/effort for memory optimization), instead of having to check in to what the value of the preferences are a lot of the time? Should I be using the NSUserDefaultsDidChangeNotification to detected a change, and set AppDelegate variables once inside this notification method?
Example (omissions of the obvious assumes those methods are there... just not related to my preferences:
In my AppDelegate:
#progma mark Application Lifecycle
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
// check values; if not present, set some defaults.
if (![prefs stringForKey:#"pfUseCamera"]) {
[prefs setValue:#"NO" forKey:#"pfUseCamera"];
}
if (![prefs stringForKey:#"pfWorkInBackground"]) {
[prefs setValue:#"NO" forKey:#"pfWorkInBackground"];
}
[prefs synchronize];
self.backgroundTasksTimer = [NSString stringWithString:[prefs stringForKey:#"pfWorkInBackground"]];
self.shouldUseCamera = [NSString stringWithString:[prefs stringForKey:#"pfUseCamera"]];
// start our timer
// chose to run this timer ALL-the-time, dispite the user-preference; instead, see below that the timer method returns based on the user preference.
[NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(backgroundTasks:) userInfo:nil repeats:YES];
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// pretty much the same as above
// except NSTimer, which is controlled elsewhere
}
#progma mark AppDelegate-controlled Methods
- (void)backgroundTasks:(NSTimer *)timer {
if (![self.backgroundTasksTimer boolValue]) {
return;
} else {
// do stuff in a timer begun in AppDelegate only if this preference is NOT NO
}
}
My primary ViewController:
#progma mark ViewController Lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
MyAppDelegate *app = [MyAppDelegate sharedAppDelegate]; // I have this set up in the AppDelegate to allow this
if ([app.shouldUseCamera boolValue]) {
// init the camera and have it ready for use
}
}
- (void)viewDidAppear:(BOOL)animated {
MyAppDelegate *app = [MyAppDelegate sharedAppDelegate]; // I have this set up in the AppDelegate to allow this
if ([app.shouldUseCamera boolValue]) {
// resume the camera capture
}
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
MyAppDelegate *app = [MyAppDelegate sharedAppDelegate];
if ([app.shouldUseCamera boolValue]) {
// suspend the camera capture
}
}
- (void)viewDidUnload {
MyAppDelegate *app = [MyAppDelegate sharedAppDelegate];
if ([app.shouldUseCamera boolValue]) {
// unload the camera
}
}
#progma mark User/IBAction Methods
-(IBAction)doStuff:(id)sender {
MyAppDelegate *app = [MyAppDelegate *app = [MyAppDelegate sharedAppDelegate]; sharedAppDelegate];
// take a picture
if ([app.shouldUseCamera boolValue]) {
[NSThread detachNewThreadSelector:#selector(takePicture:) toTarget:self withObject:fileName]; // take a photo and do stuff to it in a separate thread
}
}
SettingsViewController:
- (void)viewDidLoad {
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
MyAppDelegate *app = [MyAppDelegate sharedAppDelegate];
// set UITextField to value of String Preference
someUITextField.text = [prefs stringForKey:#"pfSomeOtherValue"];
// set UISwitch to value of PSToggleSwitchSpecifier
if ([app.backgroundTasksTimer boolValue]) {
[shouldExecuteBackgroundTasks setOn:YES];
} else {
[shouldExecuteBackgroundTasks setOn:NO];
}
// set UISwitch to value of PSToggleSwitchSpecifier
if ([app.shouldUseCamera boolValue]) {
[shouldUseCamera setOn:YES];
} else {
[shouldUseCamera setOn:NO];
}
- (IBAction)done:(id)sender {
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
MyAppDelegate *app = [MyAppDelegate sharedAppDelegate];
[prefs setObject:companyID.text forKey:#"pfCompanyID"];
if ([shouldUseCamera isOn]) {
[prefs setObject:#"YES" forKey:#"pfUseCamera"];
app.backgroundTasksTimer = #"YES";
} else {
[prefs setObject:#"NO" forKey:#"pfUseCamera"];
app.backgroundTasksTimer = #"NO";
}
if ([shouldExecuteBackgroundTasks isOn]) {
[prefs setObject:#"YES" forKey:#"pfWorkInBackground"];
app.shouldUseCamera = #"YES";
} else {
[prefs setObject:#"NO" forKey:#"pfWorkInBackground"];
app.shouldUseCamera = #"NO";
}
[prefs synchronize];
[self.delegate settingsControllerDidFinish:self];
}
one approach is to get rid of the fields in your app delegate and just use NSUserDefaults. I'm not sure of the overhead with this but have a "Config" class with static accessors. Also, you might consider using the bool (and other) accessors like:
#implementation Config
+(BOOL)shouldUseCamera {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
return [defaults boolForKey:#"shouldUseCamera"];
}
+(void)setShouldUseCamera:(BOOL)should {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:should forKey:#"shouldUseCamera"];
}
Then it's cake to access these from anywhere. I'll use NSBundle and NSProcessInfo in the same Config class and then my app has a single place for all config.
Also, from what I've read, you don't normally have to call synchronize.
Take a look at this topic which goes through registering for the change notification on default changes. Nsuserdefaultschange
If multiple views need to check this value you could just expose the value as a property of your app delegate and check that property when needed. The app delegate would subscribe to the notification and reload the property value when it fires.

Need advice about how to cache text data on iPhone

Could you give me some algorithm or example?
NSUserDefaults will nicely hold onto snippets of data.
Example:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
[window addSubview:tabBarController.view];
if ([[NSUserDefaults standardUserDefaults] integerForKey:kTabIndexKey])
tabBarController.selectedIndex = [[NSUserDefaults standardUserDefaults] integerForKey:kTabIndexKey];
}
- (void)applicationWillTerminate:(UIApplication *)application {
[[NSUserDefaults standardUserDefaults] setInteger:tabBarController.selectedIndex forKey:kTabIndexKey];
}
Persists and loads an integer (your method would be "setString" and "stringForKey", check the docs.)