Monday 13 February 2012

Table View forms: scroll to a selected text field's row

If, like me, you sometimes completely fail to understand Apple's Class Reference documentation, then we're here for each other!

I had reached the end of "Your Second iOS Application" and decided to try to learn new skills by extending it. What better than to have the keyboard appear as soon as you enter a form like table. Then, to press next and it go to the next field. When you are in the next field, scroll the view up so it is visible and usable.

The first two ideas were easy. But the last, scrolling the text field's row in to view, seemed a simple problem really, but after searching for two hours and coming no where close, I was near to giving up when stumbled up on the solution.

(If you only need to implement the scroll to action, scroll to the 'BUT!' of this post!)

Making the keyboard appear when the View Appears
This one is simple. You want the table view controller of your 'form' to make the keyboard appear when the view appears.

This example assumes you have textfields in your table view, that they are set up in a nib file or storyboard, and connected as outlets to your code with useful names. In the case of the "Second iOS App" tutorial, this would mean you control dragged from the textfields in the storyboard to create the IBOutlet code in the AddSightingViewController.h file, resulting in code like this:


@interface AddSightingViewController : UITableViewController <UITextFieldDelegate>

@property (weak, nonatomic) IBOutlet UITextField *birdNameInput;
@property (weak, nonatomic) IBOutlet UITextField *locationInput;

Two things to note, you must have <UITextFieldDelegate> for everything that follows to work.

To make the keyboard appear I used becomeFirstResponder in the  viewDidAppear: set up.

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [self.birdNameInput becomeFirstResponder];
}

[At the moment my first field is 'birdNameInput', but what if this changes? We will come to that another time.]

Making 'Return' go to the next input field
You textfield will have 'Return' on the keyboard by default. You can change this in Storyboard by:
  1. Selecting the textfield
  2. Going to 'attributes' (alt-cmd-4)
  3. In the 'Return Key' drop down list, select 'Next' or which ever is appropriate.
Now this is why the <UITextFieldDelegate> declaration in @interface was important. It enables us to use the delegate protocol which gives us all sorts of lovely extra functions and customisations. Currently in the tutorial, if you press return in either field they keyboard will be dismissed. It looks like this:

- (BOOL)textFieldShouldReturn:(UITextField *)textField 
{
    if ((textField == self.birdNameInput) || (textField == self.locationInput) {
        [textField resignFirstResponder];
    }    
        
    return YES;
}

If the textfield is either of them and return is press, dismiss the keyboard. To make our example select the next textfield, we want to use our becomeFirstResponder method to get to it. Change the if statement:

- (BOOL)textFieldShouldReturn:(UITextField *)textField 
{
    if (textField == self.birdNameInput) {
        [self.locationInput becomeFirstResponder];
        
        // scroll to row!

    } else if (textField == self.locationInput) {
        [textField resignFirstResponder];
    }
    return YES;
}
And return (or whatever you made the button) will go to the next from the first field, and on the last it will dismiss itself. Great. 

BUT! How do I scroll to the row of tableView? 
Obviously for this small example it isn't an issue in portrait view, but if you are in landscape, the second field is obscured by the keyboard, and you have to scroll the view to see it as you type. A terrible user experience!

As I have hinted in the code above, I want to scroll to the row that I have just made 'First Responder'. You can look at Apple's Developer Document if you want. It's in the 'UITableView Class Reference' part  
under '-scrollToRowAtIndexPath'. Look at it here now if you really want, but it's not pretty. Well, not for noobs. 

It turns out the answer is simple. In the tutorial we've used a Table View with static Cells, so we know we have one section (section 0) and two rows (Row 0 and row 1). We've made the textfield in row 1 the first responder. [In more complex examples you could get this programmatically, but that's not this post's point. ] So we can now add this very simple code:


        // scroll to row!
        [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];

When I tap 'next'/'return'/whatever(!) the textfield comes magically in to view. 

It works by telling (sending a message if you like) to tableView that it needs to enact the scrollToRow action. You KNOW the index path for the row: 1. So NOW it all 'just works'. Now we can understand more fully what the class reference is getting at...! 

The UITableViewScrollPosition accepts a few different options: top, middle or bottom. I used Bottom and it worked great, but experiment with them yourself to see which best fits your needs. Find out more about that here: "TableViewScrollPosition"

In Conclusion
Hopefully seeing some code actually 'in action' will help you understand just a little bit more how it all works and comes together. Eventually I suppose I will understand how to read and put these reference documents in to practice, but at the moment they do seem to just make me go cross eyed. 

Any comments, additions or corrections etc. are very welcome!

No comments:

Post a Comment