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

Posted

技术标签:

【中文标题】自定义 UITableViewCell 中的操作按钮调用的 UITableView reloadRows() 总是落后一步【英文标题】:UITableView reloadRows() called by action button inside custom UITableViewCell always lags one step behind 【发布时间】:2021-01-16 14:26:15 【问题描述】:

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

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)elsefv.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")
    


【问题讨论】:

【参考方案1】:

为您的单元格提供对其控制器的引用是一种不好的模式。

您最好使用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)elseself.fv.insert(indexPath)
        self.tbl.reloadRows(at: [indexPath], with: .none)
    

    return cell

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

【讨论】:

很遗憾,这并没有解决问题,但感谢您提供的这个漂亮的关闭技巧 嗯...它对我有用。您是否使用了我发布的确切代码? 是的,我做到了..同样的问题发生了..动画更改总是落后一步..我不知道这可能是模拟器问题,但在其他情况下,当我将动作分配给通过在 didSelectRow() 委托函数中使用 reloadRows() 整个单元格它工作正常,但我需要将操作分配给按钮而不是整个单元格 我刚刚在这里找到了一个解决方案,它对我有用:) ***.com/questions/24787098/…,通过向按钮添加操作以强制触发委托函数 (didSelectRowAtIndexPath) 并在委托函数内部应用 reloadRows( )【参考方案2】:

我在这里找到了一个解决方案,它对我有用 https://***.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)elseself.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)
    

【讨论】:

以上是关于自定义 UITableViewCell 中的操作按钮调用的 UITableView reloadRows() 总是落后一步的主要内容,如果未能解决你的问题,请参考以下文章

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

如何从 UITableViewCell 中的自定义 UIButton 中删除触摸延迟

自定义UITableViewCell中的UITextField意外发现为零

在自定义 UITableViewCell 中按下 UIButton 时如何获取 indexPathForSelectedRow

来自 .xib 文件的自定义 UITableViewCell

用作自定义 UITableViewCell 的 XIB 中的自定义按钮不响应点击(ios7)