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 在自定义单元格上使用带有提示的警报视图重新加载数据的问题