68

I have n sections (known amount) and X rows in each section (unknown amount. Each row has a UITextField. When the user taps the "Done" button I want to iterate through each cell and do some conditional tests with the UITextField. If the tests pass data from each cell is written to a database. If not, then a UIAlert is shown. What is the best way to loop through the rows and if there is a more elegant solution to this please do advise.

JOM
  • 8,139
  • 6
  • 78
  • 111
  • 1
    Don't do it this way (with possible exception that you have a small table, and have defined the cells as not being reusable). The recommended way to use tableviews is to maintain state in a separate *model*. *As user makes changes, you update that model with the changes*, by adding handler to each field's `editDidEnd` or similar method. Then when "Done", you are examining your custom model data - the displayed fields are not needed. – ToolmakerSteve Apr 07 '17 at 03:41

10 Answers10

163

If you only want to iterate through the visible cells, then use

NSArray *cells = [tableView visibleCells];

If you want all cells of the table view, then use this:

NSMutableArray *cells = [[NSMutableArray alloc] init];
for (NSInteger j = 0; j < [tableView numberOfSections]; ++j)
{
    for (NSInteger i = 0; i < [tableView numberOfRowsInSection:j]; ++i)
    {
        [cells addObject:[tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:j]]];
    }
}

Now you can iterate through all cells:
(CustomTableViewCell is a class, which contains the property textField of the type UITextField)

for (CustomTableViewCell *cell in cells)
{
    UITextField *textField = [cell textField];
    NSLog(@"%@"; [textField text]);
}
Paul Warkentin
  • 3,899
  • 3
  • 26
  • 35
  • I supposed [[cells objectAtIndex:j] objectAtIndex:i] would get me a cell, which I could just dot access the UITextField, but it does not appear for autocompletion. Any ideas? –  Aug 02 '11 at 15:54
  • Ok, sorry I didn't specify this before. I am subclassing UITableViewCell. Does that affect any of this? –  Aug 02 '11 at 16:38
  • 1
    No, subclassing haven't any affect of this. _cells_ is an array with all cells of _tableview_ of the type _CustomTableViewCell_ or somewhat else. – Paul Warkentin Aug 02 '11 at 16:40
  • 1
    I found that I had to remove the intValue operators on numberOfSections and numberOfRowsInSection. However this was exactly what I was looking for, thanks. – Sheepdogsheep May 04 '12 at 12:06
  • 1
    Indeed, not sure if it was changed, but both numberOfSections and numberOfRowsInSection already return an NSInteger. – woutr_be Jun 01 '12 at 04:19
  • GREAT answer! To add to this, I did not use a custom cell, but was able to simply refer to the desired element in the table view cell by tag number [cell viewWithTag:1]. (in case anyone else was wondering how to use this without a custom cell) – d2burke Nov 08 '12 at 14:20
  • 12
    Only the first part of this answer is valid. The second part makes no sense. If you're looping through and modifying all of the cells for a table view, thinking that there's one cell instance for each row, you don't really understand how table views work. – Dave Batton Mar 12 '13 at 21:16
  • 2
    As a row goes out of view its cell is reused by a row that just came into view. So basically you need to maintain each row's state outside of "its" cell (perhaps in an array). To modify all the rows at once, update such an array and have your TableView's GetCell method modify each row's cell when it next becomes visible. – alan Dec 26 '14 at 17:35
  • This works great as a Category: https://gist.github.com/vikdenic/c97c1acdedd5acdd3aa8 – vikzilla Feb 13 '16 at 23:02
16

Here is a nice swift implementation that works for me.

 func animateCells() {
        for cell in tableView.visibleCells() as! [UITableViewCell] {
            //do someting with the cell here.

        }
    }
7

Accepted answer in swift for people who do not know ObjC (like me).

for section in 0 ..< sectionCount {
    let rowCount = tableView.numberOfRowsInSection(section)
    var list = [TableViewCell]()

    for row in 0 ..< rowCount {
        let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: row, inSection: section)) as! YourCell
        list.append(cell)
    }
}
return true
  • 7,839
  • 2
  • 19
  • 35
2ank3th
  • 2,948
  • 2
  • 21
  • 35
5

for xcode 9 use this - (similar to @2ank3th but the code is changed for swift 4):

let totalSection = tableView.numberOfSections
for section in 0..<totalSection
{
    print("section \(section)")
    let totalRows = tableView.numberOfRows(inSection: section)

    for row in 0..<totalRows
    {
        print("row \(row)")
        let cell = tableView.cellForRow(at: IndexPath(row: row, section: section))
        if let label = cell?.viewWithTag(2) as? UILabel
        {
            label.text = "Section = \(section), Row = \(row)"
        }
    }
}
GhostCat
  • 137,827
  • 25
  • 176
  • 248
luhuiya
  • 2,129
  • 21
  • 20
2
for (UIView *view in TableView.subviews) {
    for (tableviewCell *cell in view.subviews) {
       //do
    }
}
Darshit Shah
  • 2,366
  • 26
  • 33
2

Since iOS may recycle tableView cells which are off-screen, you have to handle tableView one cell at a time:

NSIndexPath *indexPath;
CustomTableViewCell *cell;

NSInteger sectionCount = [tableView numberOfSections];
for (NSInteger section = 0; section < sectionCount; section++) {
    NSInteger rowCount = [tableView numberOfRowsInSection:section];
    for (NSInteger row = 0; row < rowCount; row++) {
        indexPath = [NSIndexPath indexPathForRow:row inSection:section];
        cell = [tableView cellForRowAtIndexPath:indexPath];
        NSLog(@"Section %@ row %@: %@", @(section), @(row), cell.textField.text);
    }
}

You can collect an NSArray of all cells beforehands ONLY, when the whole list is visible. In such case, use [tableView visibleCells] to be safe.

JOM
  • 8,139
  • 6
  • 78
  • 111
1

quick and dirty:

for (UIView *view in self.tableView.subviews){
    for (id subview in view.subviews){
        if ([subview isKindOfClass:[UITableViewCell class]]){
            UITableViewCell *cell = subview;
            // do something with your cell
        }
    }
}
budiDino
  • 13,044
  • 8
  • 95
  • 91
  • 1
    actually didn't test this... so there might be problems with cells that are not visible :/ Use with caution :) – budiDino Jan 22 '15 at 13:04
0

Here's a completely different way of thinking about looping through UITableView rows...here's an example of changing the text that might populate your UITextView by looping through your array, essentially meaning your tableView cell data.

All cells are populated with data from some kind of model. A very common model would be using an NSObject and NSMutableArray of those objects. If you were in didSelectRowAtIndexPath, you would then want to do something like this to affect the row you're selecting after modifying the array above:

for(YourObject *cellRow in yourArray)
{
    if(![cellRow.someString isEqualToString:@""])
    {
         cellRow.someString = @"";
    }
    //...tons of options for conditions related to your data
}

YourObject *obj = [yourArray objectAtIndex:indexPath.row];
obj.someString = @"selected";
[yourArray insertObject:views atIndex:indexPath.row];
[yourArray removeObjectAtIndex:indexPath.row];
[yourTable reloadData];

This code would remove all the UITextField's text in every row except the one you selected, leaving the text "selected" in the tapped cell's UITextField as long as you're using obj.someString to populate the field's text in cellForRowAtIndexPath or willDisplayRowAtIndexPath using YourObject and yourArray.

This type of "looping" doesn't require any conditions of visible cells vs non visible cells. If you have multiple sections populated by an array of dictionaries, you could use the same logic by using a condition on a key value. Maybe you want to toggle a cells imageView, you could change the string representing the image name. Tons of options to loop through the data in your tableView without using any delegated UITableView properties.

whyoz
  • 5,168
  • 47
  • 53
0

swift 5:

guard let cells = self.creditCardTableView.visibleCells as? [CreditCardLoanCell] else {
            return
        }
        
  cells.forEach { cell in
     cell.delegate = self
   }
Mor4eza
  • 265
  • 3
  • 12
-1

I would like to add my two cents to the matter even though this post is old. I created an array of type UITableViewCell and appended each new cell to it before returning it in cellForRowAt. See code below:

var cellArray = [UITableViewCell]()

//UITableView code

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! Cell
        //set up cell information

        cellArray.append(cell)
        return cell
    }

Then if you need any information from each cell (i.e., UITextFields) in your Done button, you can iterate through the array like so in the desired context:

for cell in cellArray {
            let myCell = cell as! Cell
            //do stuff
        }

Hope this helps anyone in the future

Kyle F.
  • 43
  • 7
  • This makes no sense at all because you don't know the index path each cell is associated with. And even if you added the information it's useless because cells are reused. The proper way is to iterate the data **model**. – vadian Feb 26 '23 at 07:17
  • @vadian if you need to know the index path of each cell, you could do `tableView.indexPath(for: cell)` This will give you the associated index path of each cell – Kyle F. Feb 27 '23 at 05:38
  • It's not useful anyway because you get only the currently **visible** cells. Therefore *some conditional tests with the UITextField* as requested in the question is impossible. To modify information do it in the **model**. The **view** is only for displaying items. – vadian Feb 27 '23 at 07:45
  • @vadian, When I have used my method so far, I have been able to access all cells of the table. This includes cells that were not in the current view. It seems to me he wants to test every cell and if even one of them fail his conditions, the alert should be sent out. I suggest, if you have the time, to test out my solution and see if there is some misunderstanding. – Kyle F. Mar 01 '23 at 20:08