Monday 13 February 2012

Enable or Disable NavBar Done Button

You've got your table based form looking just great now. However, the user experience isn’t ideal. For one thing, the Done button is active as soon as the add scene appears; it would be better if it became active after the user taps a key on the keyboard.

In my last post I told you about the <UITextFieldDelegate> and the textFieldDidEndEditing delegate function. We're going to look at the textField delegate again, but before we go on, let's tidy that up what we did last time a little bit. [Remember all this is based on Your second iOS App tutorial.]

Next... Next... Next....
Let's make the return button 'next' for both fields, see the last post if you can't remember how, so we pass the user backwards and forwards between the two fields. (If you have more than two... you can work it out!) Change the delegate method to this:


- (BOOL)textFieldShouldReturn:(UITextField *)textField 
{
    if (textField == self.birdNameInput) {

        [self.locationInput becomeFirstResponder];
        
        // scroll to row!
        [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
   
    } else if (textField == self.locationInput) {
        
        [self.birdNameInput becomeFirstResponder];
        
        // scroll to row!
        [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
        
    }
    
    return YES;
}


The 'else if' statement part is just the reverse of the first part, taking us back to the first row etc. Now, you might find a more efficient way to call all this if you have 3 or more fields. If you do, let us all know!

Disable Done button when view appears
There are a few ways to do this, but in this post I'll just give you the simplest. I'm assuming you have put a button item in the right side of the navigation item bar.

Change viewDidLoad in the view controller .m file:


- (void)viewDidLoad
{
    [super viewDidLoad];
    self.navigationItem.rightBarButtonItem.enabled = NO; // Easy way!!!
}


It works by saying that item is not enabled. This method will always act on the farthest top right nav bar button item. [Again, it was here in the UIBarItem class reference all along under enabled, although I'd never have got it simply from looking at that...]

Done button Enabled - Method 1:
- After the text field resigns its first responder status...


- (void)textFieldDidEndEditing:(UITextField *)textField 
{  
    // after the text field resigns its first responder status, enable done button... 
    self.navigationItem.rightBarButtonItem.enabled = YES;
}



- After the text field resigns its first responder status and it has text entered...


- (void)textFieldDidEndEditing:(UITextField *)textField 
{  
    if ([textField.text length] != 0) { 

        self.navigationItem.rightBarButtonItem.enabled = YES;

    }




This 'if' statement says that, when the text field resigns its first responder status (focus shifts to next field) if the text field has at least one character in it, enable the button.

Done button Enabled - Method 2: 
- Enable the done button only if both fields have at least one character in them...


Ok, I need to confess: this is an edit. The first attempt I wrote about here worked but only sort of. Then I discovered this way of doing things. What a revelation!
  • In the form view controller .h file, create a new action in the @interface section: 
- (IBAction)textField:(id)sender;
  • Go to the Storyboard scene for your 'form'.
  • Select a text field, then ctr-click. Up should pop a display of connections for the Text Field. 
  • Drag a connection from 'Editing Changed' to the View Controller icon as shown:

  •  Then select the IBAction textField which you just created, as shown:

  • Do this for each text field. This hooks up the action code with the interface builder action. When any key presses happen in the text field, each key press will call this action.
  • In the view controller .m file, add a brand new function with this code:
- (void)updateDoneButtonStatus:(id)sender {
    int a = [self.birdNameInput.text length];
    int b = [self.locationInput.text length];
        
    if ((a != 0) && (b != 0)) {

        self.navigationItem.rightBarButtonItem.enabled = YES;
        
    } else if ((a == 0) || (b == 0))  {

        self.navigationItem.rightBarButtonItem.enabled = NO
        
    }
}

[self.birdNameInput.text length] gets the length of string within the text field every time a key is pressed. I assigned these to int a and int b to keep the code tidy and easy to read and I used short names because in the scope they are against easy to read and see what they do; and are only accessible within this function action anyway. 

The first part of the 'if' statement will only enable the 'done' button if both fields have at least one character in them. The second part will disable the button if any fields character count goes back to zero. Using this function makes the code reusable, as you will see in the next step.
  • Change the textFieldDidEndEditing function, and add the code for the new action as here:
- (void)textFieldDidEndEditing:(UITextField *)textField 
{  
    [self.birdNameInput addTarget:self action:@selector(updateDoneButtonStatus:)    
                             forControlEvents:UIControlEventEditingChanged];
    [self.locationInput addTarget:self action:@selector(updateDoneButtonStatus:) 
                             forControlEvents:UIControlEventEditingChanged];    


- (IBAction)textField:(id)sender {
    [self.birdNameInput addTarget:self action:@selector(updateDoneButtonStatus:) 
                             forControlEvents:UIControlEventEditingChanged];
    [self.locationInput addTarget:self action:@selector(updateDoneButtonStatus:) 
                             forControlEvents:UIControlEventEditingChanged];
}

Yes, the code in both these functions/actions is exactly the same. This is the body of the IBAction we hooked up at the start of this method.

action:@selector(updateDoneButtonStatus:) selects the function we made just that turns the done button on and off; forControlEvents:UIControlEventEditingChanged hooks us back to the 'Sent Events' connection we made in the Storyboard/interface builder.
Currently the only way I know is to specify one for each text field we want this to work on, but I'll be on the look out for more efficient coding methods!


And finally...
You could use this to refer to the Done button specifically:

    UIBarButtonItem *donebutton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(ondoneButtonPressed)];
    self.navigationItem.rightBarButtonItem = donebutton;
    //make done button inactive
    donebutton.enabled=NO;

But that's enough for now.

No comments:

Post a Comment