NSUserDefaults crashes consistently in iPhone Simulator - iphone

Before I begin I'll freely admit it's something in my code but it's not blatantly obvious what's causing it.
I'm storing one boolean and four NSDate objects in NSUserDefaults. I have a view that loads the information into controls: boolean into a switch and the 4 date values are loaded as the label on 4 buttons as formatted values. If the user clicks on a button I switch to a new view with a UIDatePicker that is preloaded with the full date from NSUserDefaults based on the button that was clicked. The 2nd view has two buttons: cancel and set. If you choose cancel we switch back to the first view. If you choose set the value from the UIDatePicker is stored in NSUserDefaults, the chosen button's label is updated, and we switch back to the first view.
I can consistently crash the application by clicking a button and then click cancel three times in a row, and then clicking the button a fourth time. It crashes in objc_msgSend when retrieving the value out of NSUserDefaults.
Here's the 3 functions involved.
- (IBAction) buttonPressed:(id)sender {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDate *buttonDate;
switch ([sender tag]) {
case 1000:
pressedString = kStartTime1Key;
pressedButton = start1;
break;
case 1001:
pressedString = kEndTime1Key;
pressedButton = end1;
break;
case 1002:
pressedString = kStartTime2Key;
pressedButton = start2;
break;
case 1003:
pressedString = kEndTime2Key;
pressedButton = end2;
break;
default:
break;
}
buttonDate = [defaults objectForKey:pressedString];
if (buttonDate == nil) buttonDate = [NSDate date];
[datePicker setDate:buttonDate animated:NO];
[buttonDate release];
self.view = datePickerView;
}
- (IBAction) savePressed {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDate *selected = [datePicker date];
[defaults setObject:selected forKey:pressedString];
[pressedButton setTitle:[self parseDate:selected] forState:UIControlStateNormal];
self.view = settingsView;
}
- (IBAction) cancelPressed {
self.view = settingsView;
}
Being an iPhone and Obj-C newbie, I'm sure there are better ways to do things, I'm open to suggestions.

[buttonDate release];
You can't release something you didn't allocate or retain.

Related

UISwitch state in viewdidload

So guys, i've been trying literally for the last week to get this to work.
I have a app that i need a UISwitch to turn on a local notification.
Standard for the switchs state is off, but i need to change this, on load, when the alarm is on.
I've tried scheduledLocalNotification, i've tried a BOOL and i've tried to set int isOn, to 1 when it's on and 0 when it's off, nothing seems to be working for me.
The if loop i use is in ViewDidLoad and looks like this:
if (isAlarmOn == 1) {
NSLog(#"You have notifications");
[setAlarm setOn:YES];
}
else{
NSLog(#"You have no notifications");
[setAlarm setOn:NO];
}
No matter which strategy i try to use, i doesn't seem to work, hope i can get some help from your guys.
Some more code:
The code for my setAlarm
- (IBAction)setAlarm:(id)sender {
NSArray *notificationArray=[[UIApplication sharedApplication] scheduledLocalNotifications];
if (setAlarm.on) {
[alarmStatus setText:#"Alarmen er tændt"];
//Metode til at undersøge om klokken før eller efter officiel solned
NSTimeInterval checkTime = [[astronomicalCalendar sunset] timeIntervalSinceDate:[NSDate date]];
NSLog(#"Alarmen er tændt");
if (checkTime >= 0) {
[self scheduleNotificationToday];
}
else{
[self scheduleNotificationTomorrow];
}
NSLog(#"antal alamer %#", notificationArray);
isAlarmOn = 1;
}
else {
//Metode til at slette alle notifikationer
[[UIApplication sharedApplication] cancelAllLocalNotifications];
NSLog(#"Alarmen er slukket");
NSLog(#"antal alamer %#", notificationArray);
isAlarmOn = 0;
}
}
My UILocalNotification code
- (void)scheduleNotificationToday {
Class cls = NSClassFromString(#"UILocalNotification");
if (cls != nil) {
//Just some stuff determine which time to alert on.
UILocalNotification *notif = [[cls
alloc] init];
notif.fireDate = [gregorian dateByAddingComponents:traekFraDag toDate:[astronomicalCalendar sunset] options:0];
notif.timeZone = [NSTimeZone defaultTimeZone];
notif.alertBody = #"Nu går solen snart ned";
notif.alertAction = #"Show me";
notif.soundName = #"Waterline.caf";
notif.applicationIconBadgeNumber = 1;
[[UIApplication sharedApplication] scheduleLocalNotification:notif];
}
}
Please tell me if there is any other part of the code you would like to see
You need to post more code to give us better context on what these variables are and what your question actually is, but I think you might be looking for:
[setAlarm setOn:YES animated:YES];
and
[setAlarm setOn:NO animated:YES];
..if setAlarm is your switch name??
Make sure that setAlarm really does point to the switch, i.e. it's not nil and it doesn't point to some other switch. And please choose some other name for your variable! ;-)
Also, make sure that you don't have code in -viewWillAppear or some other method that's called after -viewDidLoad that resets the switch's value. In fact, you might want to defer setting up the switch and other UI elements until -viewWillAppear.
but every time the app is unloaded from the memory the uiswitch shows
the default state (off)
You are not storing the state of your switch in any persistant store. That is why it always shows as off if the app exits (dealloc is called for your class).
If you want to save the switch state even when the app is not in memory, then you'll need to save that value somewhere. You can use NSUserDefaults for this if you want.

iOS: How to hide a UITextField on settings change?

I have a settings screen in which I set the units for the app. The user can go from the main screen to the settings screen and after selecting the desired unit, the settings screen is dismissed.
When the user returns to the main screen, I want to hide and show two UITextField.
I tried the following code in viewDidLoad and viewDidAppear but doesn't work:
textBox1.hidden = YES;
textBox2.hidden = NO;
This may be trivial but any help is appreciated. Thanks.
UPDATE:
I have connected the 'Settings' screen with Sugue using push and dismiss the settings screen using [self.navigationController popViewControllerAnimated:YES]; which leads the user back to main screen. Now, I need to refresh the screen controls based on the settings and it's not working
first:
shouldn't it be
self.textBox1 = YES; // or [self.textBox1 setHidden:YES];
self.textBox2 = NO; // or [self.textBox2 setHidden:NO];
Second:
make sure that outlets are not null
NSLog("textBox1 = %#" , self.textBox1);
Third:
Try some code like this
int64_t delayInSeconds = 15.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
self.textBox1.hidden = YES;
self.textBox2.hidden = NO ;
});
Do this in your viewWillAppear or DidAppear. Then in 15 seconds you will see the changes if everything is wired correctly.
Set the following bools to yes or no (in the settings screen).
BOOL hideTextBox1;
BOOL hideTextBox2;
Save the variable (in the settings screen):
NSString *hideTextBox1String;
NSString *hideTextBox2String;
if(hideTextBox1 == YES){
hideTextBox1String = #"YES"
}else{
hideTextBox1String = nil
}
if(hideTextBox2 == YES){
hideTextBox2String = #"YES"
}else{
hideTextBox2String = nil
}
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:hideTextBox2String forKey:#"hideTextBox2"];
[defaults setObject:hideTextBox1String forKey:#"hideTextBox1"];
[defaults synchronize];
Retrieve it in the first viewcontroller:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *hideBox1 = [defaults objectForKey:#"hideTextBox1"];
NSString *hideBox2 = [defaults objectForKey:#"hideTextBox2"];
if(hideBox1 != nil){
textBox1.hidden = YES;
}else{
textBox1.hidden = NO;
}
if(hideBox2 != nil){
textBox2.hidden = YES;
}else{
textBox2.hidden = NO;
}
This will make the change permanent untill you change back. Guess that's what youre looking for.
Since setting the text does not work either I am guessing your IBOutlets may be messed up. To re-do them do the following:
First go into your Storyboard & zoom into the view controller that your working on
Control + Click your UITextView or UITextField, you will see a similar menu:
You can see that I have an IBOutlet set named txtCaseID
If you see an outlet set like I do, go ahead and click the X to remove it
To re-add this outlet, open up the Assistance Editor to view the Storyboard & your .h
Control + Click & Drag from your textBox in your Storyboard to the IBOutlet you made previously in your .h like so:
Make sure to do this for both textBoxes.
Let me know if you need any other help--
thank you for taking time.. I just realized the my code for reading the value for the boolean key from NSUserDefaults was in viewDidLoad which only gets called once :)
I moved that line of code to viewWillAppear and everything works as expected!

Multiple Images for iOS app's Background

I have seen different apps that the image will change each time the app is opened on the view background. How is this accomplished?
It sounds like what you are seeing is the cached screenshot the iOS system is making of your app just before it puts it into the background.
This is handled automatically, and you do have the opportunity to intercept this.
Check out this answer, you can put an image over your app just as it's entering the background, this will be cached and used to relaunch the app.
add background_0.png, background_1.png, background_2.png, background_3.png etc to your project.
#define max_image_number 3
Add the following to your viewDidLoad method:
-(void)viewDidLoad {
[super viewDidLoad];
[self updatedBackgroundImage];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateBackgroundImage) name:UIApplicationWillEnterForegroundNotification object:nil];
}
-(void) updateBackgroundImage{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if (![defaults integerForKey:#"imageNumber"]) {
[defaults setInteger:0 forKey:#"imageNumber"];
}
int i = [defaults integerForKey:#"imageNumber"];
myUIimageView.image = [UIImage imageNamed:[NSString stringWithFormat:#"background_%d.png",i]];
i++;
if (i > max_image_number) {
[defaults setInteger:0 forKey:#"imageNumber"];
}else{
[defaults setInteger:i forKey:#"imageNumber"];
}
...
}
The above will update the image when the viewController is first loaded and also when it returns from running in the background. I assume this is what was missing for you :).
Remember to stop listening for the notification in the viewDidUnload method and you should be set.
Looking at the screenshots in the App Store of the application you mention, it looks like there's just a UIImageView at the back of the view hierarchy for the main menu. You can change this by assigning a UIImage object to its image property.
Suppose you want to change one or more view's background images, depending on the application launch.
Save an int x = 0 into NSUserDefaults. On each application launch increment it by 1. When you are to present a view, check that int and set a background image like this:
switch(x%3){// %3 just to make it a bit random
case 0://set this image;
break;
case 1://set that image
break;
//and so on
}

How to: Get order of tabs, save into NSUserDefaults then retrieve

I have a default UITabBarApplication with default views and tabs... etc. I have eight tabs that have the customizable option, so users can reorder tabs.
How do I get the order of the tabs, save that into NSUserDefaults and retrieve it when the app loads back up. I'd like to see some code examples.
Here is where I'm at so far:
- (void)applicationWillTerminate:(UIApplication *)application {
/*
Called when the application is about to terminate.
See also applicationDidEnterBackground:.
*/
NSMutableArray *vcArray = [NSMutableArray arrayWithCapacity:8];
NSArray *savedViews = tabBarController.viewControllers;
for (UIViewController *theVC in savedViews){
[vcArray addObject:theVC.title];
}
[[NSUserDefaults standardUserDefaults] setObject:vcArray forKey:#"tabLayout"];
}
- (void)tabBar:(UITabBar *)tabBar didEndCustomizingItems:(NSArray *)items changed:(BOOL)changed {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *tabLayout = [defaults arrayForKey:#"tabLayout"];
NSMutableArray *orderedLayout = [NSMutableArray arrayWithCapacity:8];
NSArray *defaultOrder = tabBarController.viewControllers;
for (int i =0; i < 8; i++){
for (UIViewController *theVC in defaultOrder) {
if ([theVC.title isEqualToString:[tabLayout objectAtIndex:i]]) {
[orderedLayout addObject:theVC];
}
}
}
tabBarController.viewControllers = orderedLayout;
}
This code doesn't seem to work in the simulator. I reorder the tabs and hit done then stop the app, but when I hit build and run again the app reverts to default. Why isn't the code above working?
Thanks.
One comment:
NSUserDefaults don't always work with the simulator. If you're doing a new build each time, then of course everything gets reset.
Two suggestions:
put an NSLog in the latter method to make sure that it's being called
build the app to a device, move the tabs around, close the app (completely -- not just in background), open the app again, behold what happens.

iOS Game Center: Creating Lifetime Achievements

I'm trying to add a Lifetime achievement to my iOS app. That is, how many times the user performed the single action since he played the game for the first time. In other words, how many kills he got since... ever. I'm using the code based on the one provided by Apple:
.h file:
int64_t lifetimeScore;
IBOutlet UILabel *lifetimeScoreLabel;
.m file:
- (IBAction) increaseScore {
self.lifetimeScore = self.lifetimeScore + 1;
lifetimeScoreLabel.text = [NSString stringWithFormat: #"%ld", self.lifetimeScore];
// Saving the Score:
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
[defaults setInteger:lifetimeScore forKey: #"Score"];
[defaults synchronize];
[self checkAchievements]; }
- (void)viewDidLoad {
[super viewDidLoad];
// Loading the Lifetime Score:
self.lifetimeScore = [[NSUserDefaults standardUserDefaults] integerForKey: #"Score"];
lifetimeScoreLabel.text = [NSString stringWithFormat: #"%ld", self.lifetimeScore];
(...)
}
I works great, no warnings, but each time I close the app, deletes it from the Multitask bar and open the app again, the lifetimeScore goes back to zero. I'm trying to save the int_64 using NSUserDefaults but so far I can't make it work... any ideas?
EDIT: The code was fixed and now it's working 100% in case anyone wants to use it. The complete source code can found here: http://mobile.tutsplus.com/tutorials/iphone/ios-sdk-game-center-achievements-and-leaderboards-part-2/
#"Store" should be #"Score", just so I can get the points... :)