如何在单元格渲染后检查表格视图上的复选标记图标(附件视图)?

Posted

技术标签:

【中文标题】如何在单元格渲染后检查表格视图上的复选标记图标(附件视图)?【英文标题】:How to have checkmarks icons (accessory view) on tableview already checked after cells rendered? 【发布时间】:2018-12-12 13:33:49 【问题描述】:

我有模型数据,它有一个布尔值,表示是否应显示复选标记(accessoryType 为 .checkmark)...

例如,我希望在开始时选中五行中的两行(根据我所说的模型)...问题是,我可以显示复选标记,但是在我点击它们之后,切换不会不能正常工作:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 

    if let cell = tableView.cellForRow(at: indexPath) 
        cell.accessoryType = .checkmark
        model[indexPath.row].isCellSelected = true


    

 func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) 

    if let cell = tableView.cellForRow(at: indexPath) 
        cell.accessoryType = .none
        model[indexPath.row].isCellSelected = false


    


And here is a cellForRowAt:

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

    let data = model[indexPath.row]
    let identifier = data.subtitle != nil ? kSubtitleID : kNormalID

    let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)

    cell.textLabel?.text = data.title
    cell.detailTextLabel?.text = data.subtitle

    return cell

我可以这样显示复选标记:

cell.accessoryType = data.isCellSelected ? .checkmark : .none

但是当我点击它时,它会导致它被选中(allowsMultipleSelection 设置为 true),它不会被切换,而是第一次停留。

这是我使用的模型。真的很简单:

struct CellModel
    var title:String?
    var subtitle:String?
    var isCellSelected:Bool = false

【问题讨论】:

只显示您使用的模型结构,可以通过cellForRowAtdidSelectRowAt..完成。 @vaibhav 我添加了模型示例。 如果你还是卡住了,我把我的答案发过来看看。 【参考方案1】:

您应该只在tableView:didSelectRowAt: 中执行切换,然后重新加载必要的单元格。你应该省略tableView:didDeselectRowAt:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
    if let cell = tableView.cellForRow(at: indexPath) 
        // Toggle the value - true becomes false, false becomes true
        model[indexPath.row].isCellSelected = !model[indexPath.row].isCellSelected
        tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .none)
    


func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
    let data = model[indexPath.row]
    let identifier = data.subtitle != nil ? kSubtitleID : kNormalID

    let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)

    cell.textLabel?.text = data.title
    cell.detailTextLabel?.text = data.subtitle
    // Set the checkmark or none depending on your model value
    cell.inputAccessoryType = data.isCellSelected ? .checkmark : .none

    return cell

编辑:

仅用于单选 + 取消选择所选项目的能力:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
    // Loop through all model items
    for (index, item) in model.enumerated() 
        if (index == indexPath.row) 
            // Toggle tapped item
            item.isCellSelected = !item.isCellSelected
         else 
            // Deselect all other items
            item.isCellSelected = false
        
    
    tableView.reloadData();

【讨论】:

嘿,成功了,谢谢!我记得我之前对这种方法有过问题,但不能说是不是同样的情况(可能不是),但我还是跳过了它哈。 没问题。确保您的 tableView:cellForRowAt: 方法反映了您的单元格可能与您的模型同步的任何状态,因为如果您仅在 tableView:didSelectRowAt: 方法中直接修改 UITableViewCell 对象并将单元格滚动到屏幕外,则将重新渲染所有更改没有反映在模型中的它们将丢失。可能有点混乱,抱歉。 问题是,它在allowsMultipleSelection = true 时有效,但它不适用于具有单个选择的tableview...【参考方案2】:

在您有一个保存布尔值的模型之前,您不必使用选择和取消选择 在tableview中的didselect方法

func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) 

        if model[index.row].isCellSelected == true 
            model[indexpath.row].isCellSelected = false
        
        else 
            model[indexpath.row].isCellSelected = true
        
        tableview.reloadData


在单元格中进行行检查

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
    if model[index.row].isCellSelected
        cell.accessoryType = .checkmark
    
    else 
        cell.accessoryType = .none
    

【讨论】:

【参考方案3】:

我所做的是:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
        let data = model[indexPath.row]
        if let cell = tableView.cellForRow(at: indexPath) 
            cell.accessoryType = .checkmark
            model[indexPath.row].isCellSelected = true

            self.selectedData?(model.filter$0.isCellSelected)
        
    
     func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) 
         let data = model[indexPath.row]
        if let cell = tableView.cellForRow(at: indexPath) 
            cell.accessoryType = .none
            model[indexPath.row].isCellSelected = false

            self.selectedData?(model.filter$0.isCellSelected)
        
    

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

        let data = model[indexPath.row]
        let identifier = data.subtitle != nil ? kSubtitleID : kBasicID

        let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)

        cell.textLabel?.text = data.title
        cell.detailTextLabel?.text = data.subtitle

        cell.accessoryType = data.isCellSelected ? .checkmark : .none
        return cell
    

但我必须首先设置实际选择了哪些单元格:

func setSelectedCells()

        let selectedIndexes = self.model.enumerated().compactMap  (index, element) -> IndexPath? in
            if element.isCellSelected
                return IndexPath(row: index, section: 0)
            
            return nil
        

        selectedIndexes.forEach
            selectRow(at: $0, animated: false, scrollPosition: .none)
        
    

然后在 viewDidAppear 中调用它,因为我必须确保表格已经绘制了它的内容(因为如果我们尝试一些尚不存在的东西(单元格),这将失败)。不是最好的方法,但它解决了 singlemultiple 选择与 initial states 的问题。

【讨论】:

它可以用更少的代码或方法来完成,以在任何状态下显示选定的选项。【参考方案4】:

那么,cellForRowAt 方法就变得很简单了:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
    let data = model[indexPath.row]
    let identifier = data.subtitle != nil ? kSubtitleID : kNormalID

    let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)

    cell.textLabel?.text = data.title
    cell.detailTextLabel?.text = data.subtitle

    cell.accessoryType = data.isCellSelected ? .checkmark : .none

    return cell

那么,didSelectRowAt 方法就变得很简单了:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
    tableView.deselectRow(at: indexPath, animated: true)

    if let cell = tableView.cellForRow(at: indexPath)         
        let isCellSelected = !(model[indexPath.row].isCellSelected)
        cell.accessoryType = isCellSelected ? .checkmark : .none
        model[indexPath.row].isCellSelected = isCellSelected
    

请注意,我们必须切换标志并根据新值更新单元格和模型。

更好的是,您可以将 .accessory 类型更新行替换为:

tableView.reloadRows(at: [indexPath], with: .none)

所以方法看起来像这样:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
    tableView.deselectRow(at: indexPath, animated: true)

    if let cell = tableView.cellForRow(at: indexPath)         
        let isCellSelected = !(model[indexPath.row].isCellSelected)
        model[indexPath.row].isCellSelected = isCellSelected
        tableView.reloadRows(at: [indexPath], with: .none)
    

请注意,我们更新model,然后重新加载将导致cellForRowAt 被调用的单元格,并且该方法会根据模型进行正确的配置。

更一般地说,我建议使用一些StateController 对象来保持每个单元格/行的状态并注意toggling

我不久前写了一篇完整的文章,它完全可以满足您的需要,并且还展示了许多最佳实践:

https://idevtv.com/how-to-display-a-list-of-items-in-ios-using-table-views/

【讨论】:

我会试试这个...你试过了吗?这是否适用于基于单选和多选的表格(在开始时已经检查了一些单元格)?顺便说一句,我提供了我所有的代码,所以如果你有时间,你可以自己重现问题。 是的,这是生产代码,它可以工作。我的代码允许您选择许多单元格。我的表实际上已经将 allowedMultipleSelection 设置为 false。在选择时,它会取消选择具有默认动画的行,但它会跟踪它,根据需要设置/隐藏复选标记,并在 State Controller 类的数组内设置真/假标志。【参考方案5】:

@Whirlwind,嗨,如果您只需要在 tableView 内显示选择,这并不复杂,我在您的模型更新后提供我的答案。

这可以通过仅在didSelectRowAt 中维护isCellSelected 属性来完成。

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
    model[indexPath.row].isCellSelected = model[indexPath.row].isCellSelected ? false : true
    tableView.reloadData()

这里是cellForRowAt,你也可以在这里多修改几行。

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
    let data = model[indexPath.row]
    let identifier = "kNormalID"
    let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
    cell.textLabel?.text = data.title
    cell.detailTextLabel?.text = data.subtitle
    cell.accessoryType = data.isCellSelected ? .checkmark : .none
    return cell

希望我能做到最好。如果我错过了什么,请告诉我。

【讨论】:

【参考方案6】:

你为什么不试试关注!

     func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) 

    if let cell = tableView.cellForRow(at: indexPath) 
        cell.accessoryType = .none
        model[indexPath.row].isCellSelected = false
    tableView.reloadRows(at: [indexPath], with: <UITableViewRowAnimation>)
    

【讨论】:

以上是关于如何在单元格渲染后检查表格视图上的复选标记图标(附件视图)?的主要内容,如果未能解决你的问题,请参考以下文章

设置静态表格视图单元格的复选标记

TableView Cell 重用和不需要的复选标记 - 这让我很生气

滚动时重复 TableView 上的复选标记

显示复选标记附件视图移动表格内容

点击两次以突出显示收藏视图中的复选标记

如何在表格视图单元格上应用复选标记