Search code examples
objective-ccocoa-touchuitextfielduitextfielddelegate

Is there a way to use UITextField's on a dynamic UITableView, AND 'tab' between them?


This may be a tad lengthy, but bear with me. It's mostly simple code and log output. Normally, if I wanted to have a UITextField as a part of a UITableViewCell, I would likely use either a) static rows or b) I would create the cell in the storyboard, outlet the cell and outlet the field to my ViewController, and then drag the cell outside of the "Table View", but keeping it in the scene.

However, I need to create a View where I accept input from 28 various things. I don't want to outlet up 28 different UITextField's.

I want to do this dynamically, to make it easier. So I've created a custom UITableViewCell with a Label and UITextField.

My ViewController has two arrays.

@property (nonatomic, strong) NSArray *items;
@property (nonatomic, strong) NSArray *itemValues;

My cellForRowAtIndexPath looks something like this...

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSString *cellIdentifier = @"ItemCell";
    MyItemTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];

    if (!cell) {
        cell = [[MyItemTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
        cell.categoryValue.tag = indexPath.row;
        cell.categoryValue.delegate = self;
    } 

    cell.item.text = [self.items objectAtIndex:indexPath.row];
    cell.itemValue.text = [self.itemValues objectAtIndex:indexPath.row];

    return cell;
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
        NSInteger tag = [textField tag];
        NSLog(@"TFSR tag: %zd/%zd", tag, self.categories.count-1);
        if (tag < (self.categories.count - 1)) {
            NSIndexPath *nextIndex = [NSIndexPath indexPathForRow:tag+1 inSection:0];
            NSLog(@"TFSR nextRow: %zd/%zd\n", nextIndex.row);
            FFFCategoryTableViewCell *cell = (MyItemTableViewCell *)[self.tableView cellForRowAtIndexPath:nextIndex];
            [self.tableView scrollToRowAtIndexPath:nextIndex atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
            [cell.categoryValue becomeFirstResponder];
        }
        else {
            NSLog(@"DONE!");
        }

    return YES;
}

This is proving to be problematic. The goal is, the user should be able to select the UITextField on the first row, enter a value, and when they hit the 'Next' key on the keyboard, they will be sent to the UITextField on the second row. Then 3rd, 4th,... 27th, 28th.

However, let's say I first highlight the UITextField on the Cell with IndexPath.row = 11. If I tap 'Next', this is what my output looks like...

======== OUTPUT ========

TFSR tag: 11/27
TFSR nextRow: 12/27

TFSR tag: 12/27
TFSR nextRow: 13/27

TFSR tag: 13/27
TFSR nextRow: 14/27

TFSR tag: 0/27
TFSR nextRow: 1/27

Now I fully understand why this is happening. With UITableView trying to save memory in loading various cells, and with the use of dequeueReusableCellWithIdentifier... I only have 14 cells (0-13). Then it loops back to the beginning.

My problem is... I don't know a solution to this problem. I want the user to be able to his Next until the 28th UITextField row.

Any ideas/solutions on how I could achieve this?


Solution

  • The problem is your use of tags. You only set the tag the first time the cell is created. It needs to be set on every call to cellForRowAtIndexPath:. You want:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        NSString *cellIdentifier = @"ItemCell";
        MyItemTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
    
        if (!cell) {
            cell = [[MyItemTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
            cell.categoryValue.delegate = self;
        } 
    
        cell.categoryValue.tag = indexPath.row;
        cell.item.text = [self.items objectAtIndex:indexPath.row];
        cell.itemValue.text = [self.itemValues objectAtIndex:indexPath.row];
    
        return cell;
    }
    

    Please note that setting the tag to the index path only works if the rows are fixed. If your table rows are more dynamic (some can be added, removed, or reordered) then using the index path as the tag will fail and another approach must be used.