0

This one is driving me a little batty and my eyes are glossing over.

I have an app with a navigation controller.

View A has some input fields and a "Continue" button which loads View B

When I tap the "Back" button on the upper left of the navigation controller its resulting in events being fired in an order I'm not expecting/understanding

My tracing reveals ...

View B: viewWillDisappear
View A: viewWillAppear
View B: textFieldShouldEndEditing

EDIT -- more detail/code explaining my previously vague question

Conceptually the following approach has been working fine, and passed several rounds of QA testing.

In summary, I'm using the textFieldShouldEndEditing to validate textfields. If they aren't valid, I retain focus on the field and show them a message of whats wrong. All is good and validations work as the user attempts to go from field to field.

The condition that is problematic with the code below is if someone enters a partial value and then clicks BACK. All of the UITextFields in the entire app Freeze up (don't allow input) and in some cases the app crashes.

The approach I'm attempting which led me to post the initial Question was to create a private: BOOL isDisappearing;

Which I could check in viewWillDisappear (which in most cases fires PRIOR to textFieldShouldEndEditing), and if its YES I would short circuit the problematic code that is firing and freezing the UITextFields/app.

This is working in several views fine, but in 1 case where 'VIEW A:viewWillAppear' event fires before the textFieldShouldEndEditing below (VIEW B) - the isDisappearing gets set to 'NO' somehow and the problematic code is firing in textFieldShouldEndEditing

I hope this helps and you can follow. I find it hard to explain without code – but I've tried to trim it down to just what is relevant. I hope this is appropriate here – I'm pretty new to the community.

Code for VIEW B:

-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)rangereplacementString:(NSString *)string
{   
    // Enforce max lengths
    // The return key from the keyboard counts as a character, so we have to exempt it
if (textField.tag == ROUTING_NUMBER_TAG)
{
    NSUInteger newLength = [textField.text length] + [string length] - range.length; 
    return (newLength > 9 && ![string isEqualToString:@"\n"]) ? NO : YES;           
}
else if (textField.tag == ACCOUNT_NUMBER_TAG)
{
    NSUInteger newLength = [textField.text length] + [string length] - range.length; 
    return (newLength > 17 && ![string isEqualToString:@"\n"]) ? NO : YES;           
}
return YES;
}

// textField validation
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
if (isDisappearing)

    return YES;

//run fields through validators and display validation messages.
//IF THEY DON’T PASS VALIDATION IM "HOLDING THEM HOSTAGE" BY KEEPING THE FOCUS ON THE UITEXTFIELD (returning NO)

if (textField.tag == ROUTING_NUMBER_TAG )
{
    if ([Utility isValidRoutingNumber:textField.text]== NO)
    {
        [[iToast makeText:NSLocalizedString(@"Enter valid routing number", @"")] show];
        return NO;
    }
    else
    {   //save it
         extension.payment.routingNumber = routingNumber.text;
    }
}
else if (textField.tag == ACCOUNT_NUMBER_TAG)
{
    if ([Utility isValidAccountNumber:textField.text] == NO)
    {
        [[iToast makeText:NSLocalizedString(@"Enter valid account number", @"")] show];
        return NO;
    }
    else
    {   //save it
        extension.payment.accountNumber = accountNumber.text;
    }
}
return YES;
}


- (void)textFieldDidEndEditing:(UITextField *)textField
{

//I do nothing here except nulling out the 'activefield' var I use to autoscroll the uiscrollview as the user taps around from field to field

}



- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];

//reset the bool so that when they come back we're back to the 'normal' state and validation will again be checked in textFieldShouldEndEditing

isDisappearing = NO;
}

- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
}


-(void)viewWillDisappear:(BOOL)animated
{
TRC_ENTRY
//set the bool to bypass validations in textFieldShouldEndEditing

isDisappearing = YES;
[super viewWillDisappear:animated];
}
Jay Bhalani
  • 4,142
  • 8
  • 37
  • 50
jaySF
  • 94
  • 1
  • 10

3 Answers3

2

Other than cases where the order of messages is clearly defined or strongly implied by names, you should avoid depending on any particular order. For example, you can reasonably expect -viewWillAppear to be called before -viewDidAppear for any given view, but don't expect one view's -viewWillAppear to be called in any particular order with respect to any message sent to a different view.

If you need help figuring out how to implement a particular feature without depending on order, please ask. But again, unless the order of invocation is documented or blatantly obvious from the method names, don't expect a particular order.

Update: I don't see exactly what's going wrong in the code you added, but perhaps a few suggestions will help:

  1. Is your isDisappearing variable an instance variable of your view controller, a global variable, or what? If it's an instance variable, figure out how it's being changed. If it's a global variable, well... don't do that.

  2. Be sure that you're heeding the warning in the docs to the effect that -textViewShouldEndEditing: is only advisory, and that the view may stop editing no matter what you return.

  3. Try temporarily removing the iToast stuff. If the crash still happens, at least you've eliminated that as a source of problems. If it stops happening, you'll have narrowed your search.

  4. Identify the cause of the crash. (This should really be first on the list.) Crashes don't just happen mysteriously -- there's a reason that it happens. Find that reason, and you're 85% done. Start by examining the stack trace when the crash occurs. If that doesn't provide enough clues, place a breakpoint somewhere before the line that causes the crash and start stepping until you crash. If all else fails, start logging messages to trace execution and monitor your assumptions.

  5. What exactly is your view controller A doing in its -viewWillAppear method? Could that be part of the problem? Could you move that code to, say, -viewDidAppear instead?

Caleb
  • 124,013
  • 19
  • 183
  • 272
  • Just accidentally committed my comment prematurely. Let me try again ... My issue seems to be that the UITextFieldDelate related events are firing in unexpected ways. I'm sure there is a logical reason but in some cases textFieldShouldEndEditing *only* fires when clicking the NavController "Back" button while a UITextField has focus. My code in this scenario works. I've just discovered that the textFieldDidEndEditing is also firing in the case where I'm having the issue. I'm now trying to figure out why the DID is firing in this particular case but not others. – jaySF Mar 06 '12 at 01:15
  • @jayboston `-textFieldShouldEndEditing:` should be called whenever the text field is about to give up its 'first responder' status. So, it should be called for the currently focussed text field if you tap on a different text field, or do anything else that would make the text field stop being the first responder. That obviously includes hitting the 'back' button, since that will replace a large part of the responder chain including the first responder. Do you have a case where `-textFieldDidEndEditing:` is *not* being called when a text field has definitely stopped being first responder? – Caleb Mar 06 '12 at 01:43
  • @jayboston Also, remember that if you have more than one text field with the same delegate, that object will receive messages for all the text fields. So you might get a `-textFieldShouldBeginEditing:` for one field and a `-textFieldDidEndEditing:` for a different field, or other combinations, and not necessarily in that order. Pay close attention to the `sender` parameter. – Caleb Mar 06 '12 at 01:46
  • I think you're on to something with what is wrong, probably my misunderstanding of the UITextField events, and now I'm questioning my approach altogether. I'm editing my Question to include the code that I'm using with a more thorough description of what I'm trying to accomplish in hopes someone can tell me if I'm off the reservation. I _am_ using Tags to identify the senders and such - I thought I had a handle on it. I'm hoping you'll have the time to take a look at the code that's giving me grief and straighten me out – jaySF Mar 06 '12 at 02:39
  • Thank you for the great tips Caleb. I've gotten caught up in something but will get back to this over the next couple of days and try out your suggestions. isDisappearing is an instance variable, but mysteriously seems to lose its scope/value. I thought about making it a property to see if that makes a difference but it wouldn't seem like it should. Be back soon and thanks for all the help. – jaySF Mar 06 '12 at 23:18
  • Comment #2 was particularly helpful, although these are all good tips. I decided to no longer rely on textViewShouldEndEditing to help manage the state of a valid/invalid screen. I'm now returning YES for all conditions regardless of failed validations or not, and no longer holding the user hostage in the field by returning NO. This means the behavior is a little different than what I envisioned - the user can go from field to field even when the input is invalid. – jaySF Mar 09 '12 at 02:01
  • ...However I continue to throw up the iToast message and prevent them from loading the next view until all fields are valid by running the validations again on the continue button's touch event. Now going 'back' using the nav controller will ignore the currently selected field with invalid data, and that field is cleared out upon returning to the view. A solution I can live with and was led to by the cautions provided by Caleb. Thanks! Btw great name Caleb - that's what I named by son. – jaySF Mar 09 '12 at 02:02
0

Try doing [self.youTextField resignFirstResponder] on view B viewWillDisappear.

fbernardo
  • 10,016
  • 3
  • 33
  • 46
0

This might not be directly relevant to your question, but if you feel you don't fully understand lifetime of views, I suggest take a look here: What is the process of a UIViewController birth (which method follows which)?

It's a great explanation.

Community
  • 1
  • 1
Pooria Azimi
  • 8,585
  • 5
  • 30
  • 41
  • Thank you. I have the view lifecycle pretty well understood. The issue I'm having seem to be when the textField events come into play. I'm using UITextFieldDelegate and handling a lot of field validation in those events – jaySF Mar 06 '12 at 01:10