Restoring multiple text fields and labels in one view controller - ios

I have a view controller containing 2 text fields, 2 segmented controls and a few labels which display time stamps set by the user using UIButtons. I would like to be able to restore any user set values for these items upon quit / restart, as well as when going back to my main menu view controller, which is a navigation controller. Using the following code, I am able to restore one of the text fields on background / terminate / restart, but I am unsure as to how to accomplish this for my other text field or the time stamp labels and segmented controls. I have tried to duplicate the restoration code with changes for the text field name and the #"UnsavedText" string with no luck.
Furthermore, whenever I go back to the main menu using the back button in the navigation bar, I lose all of my data from all fields.
Here is the code in my delegate, opting in to state restoration:
// Sets RESTORATION
-(BOOL)application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder
{
return YES;
}
-(BOOL)application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder
{
return YES;
}
Here is the relevant code in my view controller to restore the one text field. I am including my viewDidLoad code with state initialization in case that is somehow part of the problem:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
// Updates time on currentTimeLabel
[self updateTime];
// Sets initial button states
[self setInitialState];
// Sets UITextField delegate
self.startLevel.delegate = self;
self.stopLevel.delegate = self;
// initializes basic values for segmented controls
bigTank = YES;
startFractions = #"";
stopFractions = #"";
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
// Restoration of text fields
-(void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
// start level text
[coder encodeObject:_startLevel.text forKey:#"UnsavedText"];
[super encodeRestorableStateWithCoder:coder];
}
-(void)decodeRestorableStateWithCoder:(NSCoder *)coder
{
// start level text
_startLevel.text = [coder decodeObjectForKey:#"UnsavedText"];
[super decodeRestorableStateWithCoder:coder];
}
// Sets initial button and label states
- (void)setInitialState
{
self.start.enabled = YES;
self.stop.enabled = NO;
self.calculate.enabled = NO;
self.resume.enabled = NO;
// Text for time labels reset
_startTimeLabel.text = #"- -:- -:- -.- -";
_stopTimeLabel.text = #"- -:- -:- -.- -";
// Resets the minuteRateLabel
_minuteRateLabel.text = #"--.--";
// Resets the text fields to their initial state with the placeholder "inches" text
[_startLevel setText:nil];
[_stopLevel setText:nil];
// Resets inch levels to EVEN
[self.startFractionControl setSelectedSegmentIndex:0];
[self.stopFractionControl setSelectedSegmentIndex:0];
startFractions = #"";
stopFractions = #"";
}
Thanks in advance!
EDIT:
This is the code I tried per the suggestion:
My button click should ostensibly save the time stamp value:
- (IBAction)startButton:(UIButton *)sender
{
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"hh:mm:ss.SS"];
_startTimeLabel.text = [formatter stringFromDate:[NSDate date]];
UIButton *startButton = (UIButton *)sender;
// Creates the start time stamp for use in the calculation
startTime = [NSDate timeIntervalSinceReferenceDate];
// sets button states
startButton.enabled = NO;
stop.enabled = YES;
// Hides keypad on startButton click
[self.startLevel resignFirstResponder];
[self.stopLevel resignFirstResponder];
// saves the value for restoration using user defaults
NSDateFormatter *startTimeSave = formatter;
[[NSUserDefaults standardUserDefaults]
setObject:startTimeSave forKey:#"startTimeSaver"];
}

You can save your text values using user defaults. Here is a sample code snippet:
NSString *valueToBeSaved = #"someValue";
[[NSUserDefaults standardUserDefaults]
setObject:valueToBeSaved forKey:#"preferenceName"];
And for retrieving your value later, use this:
NSString *savedValue = [[NSUserDefaults standardUserDefaults]
stringForKey:#"preferenceName"];

Related

Saving segmented controller position

I am facing an issue that my segmented controller is not saving its position after closing the application and opening it again.
My code is as per below:
- (void)viewDidLoad {
[super viewDidLoad];
[self.segmentedControlButtonStyle addTarget:self action:#selector(changeButtonStyle:) forControlEvents:UIControlEventValueChanged];
}
- (IBAction)changeButtonStyle:(id)sender {
NSUserDefaults *sharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.number.application"];
NSInteger selectedSegmentedControlerIndex = self.segmentedControlButtonStyle.selectedSegmentIndex;
if (sharedDefaults) {
[sharedDefaults setInteger: selectedSegmentedControlerIndex forKey:#"MySelectedButtonStyleKey"];
[sharedDefaults synchronize];
}
}
The funny thing is that NSUserDefaults actually is saving correct index because from method I provided above if I change button style it will keep changed after closing and opening application again because I can see it by fact but segmented controller itself is not showing correct segment.
I am not sure why this is happening because I am synchronizing after each segment change but still segmented controller keeps its default position.
in view did load you should add code to set your saved segment
NSUserDefaults *sharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.number.application"];
int mySegment = [sharedDefaults integerForKey:#"MySelectedButtonStyleKey"];
if(mySegment) {
self.segmentedControlButtonStyle.selectedSegmentIndex = mySegment;
}

Objective-C loop to compare two textfields for passwords

I am trying to extend one of my iOS projects to ask the user to input a password and then re-type the password and if the passwords don't match, keep doing it until they do. I can do it by setting up IBOutlets in my project and then do the comparison, but then how do I loop the comparison if they don't match? I've searched a lot for this but haven't found a simple answer other than a github project called PTPasscodeController, which is too complicated for what I want do, i.e., simple input and comparison, not extensive security.
I tried to get this to work by UIAlertController, but it doesn't seem possible to initialize and then check the loop for the comparison of the two textfields. That is, I know how to get an array of textfields through UIAlertController, but not how to keep putting up the alert when the two textfields don't match.
If there is code to accomplish this, or a better way, I would appreciate the help a lot!
how do I loop the comparison if they don't match?
You don't loop at all. In UI environments you accomplish "ask again" behavior by disabling actions and showing errors.
I am assuming that in addition to the "password" and "retype password" fields you have some kind of the "I am done" button (e.g. "Next", "Done", etc.) which should be disabled initially.
You should respond to textField:shouldChangeCharactersInRange:replacementString: in the protocol of text fields, check the two passwords for equality, and do one of two things:
If both passwords are non-empty, valid, and equal, enable the "Next" button
Otherwise, put red border or some other visual feedback on the "retype password" field, to tell end-user that they made a mistake while entering their password the second time.
Here is my code that works.
I have two view controllers. The first view controller is a tableView Controller that checks for a password required in the settings bundle. If a password is required, it performs a segue to the second view controller.
The second view controller compares a password to a re-entered password and sets the text color depending on whether the two match. Also, if a password was previously set, it deletes the second password request from the view (by checking the subview tags from the storyboard) and just compares the previously entered password against the password the user now enters.
Not very elegant, but it works for my purposes.
The first view controller's relevant code:
NSUserDefaults *defaults =[NSUserDefaults standardUserDefaults];
NSLog(#"defaults %#", [defaults
dictionaryRepresentation]);
BOOL passwordProtect = [[defaults valueForKey:#"password"]boolValue];
if(passwordProtect)
{
self.passwordEntered = YES;
[self performSegueWithIdentifier:#"passcodeViewController" sender:self];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:#"passcodeViewController"]){
NRCPasscodeControllerViewController *destViewController = segue.destinationViewController;
destViewController.password = self.password;
}
}
The second view controller's code:
#interface NRCPasscodeControllerViewController : UIViewController <UITextFieldDelegate>
#property (weak, nonatomic) IBOutlet UITextField *password1;
#property (weak, nonatomic) IBOutlet UITextField *password2;
#property (strong, nonatomic) NSString *password;
#property (nonatomic) BOOL passwordWasEntered;
#end
implementation file:
#import "NRCPasscodeControllerViewController.h"
#interface NRCPasscodeControllerViewController ()
#end
#implementation NRCPasscodeControllerViewController
- (IBAction)checkPasswords:(id)sender {
// if a password has not been entered, display
// both password fields
if((self.passwordWasEntered == NO)){
// password1 must be 4 characters in length
if ([self.password1.text length] == 4) {
self.password1.textColor = [UIColor greenColor];
} else {
self.password1.text =#"Invalid!";
self.password1.textColor = [UIColor redColor];
}
// password2 must be 4 characters in length
if ([self.password2.text length] == 4) {
self.password2.textColor = [UIColor greenColor];
} else {
self.password2.text =#"Invalid!";
self.password2.textColor = [UIColor redColor];
}
// passwords must match
if([self.password1.text isEqual:self.password2.text]){
self.password2.textColor = [UIColor greenColor];
// passwords match, so set the password and segue back
self.password = self.password1.text;
[self performSegueWithIdentifier:#"unwindToTableView" sender:self];
}
else{
self.password2.textColor = [UIColor redColor];
}
}
// if password has already been entered, just check the first password
// field against the entered password.
else if (self.password == self.password1.text){
// password matches entered password, so set the password and segue back
self.password1.textColor = [UIColor greenColor];
[self performSegueWithIdentifier:#"unwindToTableView" sender:self];
}
else{
// password does not match entered password
self.password1.textColor = [UIColor redColor];
self.password = self.password1.text;
}
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField{
[textField resignFirstResponder];
return YES;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.password1.delegate = self;
self.password2.delegate = self;
[self.navigationItem setHidesBackButton:YES animated:YES];
// Do any additional setup after loading the view from its nib.
}
-(void)viewWillAppear:(BOOL)animated{
// if self.password is not nil, a password has already been entered
// so only display one password field to check.
if(self.password){
[[self.view viewWithTag:3] removeFromSuperview];
[[self.view viewWithTag:2] removeFromSuperview];
self.passwordWasEntered = YES;
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
#end
and in the first controller that receives the unwind segue:
-(IBAction)unwindFromPasscodeController:(UIStoryboardSegue*)segue{
NSLog(#"Unwound from passcode controller");
NRCPasscodeControllerViewController *sourceViewController = segue.sourceViewController;
self.password = sourceViewController.password;
}

ivar for view controller in appdelegate.m

edit 1
I am adding some code to indicate its state after I have tried to work with codeInOrange's answer so far which so far behaves like my code originally behaved, that is sample link shows up at first in the text field and can be altered by the user, but when the user returns to the VC, any new link text has been replaced by the original sample link. My reason for posting this additional code is to try to reconnect with codeInOrange's promising answer because I am misunderstanding the logical flow of his original suggestions and his later comments.
In the current Storyboard I am leaving the Text field and the Placeholder Text empty because the sample link seems to be adequately supplied by the viewDidLoad method below.
- (void)viewDidLoad
{
[super viewDidLoad];
self.urlNameInput.text = #"sample http";
// Do any additional setup after loading the view, typically from a nib.
self.urlNameInput.clearButtonMode = UITextFieldViewModeAlways;
self.urlNameInput.clearsOnBeginEditing = NO;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
if (textField == self.urlNameInput) {
[textField resignFirstResponder];
[self processPbn];
}
return YES;
}
- (void)viewDidAppear:(BOOL)animated
{
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
// self.urlNameInput.text = appDelegate.stringForTextField;
appDelegate.stringForTextField = self.urlNameInput.text;
}
- (void) processPbn
{
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:self.urlNameInput.text] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
[NSURLConnection sendAsynchronousRequest:theRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *connection, NSData *data, NSError *error)
{
// lots of detail code has been elided in this method
self.iboard = 0;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:toMatch options:NSRegularExpressionDotMatchesLineSeparators error:&error];
for (NSTextCheckingResult* board in [regex matchesInString:string options:NSRegularExpressionDotMatchesLineSeparators range:NSMakeRange(0, [string length])])
{
if (self.iboard>0) {
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
appDelegate.stringForTextField = self.urlNameInput.text;
}
}];
}
edit 1
edit 0
I do not want to preserve the text between application shutdowns and launches, so the answer using NSUserDefaults is not quite what I need.
Also, it appears from my trials that the solution suggested by Michael Dautermann which suggests either putting my intialization text in viewDidLoad or in the Xib or Storyboard, does not work because the text always returns to its initial value upon return to the VC (likely because the viewDidLoad method is triggered), so I think I do need to create an ivar in my AppDelegate.m as I asked in my original question, and not in my ViewController.m viewDidLoad, to get the desired result, apparently. Perhaps it would be easier to create a B00L ivar in AppDelegate.m which is a flag that tells whether original text or current text is desired. But I cannot figure out how to do that, either. So, please consider this edit in your answer.
edit 0
My AppDelegate.m contains the following code.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
BDViewController *vc = [sb instantiateInitialViewController];
self.viewController = (id)vc;
}
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
In the VC I want an ivar, an NSString, to be set at launch so that it can be the example text in my UITextField. Later I want that UITextField to be adjusted to an new value when the user supplies valid text into a UITextField.
Currently in my VC.h, the text field is declared and is synthesized in VC.m as follows .
#property (nonatomic, strong) UITextField *urlNameInput;
#synthesize urlNameInput;
I have tried putting the following code into didFinishLaunchingWithOptions: but do not see the desired text when I run the app.
self.viewController.urlNameInput.text = #"example http";
How can I programmatically accomplish my goal of initializing the UITextField?
Put that "urlNameInput.text =" bit into your view controller's "viewDidLoad" method, instead of the "didFinishLaunchingWithOptions:" method (where your view controller is not likely yet instantiated.
Even better than that, just set the initial text in your storyboard or XIB file and then you can programmatically adjust it later on.
Ok I'm having a hard time understanding what you're trying to do but creating an NSString iVar on your app delegate (although there are many other solutions) will allow you to set the textfield text to whatever you want when that VC comes back on the screen.
In your AppDelegate.h
#property (strong, nonatomic) NSString *stringForTextField;
This way you can initialize your textfield text when the view is loaded (viewDidLoad)
self.urlNameInput.text = #"example http";
then whenever that text value needs to be changed (say for example in textFieldShouldReturn in the other view controller. I'm assuming you have another textfield based on your question)
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
appDelegate.stringForTextField = textField.text;
and in viewDidAppear in the VC with the textField set that value.
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
self.urlNameInput.text = appDelegate.stringForTextField;
probably not the best way to go about it, but it will work.
EDIT
Ok in viewDidAppear:
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
if ([appDelegate.stringForTextField isEqualToString:#""]){
self.urlNameInput.text = #"example http";
} else {
self.urlNameInput.text = appDelegate.stringForTextField;
}
Now what this will do is if the user goes to another view controller, and comes back, the text field text will be what the user last entered, unless in another view controller, stringForTextField is updated to some new value. If this still does not work, look at your processPbn method to make sure the if clause is entered and that value is set. Otherwise it will always say "example http"
I'm not understanding why it's unimportant to persist the previous value across application launches, especially when it is beneficial to your users to have it persist only during the application lifecycle. codeInOrange's answer works by adding a property to the AppDelegate. The only thing I would add to his answer is a conditional if() . If you want to do it without any properties, you can still use the NSUserDefaults.
At the top of your ViewController.m file
#define SetHTTPString(string) [[NSUserDefaults standardUserDefaults]setObject:string forKey:#"HTTPString"] //no semicolon
#define GetHTTPString() [[NSUserDefaults standardUserDefaults]objectForKey:#"HTTPString"] //no semicolon
Then, in viewWillAppear...
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSString *httpString = GetHTTPString();
if (httpString) {
self.urlNameInput.text = httpString;
} else {
self.urlNameInput.text = #"Example http";
}
}
Next, in the method where the user enters text and "enters it"
...methodToEnterURL {
SetHTTPString(self.urlNameInput.text);
}
Finally, if you absolutely want to destroy the value in the NSUserDefaults, add this method to your AppDelegate's didEnterBackground method:
[[NSUserDefaults standardUserDefaults]setObject:#"Example http" forKey:#"HTTPString"];
This is a perfect usage for NSUserDefaults. When the user enters something just store it in NSUserDefaults. Check to see if the NSUserDefaults entry is blank on each launch, and if so just display the original string.
Save the text in NSUserDefaults with something like:
[[NSUserDefaults standardUserDefaults]setObject:#"yourNewString" forKey:#"userTextEntered"];
And then just check it on each launch:
if([[NSUserDefaults standardUserDefaults] objectForKey:#"userTextEntered"])
{
//display the user entered string
}
else
{
//display the string that you want to display prior to text being entered
}
However, this solution is only necessary if you want to preserve the text between application shutdowns and launches.
The code below assumes that the Storyboard contains the initial, default Text (of at least 3 characters length).
I really appreciated the help I got from others, especially from codeInOrange . I actually believe this is codeInOrange's solution, but I was never able to quite put his pieces together until I finally stumbled upon this one.
I hope this is really a valid answer, and apologize to all if I did not state my question clearly or if I mistook others' valid answers, especially codeInOrange.
//
// ViewController.m
// StickyPlaceholder
//
//
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize textInput;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
if (!appDelegate.stringForTextField)appDelegate.stringForTextField = self.textInput.text ;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
if (textField == self.textInput) {
[textField resignFirstResponder];
// next line is dummy processing
if (self.textInput.text.length>2)appDelegate.stringForTextField = self.textInput.text;
}
return YES;
}
- (void)viewDidAppear:(BOOL)animated
{
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
self.textInput.text = appDelegate.stringForTextField;
}
#end

UITextView textViewDidChangeSelection called twice

What I have :
TextView
NSArray (string)
AVAudioplayer (not yet implemented)
When I select a word in TextView :
• Check if word exist in Array
• Start Audioplayer with associated sound
Unfortunately when I tap twice to select a word inside TextView, textViewDidChangeSelection is called twice. I don’t know why I see "Youpie" twice.
I just changed inputView to hide keyboard because I only need TextView to be used in selecting mode.
- (void)textViewDidChangeSelection:(UITextView *)tve;
{
NSString *selectedText = [tve textInRange:tve.selectedTextRange];
if(selectedText.length > 0)
{
for (NSString *text in textArray)
{
if ([selectedText isEqualToString:text])
NSLog(#"Youpie");
tve.selectedTextRange = nil;
if (ps1.playing == YES)
{
[self stopEveryPlayer];
[self updateViewForPlayerState:ps1];
}
else if ([ps1 play])
{
[self updateViewForPlayerState:ps1];
fileName.text = [NSString stringWithFormat: #"%# (%d ch.)", [[ps1.url relativePath] lastPathComponent], ps1.numberOfChannels, nil];
}
else
NSLog(#"Could not play %#\n", ps1.url);
break;
}
}
}
}
- (void)awakeFromNib
{
textArray = [[NSArray alloc] initWithObjects:#"dog",#"cat",#"person",#"bird",#"mouse", nil];
textView.inputView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
textView.delegate = self;
// ...
}
I noticed something when I was double tapping on each good word in my text.
textViewDidChangeSelection
If a word is already selected and no action choosen, I have 1 "Youpie".
If not, I have 2 "Youpie".
I found a simple solution. I removed selectedRange after getting value. textViewDidChangeSelection called once.
What I have changed
tve.selectedTextRange = nil;
I use a subclass of UITextView to disable menu.
-(BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
return NO;
return [super canPerformAction:action withSender:sender];
}
I added an implementation for AVAudioPlayer (ps1) too.
My "autoplay" is working if a known word is selecting :)
I don't have an answer for why the method gets called twice or how to prevent this, but an alternative solution might be to display an additional item in the edit menu that pops up in a text view when a word is double clicked. Then, your action for initiating a sound based on the word could be triggered from the action selector defined in that additional menu item. In this design, you'd remove your textViewDidChangeSelection and thus would not get called twice. See http://developer.apple.com/library/ios/#documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/AddingCustomEditMenuItems/AddingCustomEditMenuItems.html for some additional info about modifying the standard menu.

How to keep UITextField text when the view changes

I've got two UITextFields, the input of which I store into strings player1 and player2. These UITextFields are on a ViewController called by a popOver segue. How can I make the UITextFields keep displaying their text once the view has changed?
I tried textFieldOne.text = player1; in the viewDidLoad section of the ViewController to no avail. Any ideas?
If your loaded view's delegate isn't ViewController, your code wouldn't be executed. So be sure that your code is on the delegate of the loaded view. Use also [textFieldOne setText:player1]. It's always better to call the setter method instead of setting the ivar directly. Then be sure that your UITextField is not nil and correctly binded. Use textFieldOne = [[UITextField alloc] init] to initialise it. If your problem continues, try also [textFieldOne setText:self.player1]. Hope it helps..
EDIT :
Got the solution here. You should use NSUserDefaults so your player names are stored and can be used in each view and even after re-opening your app (if you don't want this you can erase the defaults at lunch. Here is your bunch of code you need to change :
hardOne.m :
- (void)viewDidLoad
{
[super viewDidLoad];
[hard1ON setOn:switchState animated:NO];
//read player names to user defaults
[textFieldOne setText:[[NSUserDefaults standardUserDefaults] stringForKey:#"player1"]];
[textFieldTwo setText:[[NSUserDefaults standardUserDefaults] stringForKey:#"player2"]];
}
- (IBAction) returnKey1
{
player1 = [textFieldOne text];
[players addObject:(player1)];
//set player1's name to user defaults
[[NSUserDefaults standardUserDefaults] setValue:[textFieldOne text] forKey:#"player1"];
}
- (IBAction) returnKey2
{
player2 = [textFieldTwo text];
[players addObject:(player2)];
NSLog(#"array: %#",players);
//set player2's name to user defaults
[[NSUserDefaults standardUserDefaults] setValue:[textFieldTwo text] forKey:#"player2"];
}

Resources