Błażej
Błażej

Reputation: 3635

UITableView resetting UITableViewCell after reloading

For start how I've created expandable cell with UIPicker. Just in case that would be relevant to issue.

It's created in UITableView by this code

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    var identifyer = ""
    switch(indexPath.row){
  //other cells
    case 1:
        //Kategoria
        identifyer = "category"
        categoryCell = tableView.dequeueReusableCellWithIdentifier(identifyer) as? CategoryTableViewCell
        categoryCell.pickerView.delegate = self
        categoryCell.pickerView.dataSource = self
        return categoryCell
  //other cells
}

Then I have to recognize if it's touched

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    if (indexPath.row == 1){
        isCategoryCellSelected = true
        tableView.reloadRowsAtIndexPaths([categoryIndexPath()], withRowAnimation: UITableViewRowAnimation.Automatic)
    }
}

That's how I replace text in UILabel

func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
    categoryCell.categoryNameLabel.text = categories[row]
}

And finaly when tableView refresh cell

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    switch(indexPath.row){
    case 1:
        if (isCategoryCellSelected){
            return CategoryTableViewCell.expandedHeight
        } else {
            return CategoryTableViewCell.defaultHeight
        }
     //other heights
    }
}

Defalut cell looks

[defalut]

Expanded cell looks

[expanded]

ISSUE

So when I choose item in picker then label above should have change it text and that is happening. However, when this cell shrink to default height then replace effect is gone. I have to scroll down and up to see that only then I can see changed text in label.

I assume that UITableView cache this cell and when cell is reloading then it takes this cached version. I'm only guessing. Is it how I think? How I can change this unwanted action?

SOLUTION

As @the_critic pointout my approche to save cells in vars was completly wrong. In same time recreating categoryCell every time I pick row wasn't the correct way to do it. I end up with his way of creating cell but with mine way of setting value to cell.

So it looks like this what's is chagned:

creating cell

categoryIdentifier is private let String

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    var cell: UITableViewCell
    switch(indexPath.row){
// other cells
   case 1:
        //Kategoria
        print("recreate, selected category \(selectedCategory)")
        let categoryCell = tableView.dequeueReusableCellWithIdentifier(categoryIdentifier) as! CategoryTableViewCell
        categoryCell.pickerView.delegate = self
        categoryCell.pickerView.dataSource = self
        updateSelectedCategoryIfNeeded(categoryCell)
        cell = categoryCell

// other cells

    return cell;
}

private func updateSelectedCategoryIfNeeded(cell:CategoryTableViewCell) {
    if let selectedCategory = self.selectedCategory{
        // A category has been selected!
        cell.categoryNameLabel.text = selectedCategory
        updatePicekerRowPosition(cell)
    }else{
        // no category selected!
        cell.categoryNameLabel.text = "Wybierz kategorie..."
    }
}

private func updatePicekerRowPosition(cell:CategoryTableViewCell) {
    if let index = categories.indexOf(selectedCategory!){
        cell.pickerView.selectRow(Int(index.value), inComponent: 0, animated: false)
    }
}    

recognizing row selecting

categoryIndexPath is private let NSIndexPath

func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
    selectedCategory = categories[row]
    if let categoryCell = tableView.cellForRowAtIndexPath(categoryIndexPath) as? CategoryTableViewCell {
        categoryCell.categoryNameLabel.text = selectedCategory
    }
}

Upvotes: 0

Views: 1696

Answers (1)

the_critic
the_critic

Reputation: 12820

From what I can see in your code, there are quite a few things you misunderstand about table views.

I would try to say that more politely, but I can't. Your way of referencing a categoryCell in your code is completely wrong! UITableviewCells are not static references if you dequeue them!

FIRST STEP: Remove the categoryCell variable!!!

The way the table view works is the following:

The cellForRowAtIndexPath method takes a cell from the storyboard or your nib and reuses that cell over and over again! So, in the beginning, you may get away with doing it your way (creating a reference to categoryCell), but the situation changes as soon as you have more cells than fit on the screen, because the variable will reference a different cell!

Reading recommendation: Creating and Configuring a Table View

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    var cell : UITableViewCell?
    switch(indexPath.row){
  //other cells
    case 1:
        //Kategoria
        let identifier = "category"
        // tableview checks if there is a cached cell 
        // for the identifier (reuse), 
        // if there is, it will take that one!
        cell = tableView.dequeueReusableCellWithIdentifier(identifier) as! CategoryTableViewCell
        cell.pickerView.delegate = self
        cell.pickerView.dataSource = self
        if let selectedCategory = self.selectedCategory{
            // A category has been selected!
            cell.categoryNameLabel.text = selectedCategory
        }else{
            // no category selected!
            cell.categoryNameLabel.text = "Wybierz kategorie..."
        }
        return cell
  //other cells
}

As you can see above, I introduced a new variable called selectedCategory, which will reflect which category is currently selected...

Set it up like this in your controller:

var selectedCategory : String?

What happens when you reload a section or row or the whole table, is that all the UITableViewDataSource methods for the given rows are called again! In a way the table view always tries to reflect some state of your model.

You should always reflect changes in your model by reloading the row/section or the whole table (depending on what changed).

So when you pick your category, you change the model and reload your row!

func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {

    // update the model!
    selectedCategory = categories[row]

    // the table view now needs to know that the model changed!
    // this will trigger the dataSource method cellForRowAtIndexPath
    // and because it selectedCategory is now set, it will update 
    // your string accordingly!
    tableView.reloadRowsAtIndexPaths([/*<IndexPath of your categoryLabelCell>*/], withRowAnimation: UITableViewRowAnimation.Automatic)

}

Phew! I hope that helps...

Upvotes: 1

Related Questions