UITableViewCell 无意中在不需要的行上放置了复选标记

Posted

技术标签:

【中文标题】UITableViewCell 无意中在不需要的行上放置了复选标记【英文标题】:UITableViewCell unintentionally placing checkmark on unwanted rows 【发布时间】:2018-02-13 02:19:41 【问题描述】:

我正在制作一个音乐流派选择应用程序,当我去我的桌子选择流派时,我选择了一行,它从我的选择中选择了大约 10 左右的随机行。

我的选择代码是:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
    let genresFromLibrary = genrequery.collections
    let rowitem = genresFromLibrary![indexPath.row].representativeItem
    print(rowitem?.value(forProperty: MPMediaItemPropertyGenre) as! String
    )
    if let cell = tableView.cellForRow(at: indexPath)
    
        cell.accessoryType = .checkmark
    


override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) 
    if let cell = tableView.cellForRow(at: indexPath)
    
        cell.accessoryType = .none
    

【问题讨论】:

【参考方案1】:

在调用cellForRowAtIndexPath 时,默认情况下会重用单元格。当您不跟踪已选择的 indexPaths 时,这会导致单元格包含错误的数据。您需要跟踪当前选择的索引路径,以便在表格视图中显示适当的附件类型。

一种方法是在 UITableViewController 中有一个属性,它只存储所选单元格的索引路径。可以是数组,也可以是集合。

var selectedIndexPaths = Set<IndexPath>()

当您在didSelectRowAt 上选择一行时,在selectedIndexPaths 中添加或删除单元格,具体取决于索引路径是否已在数组中:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
    if selectedIndexPaths.contains(indexPath) 
         // The index path is already in the array, so remove it.
         selectedIndexPaths.remove(indexPathIndex)
     else 
         // The index path is not part of the array
         selectedIndexPaths.append(indexPath)
    

    // Show the changes in the selected cell (otherwise you wouldn't see the checkmark or lack thereof until cellForRowAt got called again for this cell).
    tableView.reloadRows(at: [indexPath], with: .none)

一旦你有了这个,在你的 cellForRowAtIndexPath 上,检查 indexPath 是否在 selectedIndexPaths 数组中以选择 accessoryType

if selectedIndexPaths.contains(indexPath) 
    // Cell is selected
    cell.accessoryType = .checkmark
 else 
    cell.accessoryType = .none

这应该可以解决每 10 个左右检查一次看似随机的单元格的问题(这不是随机的,只是带有复选标记的单元格正在被重复使用)。

【讨论】:

didSelectRowAt 中,更新selectedIndexPaths 后简单地重新加载indexPath 处的行可能比获取和更新单元格更好。 @rmaddy 好点。我忘了reloadRows。我会更新我的答案来改变这一点。 如果您将selectedIndexPaths 设置为Set 而不是Array,您的代码也会更加高效。然后删除索引路径会更有效。 是的,我提到它可以是数组或集合,通常我在这些示例中使用数组,因为大多数新手可能更习惯使用它们。无论如何,我已经更改了它,因为我已经完成了reloadRows 更改。感谢您指出这一切,@rmaddy! 现在您已经将其设置为一组,您需要重做删除索引路径的代码。您不再需要获取索引。【参考方案2】:

因为cellForRow 返回您生成的缓存单元格。当滚动出屏幕时,单元格的顺序会改变并且单元格会被重复使用。所以它似乎是“随机选择的”。

不要使用cellForRow,而是记录选择数据。

这里的代码在单视图游乐场中工作。

import UIKit
import PlaygroundSupport

class MyViewController : UIViewController, UITableViewDataSource, UITableViewDelegate 

    let tableView = UITableView()
    var selection: [IndexPath: Bool] = [:]

    override func viewDidLoad() 
        super.viewDidLoad()
        tableView.delegate = self
        tableView.dataSource = self
        tableView.tableFooterView = UIView()
        view.addSubview(tableView)
    

    override func viewDidLayoutSubviews() 
        super.viewDidLayoutSubviews()
        tableView.frame = self.view.bounds
    

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCell(withIdentifier: "c")
        if let sc = cell 
            sc.accessoryType = .none
            let isSelected = selection[indexPath] ?? false
            sc.accessoryType = isSelected ? .checkmark : .none
            return sc
        
        return UITableViewCell(style: .default, reuseIdentifier: "c")
    

    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) 
        cell.textLabel?.text = NSNumber(value: indexPath.row).stringValue
    

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
        selection[indexPath] = true
        tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
    

    func numberOfSections(in tableView: UITableView) -> Int 
        return 1
    

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return 30
    


// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()

【讨论】:

为什么accessoryTypecellForRowAt中设置了两次? 这是我第一次看到这样做。我觉得这会变得很复杂,因为您必须一直跟踪所有索引路径及其各自的布尔值,特别是如果您的表视图控制器非常动态(单元格插入、删除、移动等)。

以上是关于UITableViewCell 无意中在不需要的行上放置了复选标记的主要内容,如果未能解决你的问题,请参考以下文章

iOS自定义表格视图的行选择样式?

删除记事本++中在同一行上具有相同编号的行

在 ViewSwitcher 中在不确定的 ProgressBar 和 Image 之间切换

重复的 UItableViewCell,UITableView 的行

当 UITableViewCell 中的行数固定时,UILabel 占用空间?

删除自定义 UITableViewCell 框架问题的行