在tableview中检测uibutton:Swift Best Practices

时间:2014-12-11 18:12:47

标签: ios uitableview swift uibutton

我有一个tableview,其中包含可变数量的单元格,表示与其特定教师相对应的学生。它们是具有按钮的自定义单元格,该按钮触发新VC的segue,提供有关其单元格的学生的详细信息。我的问题是:

swift中用于识别按下哪个按钮的最佳做法是什么?

一旦我知道索引路径,我就可以确定哪个学生的信息需要传递给下一个VC。在下面的帖子中,对于目标C有一个很好的答案,但我不确定如何转换为Swift。任何帮助将不胜感激。

Detecting which UIButton was pressed in a UITableView

13 个答案:

答案 0 :(得分:79)

如果您的代码允许,我建议您将UIButton标记设置为等于indexPath.row,因此当触发其操作时,您可以拉出标记,从而排出按钮数据在触发方法期间。例如,在cellForRowAtIndexPath中,您可以设置标记:

button.tag = indexPath.row
button.addTarget(self, action: "buttonClicked:", forControlEvents: UIControlEvents.TouchUpInside)

然后在buttonClicked:中,您可以获取标记,从而获取行:

func buttonClicked(sender:UIButton) {

    let buttonRow = sender.tag
}

否则,如果由于某种原因这不利于您的代码,则this Objective-C answer you linked to的Swift翻译:

- (void)checkButtonTapped:(id)sender
{
    CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
    if (indexPath != nil)
    {
     ...
    }
}

是:

func checkButtonTapped(sender:AnyObject) {
      let buttonPosition = sender.convert(CGPoint.zero, to: self.tableView)
    let indexPath = self.tableView.indexPathForRow(at: buttonPosition)
    if indexPath != nil {
        ...
    }
}

答案 1 :(得分:14)

Swift 3.0解决方案

window

答案 2 :(得分:6)

针对Swift 3进行了更新

如果你想要做的唯一事情就是在触摸时触发一个segue,那么通过UIButton这样做是违反最佳做法的。您可以简单地使用UIKit的内置处理程序来选择单元格,即func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)。你可以通过以下方式实现它:

创建自定义UITableViewCell

class StudentCell: UITableViewCell {
    // Declare properties you need for a student in a custom cell.
    var student: SuperSpecialStudentObject!

    // Other code here...
}

加载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
}

然后使用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)
    }
}

最后在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
        }
    }
}

或者,如果您希望它们仅选择单元格的一部分而不是它们触摸单元格内的任何位置,您可以将附件项目添加到单元格中,例如细节附件项目(看起来像带有“i”的圆圈“在其内部)并使用override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath)代替。

答案 3 :(得分:2)

另一种可能的解决方案是使用dispatch_block_t。如果您使用Storyboard执行此操作,则首先必须在自定义UITableViewCell类中创建成员变量。

var tapBlock: dispatch_block_t?

然后您必须创建IBAction并致电tapBlock

@IBAction func didTouchButton(sender: AnyObject) {
    if let tapBlock = self.tapBlock {
        tapBlock()
    }
}

在具有UITableView的视图控制器中,您只需对此按钮事件做出反应

let cell = tableView.dequeueReusableCellWithIdentifier("YourCellIdentifier", forIndexPath: indexPath) as! YourCustomTableViewCell

cell.tapBlock = {
   println("Button tapped")
}

但是,在访问块内的self时必须注意,不要创建保留周期。请务必以[weak self]

的形式访问它

答案 4 :(得分:2)

Swift 3

@ cellForRowAt indexPath

<div class="section-block">

<section data-field="box-image">
    <img
        src=""
        width="160" height="160" alt="">
</section>
<section data-field="box-content">
    <h3>Aquarius</h3>
    <p>20 January&nbsp;- 18 February</p>

</section>
<section data-field="box-image">
    <img
        src=""
        width="160" height="160" alt="">
</section>
<section data-field="box-content">
    <h3>Pisces</h3>
    <p>19 February&nbsp;- 20 March</p>
</section>

</div>

然后

cell.Btn.addTarget(self, action: #selector(self.BtnAction(_:)), for: .touchUpInside)

答案 5 :(得分:1)

使用标签来识别单元格和索引路径绝不是一个好主意,最终你会得到一个错误的indexPath,从而导致错误的单元格和信息。

我建议你尝试下面的代码(使用UICollectionView,没有用TableView测试它,但它可能会正常工作):

SWIFT 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

        }
    }
}

答案 6 :(得分:1)

在单击按钮时为 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")
        }

    }
  

这是UITableView单元格方法,用于添加目标

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
    }

答案 7 :(得分:0)

我是通过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
    }

在下一个控制器上我将重新加载完整的项目属性,通过知道它的id并将其设置为DetailVC中的var thisItem

答案 8 :(得分:0)

作为对@Lyndsey和@bowbow评论的后续跟进,我注意到当我在故事板中将segue从按钮转到destinationVC时,在buttonClicked函数可以更新urlPath之前调用prepareForSegue变量。为了解决这个问题,我将segue直接从第一个VC设置为destinationVC,并在执行了buttonClicked中的代码后以编程方式执行了segue。也许不理想,但似乎有效。

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
    }

答案 9 :(得分:0)

我打算使用indexPath方法,直到我明白在某些情况下它会不可靠/错误(例如删除或移动的单元格)。

我做的更简单。例如,我正在显示一系列颜色及其RGB值 - 每个tableview单元一个。每种颜色都以颜色结构数组定义。为清楚起见,这些是:

struct ColorStruct {
    var colorname:String    = ""
    var red:   Int = 0
    var green: Int = 0
    var blue:  Int = 0
}

var colors:[ColorStruct] = []       // The color 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)")
    }
}

此解决方案需要三行代码,一行在原型单元格定义中,第二行在填充新单元格的逻辑中,第三行在 IBAction函数中按下任何单元格按钮时调用的。 因为我已经有效地将“密钥”(索引)隐藏到每个单元格中的数据AS我正在填充新单元格,所以不需要计算 - 并且 - 如果移动单元格则不需要更新任何内容。

答案 10 :(得分:0)

我发现通过使用Model类,可以非常简单方便地在tableView和collectionView中管理任何单元格,这是一项完美的工作。

现在确实有更好的方法来处理此问题。这将适用于管理单元格和价值

here is my output(screenshote) so see this

这是我的代码

  1. 创建模型分类非常简单,请按照以下步骤操作。 创建名称为“ RNCheckedModel”的swift类,编写如下代码。

RNCheckedModel类:NSObject {

var is_check = false
var user_name = ""

}
  1. 创建您的单元格类

InviteCell类:UITableViewCell {

@IBOutlet var imgProfileImage: UIImageView!
@IBOutlet var btnCheck: UIButton!
@IBOutlet var lblName: UILabel!
@IBOutlet var lblEmail: UILabel!
}
    当您使用 UITableView 时,
  1. 并最终在 UIViewController 中使用模型类。

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()

}

 }

答案 11 :(得分:0)

快速5。在cellForRowAtIndexPath中,设置标签:

cell.shareButton.tag = indexPath.row
cell.shareButton.addTarget(self, action: #selector(shareBtnPressed(_:)), for: .touchUpInside)

然后在shareBtnPressed中获取标签

  @IBAction func shareBtnPressed(_ sender: UIButton) {

    let buttonRow = sender.tag

    print("Video Shared in row \(buttonRow)")
}

答案 12 :(得分:0)

已针对Swift 5更新:

在您的ViewController类中放置以下代码

@IBAction func buttonClicked(_ sender: UIButton) {
    if let tableView = tableView {
        let point = tableView.convert(sender.center, from: sender.superview!)

//can call wantedIndexPath.row here

        }
    }
}