UITableView: reloadRows(at:) takes two hits for table to be updated and scroll every time

為{幸葍}努か 提交于 2019-12-20 06:08:52

问题


I have a table view (controller: MetricsViewController) which gets updated from a CoreData database. I have used prototype cells (MetricsViewCell) which I have customized for my needs. It contains a segmented control, a UIView (metricsChart, which is used to display a chart - animatedCircle), and some UILabels.

MetricsViewCell:

class MetricsViewCell: UITableViewCell {

    var delegate: SelectSegmentedControl?
    var animatedCircle: AnimatedCircle?

    @IBOutlet weak var percentageCorrect: UILabel!
    @IBOutlet weak var totalPlay: UILabel!

    @IBOutlet weak var metricsChart: UIView! {
        didSet {
            animatedCircle = AnimatedCircle(frame: metricsChart.bounds)
        }
    }
    @IBOutlet weak var recommendationLabel: UILabel!

    @IBOutlet weak var objectType: UISegmentedControl!

    @IBAction func displayObjectType(_ sender: UISegmentedControl) {
        delegate?.tapped(cell: self)

    }
}

protocol SelectSegmentedControl {
    func tapped(cell: MetricsViewCell)
}

MetricsViewController:

class MetricsViewController: FetchedResultsTableViewController, SelectSegmentedControl {

func tapped(cell: MetricsViewCell) {
    if let indexPath = tableView.indexPath(for: cell) {
        tableView.reloadRows(at: [indexPath], with: .none)
    }
}

var container: NSPersistentContainer? = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer { didSet { updateUI() } }

private var fetchedResultsController: NSFetchedResultsController<Object>?

private func updateUI() {
    if let context = container?.viewContext {
        let request: NSFetchRequest<Object> = Object.fetchRequest()
        request.sortDescriptors = []
        fetchedResultsController = NSFetchedResultsController<Object>(
            fetchRequest: request,
            managedObjectContext: context,
            sectionNameKeyPath: "game.gameIndex",
            cacheName: nil)
        try? fetchedResultsController?.performFetch()
        tableView.reloadData()
    }
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Object Cell", for: indexPath)
    if let object = fetchedResultsController?.object(at: indexPath) {

        if let objectCell = cell as? MetricsViewCell {
            objectCell.delegate = self

            let request: NSFetchRequest<Object> = Object.fetchRequest()
            ...
            ...

            }
        }
    }
    return cell
}

When a user selects one of the segments in a certain section's segmented control, MetricsViewController should reload the data in that particular row. (There are two sections with one row each). Hence, I've defined a protocol in MetricsViewCell to inform inform my controller on user action.

Data is being updated using FetchedResultsTableViewController - which basically acts as a delegate between CoreData and TableView. Everything is fine with that, meaning I am getting the correct data into my TableView.

There are two issues:

  1. I have to tap segmented control's segment twice to reload the data in the row where segmented control was tapped.

  2. The table scrolls back up and then down every time a segment from segmented control is selected.

Help would be very much appreciated. I've depended on this community for a lot of issues I've faced during the development and am thankful already :)

For example, in Animal Recognition section, I have to hit "Intermediate" two times for its row to be reloaded (If you look closely, the first time I hit Intermediate, it gets selected for a fraction of second, then it goes back to "Basic" or whatever segment was selected first. Second time when I hit intermediate, it goes to Intermediate). Plus, the table scroll up and down, which I don't want.

Edit: Added more context around my usage of CoreData and persistent container.


回答1:


Your UISegmentedControl are reusing [Default behaviour of UITableView].

To avoid that, keep dictionary for getting and storing values.

Another thing, try outlet connection as Action for UISegmentedControl in UIViewController itself, instead of your UITableViewCell

The below code will not reload your tableview when you tap UISegmentedControl . You can avoid, delegates call too.

Below codes are basic demo for UISegmentedControl. Do customise as per your need.

var segmentDict = [Int : Int]()


override func viewDidLoad() {
    super.viewDidLoad()

    for i in 0...29 // number of rows count
    {
        segmentDict[i] = 0 //DEFAULT SELECTED SEGMENTS
    }

}


func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! SOTableViewCell

    cell.mySegment.selectedSegmentIndex = segmentDict[indexPath.row]!

    cell.selectionStyle = .none
    return cell
}

@IBAction func mySegmentAcn(_ sender: UISegmentedControl) {

    let cellPosition = sender.convert(CGPoint.zero, to: tblVw)
    let indPath = tblVw.indexPathForRow(at: cellPosition)

    segmentDict[(indPath?.row)!] = sender.selectedSegmentIndex


    print("Sender.tag     ", indPath)

}



回答2:


Instead of using indexPathForRow(at: <#T##CGPoint#>) function to get the indexPath object of cell you can directly use indexPath(for: <#T##UITableViewCell#>) as you are receiving the cell object to func tapped(cell: MetricsViewCell) {} and try to update your data on the UI always in main thready as below.

func tapped(cell: MetricsViewCell) {
    if let lIndexPath = table.indexPath(for: <#T##UITableViewCell#>){
        DispatchQueue.main.async(execute: {
            table.reloadRows(at: lIndexPath, with: .none)
        })
    }
}


来源:https://stackoverflow.com/questions/48636176/uitableview-reloadrowsat-takes-two-hits-for-table-to-be-updated-and-scroll-e

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!