[英]detecting uibutton pressed in tableview: Swift Best Practices
I have a tableview with a variable number of cells representing students that correspond to their particular instructor.我有一个表格视图,其中包含可变数量的单元格,代表与其特定教师相对应的学生。 They are custom cells with a button that triggers a segue to a new VC, bringing up detailed information on the student whose cell it was.它们是自定义单元格,带有一个按钮,可触发新 VC 的转场,显示该单元格所在学生的详细信息。 My question is:我的问题是:
What is the best practice in swift for identifying which button was pressed?快速识别按下哪个按钮的最佳实践是什么?
Once i know the index path, I can identify which student's information needs to be passed to the next VC.一旦我知道索引路径,我就可以确定哪些学生的信息需要传递给下一个 VC。 There is a great answer for objective C in the post below, but I'm not sure how to translate to Swift.在下面的帖子中对目标 C 有一个很好的答案,但我不确定如何转换为 Swift。 Any help would be much appreciated.任何帮助将非常感激。
Detecting which UIButton was pressed in a UITableView 检测在 UITableView 中按下了哪个 UIButton
If your code allows, I'd recommend you set the UIButton
tag equal to the indexPath.row
, so when its action is triggered, you can pull the tag and thus row out of the button data during the triggered method.如果您的代码允许,我建议您将UIButton
标记设置为等于indexPath.row
,这样当它的动作被触发时,您可以在触发方法期间拉出该标记并因此从按钮数据中取出。 For example, in cellForRowAtIndexPath
you can set the tag:例如,在cellForRowAtIndexPath
您可以设置标签:
button.tag = indexPath.row
button.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)
then in buttonClicked:
, you can fetch the tag and thus the row:然后在buttonClicked:
,您可以获取标签,从而获取行:
func buttonClicked(sender:UIButton) {
let buttonRow = sender.tag
}
Otherwise, if that isn't conducive to your code for some reason, the Swift translation of this Objective-C answer you linked to :否则,如果由于某种原因这不利于您的代码,那么您链接到的这个 Objective-C 答案的 Swift 翻译:
- (void)checkButtonTapped:(id)sender
{
CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
if (indexPath != nil)
{
...
}
}
is:是:
func checkButtonTapped(sender:AnyObject) {
let buttonPosition = sender.convert(CGPoint.zero, to: self.tableView)
let indexPath = self.tableView.indexPathForRow(at: buttonPosition)
if indexPath != nil {
...
}
}
Swift 3.0 Solution Swift 3.0 解决方案
cell.btnRequest.tag = indexPath.row
cell.btnRequest.addTarget(self,action:#selector(buttonClicked(sender:)), for: .touchUpInside)
func buttonClicked(sender:UIButton) {
let buttonRow = sender.tag
}
Updated for Swift 3为 Swift 3 更新
If the only thing you want to do is trigger a segue on a touch, it would be against best practice to do so via a UIButton.如果您只想在触摸时触发转场,那么通过 UIButton 执行此操作将违反最佳实践。 You can simply use UIKit's built in handler for selecting a cell, ie func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
.您可以简单地使用 UIKit 的内置处理程序来选择单元格,即func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
。 You could implement it doing something like the following:您可以执行以下操作来实现它:
Create a custom UITableViewCell创建自定义 UITableViewCell
class StudentCell: UITableViewCell {
// Declare properties you need for a student in a custom cell.
var student: SuperSpecialStudentObject!
// Other code here...
}
When you load your UITableView, pass the data into the cell from you data model:加载 UITableView 时,将数据从数据模型传递到单元格中:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "StudentCell", for: indexPath) as! StudentCell
cell.student = superSpecialDataSource[indexPath.row]
return cell
}
Then use didSelectRow atIndexPath
to detect when a cell has been selected, access the cell and it's data, and pass the value in as a parameter to performSegue
.然后使用didSelectRow atIndexPath
检测何时选择了单元格,访问单元格及其数据,并将值作为参数传递给performSegue
。
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! StudentCell
if let dataToSend = cell.student {
performSegue(withIdentifier: "DestinationView", sender: dataToSend)
}
}
And finally in prepareForSegue
:最后在prepareForSegue
:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "DestinationView" {
let destination = segue.destination as! DestinationViewController
if let dataToSend = sender as? SuperSpecialStudentObject {
destination.student = dataToSend
}
}
}
Alternatively if you want them to only select a part of the cell instead of when they touch anywhere inside the cell, you could add an accessory item onto your cell such as the detail accessory item (looks like the circle with an "i" inside of it) and use override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath)
instead.或者,如果您希望他们只选择单元格的一部分,而不是当他们接触单元格内的任何地方时,您可以在单元格中添加一个附件项目,例如细节附件项目(看起来像里面带有“i”的圆圈it) 并使用override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath)
代替。
Another possible solution would be using dispatch_block_t
.另一种可能的解决方案是使用dispatch_block_t
。 If you do it with Storyboard you first have to create a member variable in your custom UITableViewCell
class.如果你使用 Storyboard,你首先必须在你的自定义UITableViewCell
类中创建一个成员变量。
var tapBlock: dispatch_block_t?
Then you have to create an IBAction
and call the tapBlock
.然后你必须创建一个IBAction
并调用tapBlock
。
@IBAction func didTouchButton(sender: AnyObject) {
if let tapBlock = self.tapBlock {
tapBlock()
}
}
In your view controller with the UITableView
you can simply react to the button events like this在带有UITableView
视图控制器中,您可以简单地对这样的按钮事件做出反应
let cell = tableView.dequeueReusableCellWithIdentifier("YourCellIdentifier", forIndexPath: indexPath) as! YourCustomTableViewCell
cell.tapBlock = {
println("Button tapped")
}
However you have to be aware when accessing self
inside the block, to not create a retain cycle.但是,在块内访问self
时必须注意,不要创建保留循环。 Be sure to access it as [weak self]
.请务必以[weak self]
身份访问它。
Swift 3斯威夫特 3
@ cellForRowAt indexPath @cellForRowAt indexPath
cell.Btn.addTarget(self, action: #selector(self.BtnAction(_:)), for: .touchUpInside)
Then然后
func BtnAction(_ sender: Any)
{
let btn = sender as? UIButton
}
It's never a good idea to use tags to identify cells and indexPaths, eventually you'll end up with a wrong indexPath and consequently the wrong cell and information.使用标签来标识单元格和 indexPaths 从来都不是一个好主意,最终你会得到一个错误的 indexPath 并因此得到错误的单元格和信息。
I suggest you try the code bellow (Working with UICollectionView, didn't tested it with a TableView, but it probably will work just fine):我建议你试试下面的代码(使用 UICollectionView,没有用 TableView 测试它,但它可能会工作得很好):
SWIFT 4快速 4
@objc func buttonClicked(_ sender: UIButton) {
if let tableView = tableViewNameObj {
let point = tableView.convert(sender.center, from: sender.superview!)
if let wantedIndexPath = tableView.indexPathForItem(at: point) {
let cell = tableView.cellForItem(at: wantedIndexPath) as! SpecificTableViewCell
}
}
}
Detecting the Section and row for UiTableView indexPath on click Button click单击按钮单击时检测UiTableView indexPath 的部分和行
//MARK:- Buttom Action Method
@objc func checkUncheckList(_sender:UIButton)
{
if self.arrayRequestList != nil
{
let strSection = sender.title(for: .disabled)
let dict = self.arrayRequestList![Int(strSection!)!]["record"][sender.tag]
print("dict:\(dict)")
self.requestAcceptORReject(dict: dict, strAcceptorReject: "1")
}
}
Here is UITableView Cell Method to add the targate这里是 UITableView Cell 方法来添加 targate
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "OtherPropertySelectiingCell", for: indexPath as IndexPath) as! OtherPropertySelectiingCell
cell.btnAccept.tag = indexPath.row
cell.btnAccept.setTitle("\(indexPath.section)", for: .disabled)
cell.btnAccept.addTarget(self, action: #selector(checkUncheckList(_sender:)), for: .touchUpInside)
return cell
}
Swift 5. In cellForRowAtIndexPath you set the tag: Swift 5. 在 cellForRowAtIndexPath 中设置标签:
cell.shareButton.tag = indexPath.row
cell.shareButton.addTarget(self, action: #selector(shareBtnPressed(_:)), for: .touchUpInside)
Then in shareBtnPressed you fetch the tag然后在 shareBtnPressed 中获取标签
@IBAction func shareBtnPressed(_ sender: UIButton) {
let buttonRow = sender.tag
print("Video Shared in row \(buttonRow)")
}
As a follow up to @Lyndsey and @longbow's comments, I noticed that when I had the segue in storyboard going from the button to the destinationVC, the prepareForSegue was being called before the buttonClicked function could update the urlPath variable.作为@Lyndsey 和@longbow 评论的后续,我注意到当故事板中的segue 从按钮到destinationVC 时,在buttonClicked 函数可以更新urlPath 变量之前调用prepareForSegue。 To resolve this, I set the segue directly from the first VC to the destinationVC, and had the segue performed programmatically after the code in buttonClicked was executed.为了解决这个问题,我将 segue 直接从第一个 VC 设置到 destinationVC,并在执行 buttonClicked 中的代码后以编程方式执行 segue。 Maybe not ideal, but seems to be working.也许不理想,但似乎正在起作用。
func buttonClicked(sender:UIButton) {
let studentDic = tableData[sender.tag] as NSDictionary
let studentIDforTherapyInt = studentDic["ID"] as Int
studentIDforTherapy = String(studentIDforTherapyInt)
urlPath = "BaseURL..."+studentIDforTherapy
self.performSegueWithIdentifier("selectTherapySegue", sender: sender)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "selectTherapySegue") {
let svc = segue.destinationViewController as SelectTherapyViewController;
svc.urlPath = urlPath
}
Updated for Swift 5:为 Swift 5 更新:
Place the following code within your ViewController class将以下代码放在您的 ViewController 类中
@IBAction func buttonClicked(_ sender: UIButton) {
if let tableView = tableView {
let point = tableView.convert(sender.center, from: sender.superview!)
//can call wantedIndexPath.row here
}
}
}
I am doing it via prepareforSegue我是通过 prepareforSegue 做的
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let indexPath = self.tableView.indexPathForSelectedRow()
let item = tableViewCollection[indexPath!.row].id
let controller = segue.destinationViewController as? DetailVC
controller?.thisItem = item
}
and on the next controller i will just reload the full item properties, by knowing its id and setting it to the var thisItem in the DetailVC在下一个控制器上,我将通过知道它的 id 并将其设置为 DetailVC 中的 var thisItem 来重新加载完整的项目属性
I was going to use the indexPath approach until I came to understand that it would be unreliable/wrong in some situations (deleted or moved cell, for instance).我将使用 indexPath 方法,直到我明白它在某些情况下不可靠/错误(例如,删除或移动单元格)。
What I did is simpler.我做的更简单。 By example, I am displaying a series of colors and their RGB values—one per tableview cell.例如,我正在显示一系列颜色及其 RGB 值——每个 tableview 单元格一个。 Each color is defined in an array of color structures.每种颜色都在颜色结构数组中定义。 For clarity these are:为清楚起见,这些是:
struct ColorStruct {
var colorname:String = ""
var red: Int = 0
var green: Int = 0
var blue: Int = 0
}
var colors:[ColorStruct] = [] // The color array
My prototype cell has a var to hold the actual index/key into my array:我的原型单元格有一个 var 来将实际索引/键保存到我的数组中:
class allListsCell: UITableViewCell {
@IBOutlet var cellColorView: UIView!
@IBOutlet var cellColorname: UILabel!
var colorIndex = Int() // ---> points directly back to colors[]
@IBAction func colorEditButton(_ sender: UIButton, forEvent event: UIEvent) {
print("colorEditButton: colors[] index:\(self.colorIndex), \(colors[self.colorIndex].colorname)")
}
}
This solution takes three lines of code, one in the prototype cell definition , the second in the logic that populates a new cell, and the the third in the IBAction function which is called when any cell's button is pressed.这个解决方案需要三行代码,一行在原型单元格定义中,第二行在填充新单元格的逻辑中,第三行在 IBAction 函数中,当任何单元格的按钮被按下时调用。 Because I have effectively hidden the "key" (index) to the data in each cell AS I am populating that new cell, there is no calculation required -and- if you move cells there is no need to update anything.因为我在填充新单元格时有效地隐藏了每个单元格中数据的“键”(索引),所以不需要计算 - 而且 - 如果您移动单元格,则无需更新任何内容。
I found a very easy and saficiat way to use for manage any cell in tableView and collectionView by using a Model class and this a work as perfectly.我找到了一种非常简单和 saficiat 的方法来使用 Model 类来管理 tableView 和 collectionView 中的任何单元格,这是一个完美的工作。
There is indeed a much better way to handle this now.现在确实有更好的方法来处理这个问题。 This will work for manage cell and value这将适用于管理单元格和值
here is my output(screenshote) so see this这是我的输出(截图)所以看看这个
here is my code这是我的代码
class RNCheckedModel: NSObject {类 RNCheckedModel: NSObject {
var is_check = false
var user_name = ""
}
class InviteCell: UITableViewCell {类邀请单元: UITableViewCell {
@IBOutlet var imgProfileImage: UIImageView!
@IBOutlet var btnCheck: UIButton!
@IBOutlet var lblName: UILabel!
@IBOutlet var lblEmail: UILabel!
}
class RNInviteVC: UIViewController, UITableViewDelegate, UITableViewDataSource { RNInviteVC 类:UIViewController、UITableViewDelegate、UITableViewDataSource {
@IBOutlet var inviteTableView: UITableView!
@IBOutlet var btnInvite: UIButton!
var checkArray : NSMutableArray = NSMutableArray()
var userName : NSMutableArray = NSMutableArray()
override func viewDidLoad() {
super.viewDidLoad()
btnInvite.layer.borderWidth = 1.5
btnInvite.layer.cornerRadius = btnInvite.frame.height / 2
btnInvite.layer.borderColor = hexColor(hex: "#512DA8").cgColor
var userName1 =["Olivia","Amelia","Emily","Isla","Ava","Lily","Sophia","Ella","Jessica","Mia","Grace","Evie","Sophie","Poppy","Isabella","Charlotte","Freya","Ruby","Daisy","Alice"]
self.userName.removeAllObjects()
for items in userName1 {
print(items)
let model = RNCheckedModel()
model.user_name = items
model.is_check = false
self.userName.add(model)
}
}
@IBAction func btnInviteClick(_ sender: Any) {
}
func tableView(_ tableView: UITableView, numberOfRowsInSection
section: Int) -> Int {
return userName.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: InviteCell = inviteTableView.dequeueReusableCell(withIdentifier: "InviteCell", for: indexPath) as! InviteCell
let image = UIImage(named: "ic_unchecked")
cell.imgProfileImage.layer.borderWidth = 1.0
cell.imgProfileImage.layer.masksToBounds = false
cell.imgProfileImage.layer.borderColor = UIColor.white.cgColor
cell.imgProfileImage.layer.cornerRadius = cell.imgProfileImage.frame.size.width / 2
cell.imgProfileImage.clipsToBounds = true
let model = self.userName[indexPath.row] as! RNCheckedModel
cell.lblName.text = model.user_name
if (model.is_check) {
cell.btnCheck.setImage(UIImage(named: "ic_checked"), for: UIControlState.normal)
}
else {
cell.btnCheck.setImage(UIImage(named: "ic_unchecked"), for: UIControlState.normal)
}
cell.btnCheck.tag = indexPath.row
cell.btnCheck.addTarget(self, action: #selector(self.btnCheck(_:)), for: .touchUpInside)
cell.btnCheck.isUserInteractionEnabled = true
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
@objc func btnCheck(_ sender: UIButton) {
let tag = sender.tag
let indexPath = IndexPath(row: tag, section: 0)
let cell: InviteCell = inviteTableView.dequeueReusableCell(withIdentifier: "InviteCell", for: indexPath) as! InviteCell
let model = self.userName[indexPath.row] as! RNCheckedModel
if (model.is_check) {
model.is_check = false
cell.btnCheck.setImage(UIImage(named: "ic_unchecked"), for: UIControlState.normal)
checkArray.remove(model.user_name)
if checkArray.count > 0 {
btnInvite.setTitle("Invite (\(checkArray.count))", for: .normal)
print(checkArray.count)
UIView.performWithoutAnimation {
self.view.layoutIfNeeded()
}
} else {
btnInvite.setTitle("Invite", for: .normal)
UIView.performWithoutAnimation {
self.view.layoutIfNeeded()
}
}
}else {
model.is_check = true
cell.btnCheck.setImage(UIImage(named: "ic_checked"), for: UIControlState.normal)
checkArray.add(model.user_name)
if checkArray.count > 0 {
btnInvite.setTitle("Invite (\(checkArray.count))", for: .normal)
UIView.performWithoutAnimation {
self.view.layoutIfNeeded()
}
} else {
btnInvite.setTitle("Invite", for: .normal)
}
}
self.inviteTableView.reloadData()
}
func hexColor(hex:String) -> UIColor {
var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
if (cString.hasPrefix("#")) {
cString.remove(at: cString.startIndex)
}
if ((cString.count) != 6) {
return UIColor.gray
}
var rgbValue:UInt32 = 0
Scanner(string: cString).scanHexInt32(&rgbValue)
return UIColor(
red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
alpha: CGFloat(1.0)
)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.