UITableView.reloadRows does not reload the target row.

I created a tableview using custom cell. I want reload the target cell, let's say [section = 0, row = 0], and use the UITableView.reloadRows method.

let indexPath = IndexPath.init(row: 0, section: 0)
self.tableView.reloadRows(at: [indexPath], with: .fade)

When the button in the target cell is tapped, this method is called.


When this method called, the target cell [section = 0, row = 0] is not reloaded, but the tableview is scrolled and other cells [(section = 0, row = 2),(section = 0, row = 3)] are reloaded.


I have no idea why this happens.

Please help me if anyone has any solutions.

Best regards,

Answered by Claude31 in 372878022

I should have been more precise. Reloading is for all, only redisplay is partial. Sorry for creating confusion.


Instance Method reloadData()


Reloads the rows and sections of the table view.



Declaration

func reloadData()

Discussion

Call this method to reload all the data that is used to construct the table, including cells, section headers and footers, index arrays, and so on. For efficiency, the table view redisplays only those rows that are visible. It adjusts offsets if the table shrinks as a result of the reload. The table view’s delegate or data source calls this method when it wants the table view to completely reload its data. It should not be called in the methods that insert or delete rows, especially within an animation block implemented with calls to

beginUpdates()
and
endUpdates()
.



To change the height when you tap the button in cell:

- first, get the cell that contains the button (I copy from an existing project, did not test in this context, but should work)

One way to do this is to explore all cells (in the button IBAction):


if let cell = sender.superview?.superview {

if let theTableView = cell.superview as? UITableView { // So, the button was in a cellView

for row in 0..<thetableview.numberofrows(insection: 0)="" { // I assume there is a single section

let indexPath = IndexPath(row: row, section: 0)

if let cellAtRow = theTableView.cellForRow(at: indexPath) { // Will call didSelectRowAt

theTableView.selectRow(at: indexPath, animated: true, scrollPosition: .none) // Could scroll to position



AndI tested by modifying slightly the code


import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    @IBOutlet weak var aTableView: UITableView!
    var isTapped: Bool = false
    var selectedCellPath: IndexPath?          // NEW
    override func viewDidLoad() {
        super.viewDidLoad()
        aTableView.delegate = self
        aTableView.dataSource = self
    }
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 50
    }
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        print("heightForRowAt", indexPath, selectedCellPath)
        if selectedCellPath != nil && indexPath.row == selectedCellPath!.row { // 0 {     // CHANGED
            return 100.0
        }
        return 50.0
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel!.text = "cell \(indexPath.row)"

        let backgroundView = UIView()
        backgroundView.backgroundColor = .red
        cell.selectedBackgroundView = backgroundView

        return cell
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("didSelectRowAt:", indexPath)
        isTapped = true
        selectedCellPath = indexPath

        tableView.reloadRows(at: [indexPath], with: .none)
        tableView.selectRow(at: indexPath, animated: true, scrollPosition: .middle)
    }

}



Additional info;

I use big cell size.

Height of the cell is bigger than height of the screen.

If I use relatively small cell, it works fine.

But, if I use bigger cells, it does not work correctly.


I use a big photo image in each cell and some of them are as big as screen size.


Is this normal behavier of this method?

Whet happens if you set animation to .none ?

Same.

It does not matter if you set animation to .none or any other option.

You can reproduce the same symptom by the following code;

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        if indexPath.row == 0 {
            return 100.0
        }
        return 50.0
    }

When you create this tableView and scroll up by around 50 point, then tap cell 0.

Please try it.

To Apple Technical Support personel,


Please try the following code to find the problem I have been facing.


import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    @IBOutlet weak var aTableView: UITableView!
     var isTapped: Bool = false

    override func viewDidLoad() {
        super.viewDidLoad()
        aTableView.delegate = self
        aTableView.dataSource = self
    }
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 50
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        if indexPath.row == 0 {
            return 100.0
        }
        return 50.0
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        print("cellForRowAt:", indexPath)
        let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel!.text = "cell \(indexPath.row)"
        if isTapped {
            cell.backgroundColor = .red
            isTapped = false
        }
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("didSelectRowAt:", indexPath)
        isTapped = true
        tableView.reloadRows(at: [indexPath], with: .none)
    }
}


Launch the app and scroll the tableView by 50 point, which is half height of cell 0, then tap cell 0. If you use iPhone8,7,6, the background of cell 14 becomes red instead of cell 0.

You can also find the print "didSelectRowAt: [0, 0]" and "cellForRowAt: [0, 14]".


Is this an UITableView's bug, or correct behavier?

Please try it and give me your comment.


Best regards,

I tried and got the problem.


- when you reload, you reload only thells that were not visible (the 1'th for instance), not the first.

I changed the code to set a red background for each cell ; it will be displayed when selected.


import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
   
    @IBOutlet weak var aTableView: UITableView!
    var isTapped: Bool = false
   
    override func viewDidLoad() {
        super.viewDidLoad()
        aTableView.delegate = self
        aTableView.dataSource = self
    }
   
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
   
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 50
    }
   
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        if indexPath.row == 0 {
            return 100.0
        }
        return 50.0
    }
   
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//        print("cellForRowAt:", indexPath)
        let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel!.text = "cell \(indexPath.row)"
//        if isTapped {
//            cell.backgroundColor = .red
            isTapped = false
//        }
        let backgroundView = UIView()
        backgroundView.backgroundColor = .red
        cell.selectedBackgroundView = backgroundView

        return cell
    }
   
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("didSelectRowAt:", indexPath)
        isTapped = true
//        tableView.reloadRows(at: [indexPath], with: .none)
    }


}

Thank you for you response.


It seems working in case of background color.


This code is just a sample code to let everyone know the problem ("reloadRows" does not work correctly).

You mentioned that reload is just applied to the cells that are not visible.

Is this true?

I thought reload is applied to all cells.

Am I wrong?


Anyway,

What I would like to do is to change selected cell height.


I am developing news feed app and using a tableView to display each news in each cell.

A news has a large image vIew and long text description, which make the cell height relatively higher.

The text description is display by UILabel. If number of lines of the UILabel exceeds 7 lines, height of UILabel is fixed at 7 lines and “read more” button is shown to expand UILabel height and cell height.


When you scroll the news cell and “read more” button is displayed in the middle of the iPhone screen, top of the cell is hidden at - (minus) Y axis. If you tap “read more” button, “reloadRows” behaves something wrong like my sample app.


I would like to expand UILabel and the cell correctly when “read more” button is tapped.

Do you have any good code to realize this?


I appreciate your support.

Accepted Answer

I should have been more precise. Reloading is for all, only redisplay is partial. Sorry for creating confusion.


Instance Method reloadData()


Reloads the rows and sections of the table view.



Declaration

func reloadData()

Discussion

Call this method to reload all the data that is used to construct the table, including cells, section headers and footers, index arrays, and so on. For efficiency, the table view redisplays only those rows that are visible. It adjusts offsets if the table shrinks as a result of the reload. The table view’s delegate or data source calls this method when it wants the table view to completely reload its data. It should not be called in the methods that insert or delete rows, especially within an animation block implemented with calls to

beginUpdates()
and
endUpdates()
.



To change the height when you tap the button in cell:

- first, get the cell that contains the button (I copy from an existing project, did not test in this context, but should work)

One way to do this is to explore all cells (in the button IBAction):


if let cell = sender.superview?.superview {

if let theTableView = cell.superview as? UITableView { // So, the button was in a cellView

for row in 0..<thetableview.numberofrows(insection: 0)="" { // I assume there is a single section

let indexPath = IndexPath(row: row, section: 0)

if let cellAtRow = theTableView.cellForRow(at: indexPath) { // Will call didSelectRowAt

theTableView.selectRow(at: indexPath, animated: true, scrollPosition: .none) // Could scroll to position



AndI tested by modifying slightly the code


import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    @IBOutlet weak var aTableView: UITableView!
    var isTapped: Bool = false
    var selectedCellPath: IndexPath?          // NEW
    override func viewDidLoad() {
        super.viewDidLoad()
        aTableView.delegate = self
        aTableView.dataSource = self
    }
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 50
    }
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        print("heightForRowAt", indexPath, selectedCellPath)
        if selectedCellPath != nil && indexPath.row == selectedCellPath!.row { // 0 {     // CHANGED
            return 100.0
        }
        return 50.0
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel!.text = "cell \(indexPath.row)"

        let backgroundView = UIView()
        backgroundView.backgroundColor = .red
        cell.selectedBackgroundView = backgroundView

        return cell
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("didSelectRowAt:", indexPath)
        isTapped = true
        selectedCellPath = indexPath

        tableView.reloadRows(at: [indexPath], with: .none)
        tableView.selectRow(at: indexPath, animated: true, scrollPosition: .middle)
    }

}



Thank you for your response.


Yes, your code works fine for my app.

It seems I just missed "tableView.selectRow(at:...)" method.


Thank you again for your support.

UITableView.reloadRows does not reload the target row.
 
 
Q