UITableView 自定义单元格按钮从主视图中选择其他按钮?

Posted

技术标签:

【中文标题】UITableView 自定义单元格按钮从主视图中选择其他按钮?【英文标题】:UITableView Custom Cell button selects other buttons out of the main view? 【发布时间】:2018-09-04 08:16:36 【问题描述】:

在我的自定义 tableviewcell 上,我有一个按钮,用户可以按下该按钮在选中和未选中之间切换。这只是一个简单的 UIButton,这个按钮包含在我的 customtableviewcell 中作为插座。

import UIKit

class CustomCellAssessment: UITableViewCell 

@IBOutlet weak var name: UILabel!
@IBOutlet weak var dateTaken: UILabel!
@IBOutlet weak var id: UILabel!
@IBOutlet weak var score: UILabel!
@IBOutlet weak var button: UIButton!


override func awakeFromNib() 
     super.awakeFromNib()
    // Initialization code


 override func setSelected(_ selected: Bool, animated: Bool) 
    super.setSelected(selected, animated: animated)


    // Configure the view for the selected state


@IBAction func selectButton(_ sender: UIButton) 

if sender.isSelected

  sender.isSelected = false

else
  sender.isSelected = true
 
 

奇怪的是,当我按下第一个单元格上的按钮时,它会在同一个表格视图中的另一个单元格(视图外)上选择一个按钮 8 个单元格。每个单元格都有自己的按钮,但好像使用 dequeReusableCell 会导致系统以这种方式运行。为什么要这样做,这是否与 UIbuttons 在 tableviewcells 上的工作方式有关?

谢谢

【问题讨论】:

【参考方案1】:

每个单元格都有自己的按钮,但好像使用 dequeReusableCell 会导致系统以这种方式运行。

错了。 UITableViewCells 是可重用的,所以如果你的 tableView 有 8 个可见的单元格,当加载第 9 个时,单元格 nr 1 将被重用。

解决方案:您需要跟踪单元格的状态。调用cellForRowAtIndexPath方法时,需要从头开始配置单元格。

你可以在你的 ViewController 中有一个包含单元状态的小数组:

var cellsState: [CellState]

并在此处存储每个 indexPath 的选定状态。然后在 cellForRowAtIndexPath 中,为单元格配置状态。

cell.selected = self.cellsState[indexPath.row].selected

所以,我会做的概述是:

1 - 在cellForRowAtIndexPath,我会设置

cell.button.tag = indexPath.row
cell.selected = cellsState[indexPath.row].selected

2 - 将 IBAction 移动到您的 ViewController 或 TableViewController

3 - 当点击方法被调用时,更新单元格的选中状态

self.cellsState[sender.tag].selected = true

记得始终在cellForRowAtIndexPath配置整个单元格

编辑:

import UIKit

struct CellState 
    var selected:Bool
    init()
        selected = false
    


class MyCell: UITableViewCell 

    @IBOutlet weak var button: UIButton!

    override func awakeFromNib() 
        self.button.setTitleColor(.red, for: .selected)
        self.button.setTitleColor(.black, for: .normal)
    



class ViewController: UIViewController, UITableViewDataSource 

    var cellsState:[CellState] = []

    @IBOutlet weak var tableView: UITableView!


    override func viewDidLoad() 
        super.viewDidLoad()

        //Add state for 5 cells.
        for _ in 0...5 
            self.cellsState.append(CellState())
        
       


    @IBAction func didClick(_ sender: UIButton) 

        self.cellsState[sender.tag].selected = true
        self.tableView.reloadData()
    

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return cellsState.count
    

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! MyCell
        cell.button.isSelected = self.cellsState[indexPath.row].selected
        cell.button.tag = indexPath.row
        return cell
    

【讨论】:

感谢 crom87 的回答!如何将每个索引路径的选定状态存储到 cellsState 数组中?在 numberOfRowsInSection 中? CellState 可以有一个名为 .selected 的属性。默认可以为false,当用户点击按钮时,变为true。当你知道你有多少项目时,我会创建这个数组。如果表格视图有固定数量的单元格,比如说 5,您可以在 viewDidLoad() 上创建数组。你怎么看? 我刚刚用工作代码编辑了答案。你可以试试看。 您的见解非常有帮助,我现在已经完成所有工作和设置。感谢您对 crom87 的帮助!【参考方案2】:

当您点击按钮时,您正在设置按钮的选定状态。当单元格被重用时,按钮仍处于选中状态 - 您没有做任何事情来重置或更新该状态。

如果按钮选择与表格单元格选择是分开的,那么您需要跟踪选定按钮的索引路径作为数据模型的一部分,然后在 @987654321 中更新按钮的选定状态@。

表格视图只会创建所需数量的单元格来显示屏幕和一点点信息。之后,细胞被重复使用。如果单元格滚动到屏幕顶部,表格视图会将其放入重用队列中,然后当您滚动时它需要在底部显示新行时,它会将其拉出队列并将其提供给您tableView(_: cellForRowAt:)

您在此处获得的单元格将与您之前使用 8 行左右的单元格完全相同,完全由您来配置它。您可以在cellForRow 中执行此操作,并且您还可以通过实现prepareForReuse 在单元格本身中获得机会,这在单元格出列之前被调用。

【讨论】:

【参考方案3】:

在您的tableView(_: cellForRowAt:) 中为按钮单击采取行动并添加目标

cell.button.addTarget(self, action: #selector(self.selectedButton), for: .touchUpInside)

在目标方法使用下面的行来获取indexPath

func selectedButton(sender: UIButton)
       let hitPoint: CGPoint = sender.convert(CGPoint.zero, to: self.tableView)
       let indexPath: NSIndexPath = self.tableView.indexPathForRow(at: hitPoint)! as NSIndexPath

然后使用 indexPath 做你的事情。 实际上,您的方法无法找到单击了哪个 indexPath 按钮,这就是为什么无法使用所需按钮的原因。

【讨论】:

【参考方案4】:

解决问题的一种方法如下

1. First create a protocol to know when button is clicked

protocol MyCellDelegate: class 
    func cellButtonClicked(_ indexPath: IndexPath)

2. Now in your cell class, you could do something like following

class MyCell: UITableViewCell 

var indexPath: IndexPath?
weak var cellButtonDelegate: MyCellDelegate?


func configureCell(with value: String, atIndexPath indexPath: IndexPath, selected: [IndexPath]) 
        self.indexPath = indexPath  //set the indexPath
        self.textLabel?.text = value
        if selected.contains(indexPath) 
            //this one is selected so do the stuff
            //here we will chnage only the background color
            backgroundColor = .red
            self.textLabel?.textColor = .white

         else 
            //unselected
            backgroundColor = .white
            self.textLabel?.textColor = .red
        


    @IBAction func buttonClicked(_ sender: UIButton) 
        guard let delegate = cellButtonDelegate, let indexPath = indexPath else  return 
        delegate.cellButtonClicked(indexPath)
    

3. Now in your controller. I'm using UItableViewController here

class TheTableViewController: UITableViewController, MyCellDelegate 

    let cellData = ["cell1","cell2","cell3","cell4","cell2","cell3","cell4","cell2","cell3","cell4","cell2","cell3","cell4","cell2","cell3","cell4","cell2","cell3","cell4"]
    var selectedIndexpaths = [IndexPath]()


    override func viewDidLoad() 
        super.viewDidLoad()
        tableView.register(MyCell.self, forCellReuseIdentifier: "MyCell")
    


    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return cellData.count
    

    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat 
        return 55.0
    

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! MyCell
        cell.cellButtonDelegate = self
        cell.configureCell(with: cellData[indexPath.row], atIndexPath: indexPath, selected: selectedIndexpaths)
        return cell
    

    override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat 
        return 30.0
    

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
        let cell = tableView.cellForRow(at: indexPath) as! MyCell
        cell.buttonClicked(UIButton())
    

    func cellButtonClicked(_ indexPath: IndexPath) 
        if selectedIndexpaths.contains(indexPath) 
            //this means cell has already selected state
            //now we will toggle the state here from selected to unselected by removing indexPath
            if let index = selectedIndexpaths.index(of: indexPath) 
                selectedIndexpaths.remove(at: index)
            

         else 
            //this is new selection so add it
            selectedIndexpaths.append(indexPath)
        

        tableView.reloadData()
    

【讨论】:

以上是关于UITableView 自定义单元格按钮从主视图中选择其他按钮?的主要内容,如果未能解决你的问题,请参考以下文章

无法从自定义表格视图单元格按钮中删除行

iPhone:如何从视图控制器中获取自定义单元格的按钮操作?

UItableview 在自定义单元格上使用带有提示的警报视图重新加载数据的问题

使用自定义单元格反转 UITableView

在没有自定义单元格的情况下向 uitableview 显示详细信息披露按钮

iOS - 使 UITableView 与自定义单元格在按钮单击时可编辑