简体   繁体   English

检测在 tableview 中按下的 uibutton:Swift 最佳实践

[英]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这是我的代码

  1. Its very simple to create model clas , please follow the below procedure.创建模型类非常简单,请按照以下步骤操作。 Create swift class with name "RNCheckedModel", write the code as below.创建名为“RNCheckedModel”的swift类,编写如下代码。

class RNCheckedModel: NSObject {类 RNCheckedModel: NSObject {

var is_check = false
var user_name = ""

}
  1. create your cell class创建你的细胞类

class InviteCell: UITableViewCell {类邀请单元: UITableViewCell {

@IBOutlet var imgProfileImage: UIImageView!
@IBOutlet var btnCheck: UIButton!
@IBOutlet var lblName: UILabel!
@IBOutlet var lblEmail: UILabel!
}
  1. and finaly use model class in your UIViewController when you use your UITableView .最后在使用UITableView时在UIViewController 中使用模型类。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM