Using NSCoder for restoration between views (viewWillAppear / viewWillDisappear) - ios

I am wondering if there is a way to maintain user input when navigating back and forth to my main menu using the UINavigationBar back arrow using NSCoder. I am currently using NSCoder to maintain this data during background/terminate/restart and NSUserDefaults to maintain data between views, but the combination of NSCoder and NSUserDefaults is giving unpredictable results. Sometimes, both types of restoration work, but sometimes the BG/Term/RS restoration does not work, and I am talking about loading the same identical code on my device. When the NSUserDefaults code is commented out, the BG/Term/RS restoration works every time.
I would like to know if it is possible to use NSCoder for all of my restoration needs, and if so, what that code would look like.
This is what I am using for BG/Term/RS restoration:
-(void)encodeRestorableStateWithCoder:(NSCoder *)coder {
// start level text
[coder encodeObject:_startLevel.text forKey:#"startText"];
// stop level text
[coder encodeObject:_stopLevel.text forKey:#"stopText"];
}
-(void)decodeRestorableStateWithCoder:(NSCoder *)coder {
// start level text
_startLevel.text = [coder decodeObjectForKey:#"startText"];
// stop level text
_stopLevel.text = [coder decodeObjectForKey:#"stopText"];
}
This is the NSUserDefaults code that I am currently using to persist data back and forth between my menu and main view, and which I would ideally like to replace with an NSCoder solution:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// start level
[[NSUserDefaults standardUserDefaults] setObject:_startLevel.text
forKey:#"startLevelRestore"];
// stop level
[[NSUserDefaults standardUserDefaults] setObject:_stopLevel.text
forKey:#"stopLevelRestore"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// start level
[_startLevel setText:[[NSUserDefaults standardUserDefaults]
objectForKey:#"startLevelRestore"]];
// stop Level
[_stopLevel setText:[[NSUserDefaults standardUserDefaults]
objectForKey:#"stopLevelRestore"]];
[[NSUserDefaults standardUserDefaults] synchronize];
}
I have been battling this issue for a couple of days with no luck. Any help would be greatly appreciated! Thanks.

Instead of setting UI objects in decodeRestorableStateWithCoder: use a member variable.
Then in viewWillAppear: use the member variable if it has been set otherwise use the value from NSUserDefault.

Related

Save UISwitch Status - Objective C

I know this has been asked before but I have yet to find a solution to this. I am attempting to save UISwitch state so that no matter which VC I enter, that switch state is still active. However, anytime I leave the VC the switch is in, it's resorts to off. Currently this is the code I'm using to save the switch state:
- (IBAction)tvpSwitch:(UISwitch *)sender {
if (sender.isOn) {
[[NSUserDefaults standardUserDefaults]setObject:#"on" forKey:#"tvpSwitch"];
[[NSUserDefaults standardUserDefaults]synchronize];
}
else {
[[NSUserDefaults standardUserDefaults]setObject:#"off" forKey:#"tvpSwitch"];
[[NSUserDefaults standardUserDefaults]synchronize];
}}
I then put this is any VC viewWillAppear:
-(void)viewWillAppear:(BOOL)animated
{
if ([[[NSUserDefaults standardUserDefaults]valueForKey:#"tvpSwitch"]isEqualToString:#"on"])
{
(sender.isOn=YES);
}
else
{
(sender.isOn=NO);
}}
It also flags in the viewWillAppear method that reads: "Use of undeclared identifier 'sender'."I usually try using the Reference Guide but I'm having a difficult time identifying where this is going wrong. Any help would be great! Thanks!
Like you have an IBAction there, I suppose you created the UISwitch trough Interface Builder. If that's the case, create an IBOutlet from the UISwitch and then always reference to it.
In the first code snippet sender is the parameter passed in the IBAction method which is a reference to the UISwitch.
In the other view controllers you need some reference to that UISwitch but if you want only to check that state without being able to change it in the UI, just get it from NSUserDefaults and use it.
By the way there are designated methods of NSUserDefaults for saving a BOOL type.
- (IBAction)tvpSwitch:(UISwitch *)sender {
[[NSUserDefaults standardUserDefaults] setBool:sender.isOn forKey:#"tvpSwitch"];
}
BOOL switchState;
-(void)viewWillAppear:(BOOL)animated
{
switchState = [[NSUserDefaults standardUserDefaults] boolForKey:#"tvpSwitch"];
// do something with switchState
}

How to make WSCoachMarksView walkthrough only run the first time user opens app?

I'm using WSCoachMarksView to give my users a walkthrough the first time they open up the app, and I obviously only want this to run that first time, and never again after that. I followed the instructions in the documentation that tells you to put the respective code that I have in viewDidAppearso that it only runs once, but it doesn't seem to work.
It still runs through the walkthrough every time the app is open. Is there anything I'm not doing properly to detect the app running previously and setting the BOOL so that it doesn't run again?
SearchViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
// Setup coach marks
NSArray *coachMarks = #[
#{
#"rect": [NSValue valueWithCGRect:(CGRect){{50,168},{220,45}}],
#"caption": #"Just browsing? We'll only notify you periodically of new matches. Need it soon? We'll notify you more frequently, and match you with items that are closer to you."
},
];
self.coachMarksView = [[WSCoachMarksView alloc] initWithFrame:self.tabBarController.view.bounds coachMarks:coachMarks];
[self.tabBarController.view addSubview:self.coachMarksView];
self.coachMarksView.animationDuration = 0.5f;
self.coachMarksView.enableContinueLabel = YES;
[self.coachMarksView start];
}
- (void)viewDidAppear:(BOOL)animated {
// Show coach marks
BOOL coachMarksShown = [[NSUserDefaults standardUserDefaults] boolForKey:#"WSCoachMarksShown"];
if (coachMarksShown == NO) {
// Don't show again
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"WSCoachMarksShown"];
[[NSUserDefaults standardUserDefaults] synchronize];
// Show coach marks
[self.coachMarksView start];
}
}
in viewDidLoad you always start your intro, regardless of what you've set in NSUserDefaults. The check in viewDidAppear is correct, but it's already playing at that time

iOS7 Xcode5 switch state NSUserDefaults

I am pretty new to iOS so here's my question. I have a Table View (GuestTableViewController) listing some guests in a party. When I click in a person, I show a new view (GuestInfoViewController) with some info about this attendee. In this view I have switch button, so if I have 3 persons, there will be 3 switches indicating each one of them is coming or not.
Using NSUserDefaults in a IBAction in my GuestInfoViewController I have achieved to save its state (ON/OFF) between views.
The problem is that when I click one switch, all switches change state. How can reference each one of the switches.
Note: I can post images on my storyboard or even some code if needed.
Thank you so very much!
#implementation GuestInfoViewController
#synthesize nom,cognoms,foto;
#synthesize setNom,setCognoms,setFoto;
#synthesize mySwitch;
- (void)viewDidLoad
{
[super viewDidLoad];
nom.text = setNom;
cognoms.text = setCognoms;
[foto setImage:[UIImage imageNamed:setFoto]];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:#"SwitchState"])
self.mySwitch.on = [defaults boolForKey:#"SwitchState"];
}
- (IBAction)switch:(id)sender {
if(mySwitch.on){
NSLog(#"Switch is ON");
}
if(!mySwitch.on){
NSLog(#"Switch is OFF");
}
}
- (IBAction)saveSwitchState:(id)sender
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([self.mySwitch isOn])
[defaults setBool:YES forKey:#"SwitchState"];
else
[defaults setBool:NO forKey:#"SwitchState"];
}
#end
Your code uses the same key for all the attendees - that is what you should take care of.
Since you obviously set the name and surname for each person (and if we presume that two attendees don't have the same name) you could use this to your advantage.
Change all the #"SwitchState" references
to something like
[NSString stringWithFormat:#"SwitchState_%#_%#",setNom,setCognoms]
This would effectively save the state of the switches for each attendee separately.
Using you're line of thinking, you would need to store 3 keys in NSUserDefaults, one for each person. It would be a mess to use it, for instance, if you have 1000 persons.
I believe the proper way to implement this is by using a Delegate on your GuestInfoViewController.
Here's what I would do:
GuestTableViewController have a list of Person objects, Person object have a BOOL for selected or not.
GuestInfoViewController, reads the BOOL value to show the switch, if the value is changed it fires the delegate and updated the list in GuestTableViewController.
This way, everything is updated and you have all information correct. If you need help doing the delegate, you can find a million examples on Stackoverflow. Or ask and I'll elaborate.
** edit **
When dealing with a simple Person object, you don't even need a delegate, its simplier. Check the project in attach: http://www.brunotereso.net/temp/DelegateProject.zip (Please note this is just a piece of code I've put up to show you how to do it. If you implement something like this, have a look on cellForRowAtIndexPath and use reusable cells)

iOS NSUserDefaults loads slow

I am using NSUSerDefaults to store a couple strings and integers for my application. Whenever a view is opened, the string is loaded slower than the view so you see a glitch. For example, I save the selectedSegmentIndex and then read it in viewDidAppear and for a quick moment when the view is called, no segment is selected, then the right one selects. How do you make it so there is no time gap between the view being opened and the setting be read?
- (void)viewDidLoad
{
[super viewDidLoad];
int segmentIndex = [[NSUserDefaults standardUserDefaults] integerForKey:#"selectedIndex"];
unitSegmentControl.selectedSegmentIndex = segmentIndex;
BOOL location = [[NSUserDefaults standardUserDefaults] boolForKey:#"locationManager"];
[gpsSwitch setOn:location animated:NO];
deviceID.text = [[NSUserDefaults standardUserDefaults] stringForKey:#"DeviceID"];
}
- (IBAction)changeSeg:(id)sender {
if (unitSegmentControl.selectedSegmentIndex == 0) {
[[NSUserDefaults standardUserDefaults] setObject:#"http://98.246.50.81/firecom/xml/units/E01.xml" forKey:#"parserURL"];
[[NSUserDefaults standardUserDefaults] setObject:#"Hillsboro Main" forKey:#"selectedStation"];
[[NSUserDefaults standardUserDefaults] setObject:#"Hillsboro Fire & Rescue" forKey:#"selectedDepartment"];
}
if (unitSegmentControl.selectedSegmentIndex == 1) {
[[NSUserDefaults standardUserDefaults] setObject:#"http://98.246.50.81/firecom/xml/units/E02.xml" forKey:#"parserURL"];
[[NSUserDefaults standardUserDefaults] setObject:#"Hillsboro Witch Hazel" forKey:#"selectedStation"];
[[NSUserDefaults standardUserDefaults] setObject:#"Hillsboro Fire & Rescue" forKey:#"selectedDepartment"];
}
[[NSUserDefaults standardUserDefaults] setInteger:unitSegmentControl.selectedSegmentIndex forKey:#"selectedIndex"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
The defaults are not slow, you’re just loading the data too late. The standard place to populate views is in -viewDidLoad or -viewWillAppear in the view’s controller. Both will update the view soon enough to avoid visual glitches. If any of the two doesn’t work for you, here’s some tips to find the reason:
Try to set the selected index to a hard-wired number. This will tell you if the problem is in the defaults or (much more likely) in the -setSelectedSegmentIndex call.
Move the UI population code to -viewWillAppear. That’s the latest moment to update the UI before it’s displayed.
Use NSParameterAssert to make sure unitSegmentControl is not nil.
Make sure the index read back from the defaults is the expected number. Generally, it’s best to pull the defaults keys into constants. That way you can’t bump into simple typo bugs:
static NSString *const SelectedSegmentKey = #"selectedSegment";
If everything else fails, use a custom UISegmentControl subclass for your unitSegmentControl and place a breakpoint into -setSelectedSegmentIndex to see who else might be calling it.

Using a variable in another Storyboard Tab

What I am trying to do is display a calculated variable from my FirstViewController.m file in a text field in my SecondViewController.m file. The simplest way I found to do this (although probably not the best) was by using NSUserDefaults. The problem I am encountering is that when using the app if I go back to the first tab and change the value of the variable, the text field in the second tab does not refresh to reflect this when I go back onto it. I want it to change automatically without the user having to press a button. Is there any way of doing this? Also a better way to access the variable from the second class would be very useful.
//FirstViewController.m
//Calculation of epleyInt using other text fields and pressing a button.
[epleyField setText:[NSString stringWithFormat:#"%i", epleyInt]];
NSUserDefaults *settings = [NSUserDefaults standardUserDefaults];
[settings setInteger:epleyInt forKey:#"epley"];
[settings synchronize];
and
//SecondViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSUserDefaults *settings = [NSUserDefaults standardUserDefaults];
int epleyInt = [settings integerForKey:#"epley"];
[epley10Field setText:[NSString stringWithFormat:#"%i", epleyInt]];
}
Managed to do it using singleton variables and moving the code to update the textfield to the viewDidAppear method.
I found this very helpful: Simple Passing of variables between classes in Xcode

Resources