0

Earlier today I asked a question about Textfields. Now I am stuck at how to loop through each UITextFields.

How Do you loop Through each UITextfield of a Section ?

Here is my code so far:

- (IBAction)ForwardReport:(id)sender {
    NSLog(@"PRESSED");
    for (UITableViewCell *cell in [self.tableView.indexPathsForVisibleRows]) {
        if ([cell isKindOfClass:[UITextField class]]) {
            UITextField *textField = (UITextField *)cell;
            NSLog(@"UITEXTFIELD TEXT = %@", textField.text);
        }
    }
}

I tried following this answer but Unfortunately my code wouldn't compile.

I am not sure how to access the section 1 (second section) when outside of UItableView Delegate.

I would like to iterate through each UITextfield, and retrieve its text value.

Please advice:

Here's a screen shot

enter image description here

I guess my other option would be to create IBoutlets for each field, and have a bunch of if/else statement, but it sound kind of messy....

Community
  • 1
  • 1

4 Answers4

1

You should search UITextField not in cells but in cell's subviews.

Try following

- (IBAction)ForwardReport:(id)sender {
    NSLog(@"PRESSED");
    for (UITableViewCell *cell in self.tableView.visibleCells) {
        for (UIView *subview in [cell.contentView subviews])
            if ([subview isKindOfClass:[UITextField class]]) {
                UITextField *textField = (UITextField *) subview;
                NSLog(@"UITEXTFIELD TEXT = %@", textField.text);
            }
        }
    }
}
Avt
  • 16,927
  • 4
  • 52
  • 72
  • The first for loop does not compile, I believe because of `[self.tableView.indexPathsForVisibleRows]` its wrong... somehow I need to only loop through `indexPath.section == 1` but unsure how to do this inside of IBAction –  Feb 24 '14 at 20:26
  • You are right. `visibleCells` should be used here. Please check updated answer. – Avt Feb 24 '14 at 20:32
  • Still does not compile, do you know how I can adapt this http://stackoverflow.com/a/5644695/3205189 to only loop through section 1 ? –  Feb 24 '14 at 20:37
  • Ok. I have just opened Xcode and checked code. `self.tableView.visibleCells` should be used instead of `[self.tableView.visibleCells]`. I have updated answer – Avt Feb 24 '14 at 20:40
  • It is difficult to write whiteout IDE even if you know what to do. A big chance to make some stupid typo. – Avt Feb 24 '14 at 20:42
  • Thanks to @Duncan C . I have forgot about contentView. – Avt Feb 24 '14 at 20:45
  • Great it works now, but how is this code only looping through section 1, I dont see that check , as I have Textfield in other sections? –  Feb 24 '14 at 20:49
  • It loops through visible cells. It is impossible to go through invisible cells due to UITableView restrictions. Are you sure text fields from other sections are visible? Also it detects only top level text fields in cells. If you have catch an idea it will not be difficult to update solution. – Avt Feb 24 '14 at 20:54
  • Please pay attention on @Duncan C answer. May be it is possible to change application design? – Avt Feb 24 '14 at 20:56
1

My suggestion is not doing that. You cannot rely on the content of a UITextField that is embedded in a cell of a tableView, due to reusability.

Your view controller should be directly tied to the model, thus, changing the text within a textField should update your model immediately, so, when you scroll and a cell gets reused, the proper value is assigned. And when you hit "forward" you just query the content of the model (which, by all means, could probably be a simple NSMutableArray).

Let's add an NSMutableArray with as many empty strings as there will be textFields (for example 5):

- (instancetype)initWithWhatever...
{
    self = [super initWithWhatever];

    if (self)
    {
        _textFieldValues = [NSMutableArray arrayWithCapacity:5];

        for (NSInteger i=0; i < 5; i++)
        {
            _textFieldValues[i] = @"";
        }
    }       

    return self;
}

Make sure every textfield points back to your viewController, on cellForRowAtIndexPath:.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    ...        

    myCell.textField.text     = _textFieldValues[indexPath.row];
    myCell.textField.tag      = 100 + indexPath.row;
    myCell.textField.delegate = self;

    return myCell
}

The tag is there to identify each cell, other option is to make your own UITextField subclass or use a custom setValueForKey:.

Whatever you pick, when you get the delegate method (UITextFieldDelegate), check against your preferred method, tag in my case:

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    NSString *replaced = [textField.text stringByReplacingCharactersInRange:range withString:string];

    _textFieldValues[(textField.tag - 100)] = replaced;

    return YES;
}

That will keep the model up to date always, and you don't need to iterate text fields, just over the array:

// Method that gets called when hitting forward
- (IBOutlet)didPressForwardButton:(UIButton*)sender
{
    for (NSString *text in _textFieldValues)
    {
        NSLog(@"UITEXTFIELD TEXT = %@", text);
    }
}

Lesson: When working with a UITableView you should be always mapping what's on the view to what's on the model, and update immediately, you cannot rely on the view to hold the data. In fact, this should extend to anything you do in iOS, views are for shows. If you cannot modify the real model immediately, you would have to encapsulate it in a "transaction" intermediary object and then update the real model.

Can
  • 8,502
  • 48
  • 57
  • So basically the finalString contains all 5 UITexfield values? –  Feb 24 '14 at 21:16
  • No, finalString (now called replaced, to avoid confusion) is the string replaced from the `textField: shouldChangeCharactersInRange`, all the textfield's information is located within the array `_textFieldsValues`. – Can Feb 24 '14 at 21:19
  • interesting, can you please show me how to iterate through that array. sorry I have never seen it with a tag/index –  Feb 24 '14 at 21:34
  • The array is just a regular array, I just used the tags to identify each individual textField, if you notice, I just added 100 and substracted 100 to avoid clashing with other tags. – Can Feb 24 '14 at 21:54
  • @AlPacino There, I made it more specific. – Can Feb 24 '14 at 23:00
  • Thanks a lot, it took me a while to implement this, But I was able to use this approach, which is really cleaner thanks again –  Feb 25 '14 at 17:54
0

In AVT's answer, get rid of the brackets around self.tableView.visibleCells in the for loop. Then it would read

for (UITableViewCell *cell in self.tableView.visibleCells)

Then the next for loop should really be using the cell's content view's subviews:

for (UIView *subview in [cell.contentView subviews])

However, these are minor issues.

There is a more important design issue. You should not be looping through the currently visible cells. Table view cells are transient. If the user scrolls a table view cell off the screen, it's contents are discarded - and lost.

You should collect information from the view objects in a cell as the user enters that information: Switches and segmented controls should be hooked to outlets that immediately save the users changes into the data model. Likewise with text fields, you need to set up a delegate, and collect the user's input as soon as he's done entering it and save it to the data model.

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • You're right, that compiles now, Sorry Im new so I didnt understand fully the last paragraph. –  Feb 24 '14 at 20:47
  • Quick Question: how is this code only looping through section 1, I dont see that check , as I have Textfield in other sections? I want to make sure it does not look in other section. –  Feb 24 '14 at 20:50
  • What @Duncan C is saying is (correct me if I'm wrong): You should have a data structure in your class (maybe a `NSMutableArray` property) that holds the data that you want to present in your views. In your `-forwardReport` method you could then easily access the required string by calling `[self.yourArray objectAtIndex:x]` instead of iterating over all the subviews contained in your cells which is rather complicated and can cause other problems. Whenever the user changes the text in a text field you should update your data structure by replacing the corresponding value in your array. (-> MVC) – Mischa Feb 24 '14 at 21:34
  • 2
    Mischa, thats close to what I'm saying. I'm saying that you can't store state data in table view cells and expect it to persist. It won't work reliably. Table view cells are view objects. When the user enters data into the views in a table view cell, they should be copied over to the model as soon as the user is finished with each edit. Then, when you need the current data, it's always in the model. Yes, an array is typically a good structure for the model for a table view. Or perhaps an array of arrays, for a sectioned table view (outer array for the sections and inner array for rows) – Duncan C Feb 25 '14 at 02:20
  • @Duncan C, that's even more precise. – Mischa Feb 25 '14 at 09:25
0

There are two errors in your code.

First problem:

UITableViewCell *cell in [self.tableView.indexPathsForVisibleRows]

won't work because self.tableView.indexPathsForVisibleRows returns an array of indexPaths and not a UITableViewCell. Try this:

- (IBAction)ForwardReport:(id)sender {
    NSLog(@"PRESSED");
    for (NSIndexPath *indexPath in [self.tableView indexPathsForVisibleRows]) {
        UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];

        ...

    }
}

Second problem:

UITextField *textField = (UITextField *)cell;

UITextField is not a subclass of UITableViewCell and hence you cannot cast a table view cell as a textField. Instead you have to iterate over all subviews contained in your cell. So the whole method would be:

- (IBAction)ForwardReport:(id)sender {
    NSLog(@"PRESSED");
    for (NSIndexPath *indexPath in [self.tableView indexPathsForVisibleRows]) {
        UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
        for (UIView *subview in cell.subviews)
            if ([subview isKindOfClass:[UITextField class]]) {
                UITextField *textField = (UITextField *)subview;
                NSLog(@"UITEXTFIELD TEXT = %@", textField.text);
            }
        }
    }
}

(Note: I have not yet tested this code.)

Mischa
  • 15,816
  • 8
  • 59
  • 117
  • It would be appreciated if the person who downvoted would give a hint what is wrong with this answer so I can improve it. – Mischa Feb 24 '14 at 20:57
  • 1
    I dont know who did , but I upvote it, thank you for your input –  Feb 24 '14 at 21:12
  • Good catch on the misuse of self.tableView.indexPathsForVisibleRows. I missed that. (Voted) – Duncan C Feb 25 '14 at 02:26