How To Access UITableView.visibleCells Every Time New Cell Emerge

Viewed 569

I have a tableView displaying [Double]. Very Simple. But I also wanna display the average of the onscreen numbers on every row, and the difference between this number and the average.

enter image description here

Because I need to re-calculated the average every time a new row appears, I'm thinking about accessing tableView.visibleCells in cellForRowAt: indexPath method, and then update the average of this row and every other rows on screen, because the average of onscreen rows should be the same for all the onscreen rows.

But then I got this error message [Assert] Attempted to access the table view's visibleCells while they were in the process of being updated, which is not allowed. Make a symbolic breakpoint at UITableViewAlertForVisibleCellsAccessDuringUpdate to catch this in the debugger and see what caused this to occur. Perhaps you are trying to ask the table view for the visible cells from inside a table view callback about a specific row?

While this is loud and clear, I'm wondering what is the correct way or workaround for this?

Code is very simple

class ViewController: UIViewController {
    
    var data:[Double] = [13,32,43,56,89,42,26,17,63,41,73,54,26,87,64,33,26,51,99,85,57,43,30,33,20]
    
    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()        
        self.tableView.dataSource = self
        self.tableView.delegate = self
        
    }

}

extension ViewController: UITableViewDataSource, UITableViewDelegate {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        data.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell  = tableView.dequeueReusableCell(withIdentifier: "default")!
        cell.textLabel?.text = "\(data[indexPath.row])"
        print(tableView.visibleCells.count) // THIS LINE PRODUCE ERROR
        return cell    
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 64
    }
    
}

What I have tried:

I've tried didEndDisplaying and willDisplay, when I added print(tableView.visibleCells.count) to either of them, same error message was given back.

1 Answers

Answer:

You can use UITableView's delegate functions to calculate this.

tableView(_:willDisplay:forRowAt:) is called every time before cell becomes visible, so you can recalculate your average value at this moment. Also, there is tableView(_:didEndDisplaying:forRowAt:) which fires when cell goes off display and also can be used to recalculate.


Documentation:

tableView(_:willDisplay:forRowAt:)

tableView(_:didEndDisplaying:forRowAt:)


UPD:

For calculation use tableView.indexPathsForVisibleItems


Example:

import UIKit

class ViewController: UIViewController {
    
    var data:[Double] = [13,32,43,56,89,42,26,17,63,41,73,54,26,87,64,33,26,51,99,85,57,43,30,33,20]
    
    @IBOutlet weak var tableView: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .red
        self.tableView.dataSource = self
        self.tableView.delegate = self
        self.tableView.reloadData()
    }
    
    private func calculate() {
        let count = tableView.indexPathsForVisibleRows?.count
        let sum = tableView.indexPathsForVisibleRows?
            .map { data[$0.row] }
            .reduce(0) { $0 + $1 }
        
        if let count = count, let sum = sum {
            print(sum / Double(count))
        }
    }
}

extension ViewController: UITableViewDataSource, UITableViewDelegate {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        data.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell  = tableView.dequeueReusableCell(withIdentifier: "default")!
        cell.textLabel?.text = "\(data[indexPath.row])"
        return cell
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 64
    }
    
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        calculate()
    }
    
    func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        calculate()
    }
}
Related