简体   繁体   中英

Why is tableView(_: cellForRowAt indexPath:) called so many times leading to a weird UIButton bug?

I'm just making a simple project for fun but I am having a very strange bug in my program.

This function returns 29 which is correct however it is called three times when my table view is instantiated.

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
    print("ingredient count: \(Sushi.ingredients.count)")
    return Sushi.ingredients.count
}

And I think that (_: numberOfRowsInSection:) being called 3 times is what leads this code to execute almost three times the number of elements in the ingredients array.

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
    guard let cell = tableView.dequeueReusableCell(withIdentifier: "IngredientCell", for: indexPath) as? IngredientCell
    else
    {
        return UITableViewCell(style: .default, reuseIdentifier: nil)
    }

    cell.ingredientLabel.text = Sushi.ingredients[indexPath.row]
    print("ingredient label text: \(cell.ingredientLabel.text)\nindex path: \(indexPath)\ncell index: \(cell.index)\nbutton tag: \(cell.selectButton.tag)\nindex path row: \(indexPath.row)\ncell object: \(cell)\n")
    return cell
}

The table view is populated appropriately as far as it lists all the ingredients in the array correctly. However, each row of the tableView is a prototype cell that has a UILabel, a UIButton, and a UIImage. When the button is pressed the text and color change for that button but also for every thirteenth button in the tableview cells.

This class is for my individual cells

class IngredientCell: UITableViewCell
{
public var userSelected = false
public var index: Int = 0

@IBOutlet weak var picture: UIImageView!

@IBOutlet weak var selectButton: UIButton!

@IBOutlet weak var ingredientLabel: UILabel!

//This method is called when a button is pressed
@IBAction func selectIngredient(_ sender: UIButton)
{
    if userSelected == false
    {
        userSelected =  true
        selectButton.setTitleColor(.red, for: .normal)
        selectButton.setTitle("Remove", for: .normal)
    }
    else
    {
        userSelected = false
        selectButton.setTitle("Select", for: .normal)
        selectButton.setTitleColor(.blue, for: .normal)
    }

}
}

I decided to debug using print statements to see if I could figure out what exactly is going on and I found that numberOfRowsInSection is called 3 times and cellForRowAtIndexPath is called a varying amount of times. I keeps iterating over the table view giving the cell objects the same memory addresses which I suppose is what causes multiple buttons to change when only one is pressed. My console output proved that different buttons in the storyboard had the same addresses in memory.

Sorry, I can only write in Objective-C. But anyway, I am just going to show you the idea.

One of the ways to update the UI contents of just a single item of a table is to reload that specific cell.

For example: Your cell contains just a button. When you press a specific button you want to change the title of that button only. Then all you have to do is to create an dictionary which references all the titles of all buttons in your table, and then set the title to that button upon reload of that specific cell.

You can do it this way:

@interface ViewController ()<UITableViewDelegate, UITableViewDataSource>

@property NSMutableDictionary *buttonTitlesDict;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    buttonTitles = [[NSMutableDictionary alloc] init];
}

(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *identifier = @"SimpleTableItem";

    UITableViewCell *tableCell = [tableView dequeueReusableCellWithIdentifier:identifier];

    if(tableCell == nil)
    {
        tableCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
    }

    tableCell.button.title = [_buttonTitles objectForKey:@(indexPath.row)];
    tableCell.button.tag = indexPath.row;
}

 //reloads only a specific cell
 - (IBAction)updateButton:(id)sender
 {
     NSInteger row = [sender tag];

     [_buttonTitlesDict setObject:@"changedTitle" forKey:@(row)];

     [_tableView reloadRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:row inSection:0]] withRowAnimation:UITableViewRowAnimationNone]; 
}

As you can see, I just created a dictionary that holds the button title for each button with the index of the cell as the reference key. Every time I want to update the title of the button, I will get that title from the button every time the cell is loaded upon cellForRowAtIndexPath. take note that when you scroll the table, cellForRowAtIndexPath will be called when new cells appear in front of you. So as you scroll, the correct titles will be updated to the buttons.

Hope this helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM