UITableView reloadRows() 由自定义 UITableViewCell 内的操作按钮调用总是落后一步

问题描述

我有一个 UITableView 并且我制作了一个自定义 cellview 来在每个单元格内添加一个按钮,该按钮应该在单击时更改其颜色。 尽管数据已更新并正确打印,但视图总是落后一步,即当我单击第一个按钮时,它不会改变颜色,直到我单击下一个按钮。我怀疑 reloadRows() 函数调用时会导致此问题从 tableview Cell 内部。

import UIKit
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
    var mlist = [["1","2","3"],["4","5","6","7","8"],["9","10"],["11","12"],["13","14"]]
    var leagues = ["LaLiga","Premier League","Bundesliga","Serie A","Ligue 1"]
    var hidden = Set<Int>()
    @IBOutlet weak var tbl: UITableView!
    
    var fv = Set<IndexPath>()
    
    func indxs(_ section:Int) -> [IndexPath] {
        var indxs = [IndexPath]()
        for row in 0..<mlist[section].count {
            indxs.append(IndexPath(row: row,section: section))
        }
        return indxs
    }
    
    @objc
    private func hideSection(sender: UIButton) {
        let section = sender.tag
        if hidden.contains(section) {
            hidden.remove(section)
            tbl.insertRows(at: indxs(section),with: .fade)
            
        }else{
            hidden.insert(section)
            tbl.deleteRows(at: indxs(section),with: .fade)
        }
    }
    
    func cellMethod(cell: UITableViewCell) {
        guard let i = tbl.indexPath(for: cell) else { return }
        if fv.contains(i){fv.remove(i)}else{fv.insert(i)}
        tbl.reloadRows(at: [i],with: .none)
    }

    
    func tableView(_ tableView: UITableView,numberOfRowsInSection section: Int) -> Int {
        if hidden.contains(section) {
            return 0
        }
        return mlist[section].count
    }
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return mlist.count
    }
    
    func tableView(_ tableView: UITableView,viewForHeaderInSection section: Int) -> UIView? {
        let sectionButton = UIButton()
        sectionButton.setTitle(leagues[section],for: .normal)
        sectionButton.backgroundColor = .purple
        sectionButton.tag = section
        sectionButton.addTarget(self,action: #selector(self.hideSection(sender:)),for: .touchUpInside)
        return sectionButton
    }
    
    func tableView(_ tableView: UITableView,cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell1",for: indexPath) as! mcell
        cell.link = self
        cell.textLabel?.text = mlist[indexPath.section][indexPath.row]
        if fv.contains(indexPath){
            cell.accessoryView?.tintColor = .orange
        }else{
            cell.accessoryView?.tintColor = .gray
        }
        return cell
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        tbl.register(mcell.self,forCellReuseIdentifier: "cell1")
    }
}
import UIKit

class mcell: UITableViewCell{
    var link:ViewController?
    
    override init(style: UITableViewCell.CellStyle,reuseIdentifier: String?) {
        super.init(style: style,reuseIdentifier: reuseIdentifier)
        let starButton = UIButton(type: .system)
        starButton.setimage(#imageLiteral(resourceName: "fav_star"),for: .normal)
        starButton.frame = CGRect(x: 0,y: 0,width: 50,height: 50)
        starButton.tintColor = .red
        starButton.addTarget(self,action: #selector(handleMarkAsFavorite),for: .touchUpInside)
        accessoryView = starButton
    }
    
    @objc private func handleMarkAsFavorite() {
        print(self.textLabel!.text!)
        link?.cellMethod(cell: self)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

enter image description here

解决方法

让你的单元格引用它的控制器是一种糟糕的模式。

您最好使用 closure 在您点击星星时让单元格“回调”到控制器。

这是您的单元格类的更新:

class mcell: UITableViewCell{
    
    // "callback" closure to tell the controller that the Star was tapped
    var starWasTapped: (() -> ())?
    
    override init(style: UITableViewCell.CellStyle,reuseIdentifier: String?) {
        super.init(style: style,reuseIdentifier: reuseIdentifier)
        let starButton = UIButton(type: .system)
        starButton.setImage(#imageLiteral(resourceName: "fav_star"),for: .normal)
        starButton.frame = CGRect(x: 0,y: 0,width: 50,height: 50)
        starButton.tintColor = .red
        starButton.addTarget(self,action: #selector(handleMarkAsFavorite),for: .touchUpInside)
        accessoryView = starButton
    }
    
    @objc private func handleMarkAsFavorite() {
        print(self.textLabel!.text!)
        
        // tell the controller the Star was tapped
        starWasTapped?()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
}

然后,您的 cellForRowAt 将如下所示:

func tableView(_ tableView: UITableView,cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell1",for: indexPath) as! mcell
    
    cell.textLabel?.text = mlist[indexPath.section][indexPath.row]
    if fv.contains(indexPath){
        cell.accessoryView?.tintColor = .orange
    }else{
        cell.accessoryView?.tintColor = .gray
    }

    // set the cell's "callback" closure
    cell.starWasTapped = { [weak self] in
        guard let self = self else { return }
        if self.fv.contains(indexPath){self.fv.remove(indexPath)}else{self.fv.insert(indexPath)}
        self.tbl.reloadRows(at: [indexPath],with: .none)
    }

    return cell
}

现在您不需要单独的 func cellMethod(...)

,

我在这里找到了一个解决方案,它对我有用 https://stackoverflow.com/a/39416618/14061160 ,通过向按钮添加动作以强制触发委托函数 (didSelectRowAtIndexPath) 并在委托函数内应用 reloadRows()

func tableView(_ tableView: UITableView,cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell1",for: indexPath) as! mcell
//        cell.link = self
        cell.textLabel?.text = mlist[indexPath.section][indexPath.row]
        if fv.contains(indexPath){
            cell.accessoryView?.tintColor = .orange
        }else{
            cell.accessoryView?.tintColor = .gray
        }
        cell.starWasTapped = { [weak self] in
            guard let self = self else { return }
            if self.fv.contains(indexPath){self.fv.remove(indexPath)}else{self.fv.insert(indexPath)}
            self.tbl.delegate!.tableView?(self.tbl,didSelectRowAt: indexPath)
        }
        return cell
    }
    func tableView(_ tableView: UITableView,didSelectRowAt indexPath: IndexPath) {
        tbl.reloadRows(at: [indexPath],with: .fade)
    }