swift:轻按单元格中的按钮时如何获取indexpath.row?

问题描述 投票:87回答:15

我有一个带有按钮的表格视图,当我点击其中一个按钮时,我想使用indexpath.row。这是我目前拥有的,但始终为0

var point = Int()
func buttonPressed(sender: AnyObject) {
    let pointInTable: CGPoint =         sender.convertPoint(sender.bounds.origin, toView: self.tableView)
    let cellIndexPath = self.tableView.indexPathForRowAtPoint(pointInTable)
    println(cellIndexPath)
    point = cellIndexPath!.row
    println(point)
}
swift button touch nsindexpath
15个回答
139
投票

giorashc几乎得到了答案,但是他忽略了细胞具有额外的contentView层的事实。因此,我们必须更深入一层:

guard let cell = sender.superview?.superview as? YourCellClassHere else {
    return // or fatalError() or whatever
}

let indexPath = itemTable.indexPath(for: cell)

这是因为tableView在视图层次结构中将单元格作为子视图,这些子视图随后具有自己的“内容视图”,这就是为什么必须获取该内容视图的超级视图才能获取单元格本身的原因。因此,如果您的按钮包含在子视图中,而不是直接包含在单元格的内容视图中,则必须更深入地访问它。

以上是一种这样的方法,但不一定是最好的方法。虽然功能正常,但它假设Apple从未记录过的UITableViewCell的详细信息,例如视图层次结构。将来可能会对此进行更改,因此,上面的代码可能会表现出意外的结果。

由于上述原因,出于寿命和可靠性的原因,我建议采用另一种方法。此线程中列出了许多替代方法,我鼓励您阅读以下内容,但我个人最喜欢的方法如下:

在单元格类上保留闭包的属性,让按钮的action方法调用它。

class MyCell: UITableViewCell {
    var button: UIButton!

    var buttonAction: ((Any) -> Void)?

    @objc func buttonPressed(sender: Any) {
        self.buttonAction?(sender)
    }
}

然后,当您在cellForRowAtIndexPath中创建单元格时,可以为闭包分配一个值。

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! MyCell
    cell.buttonAction = { sender in
        // Do whatever you want from your button here.
    }
    // OR
    cell.buttonAction = buttonPressed(closure: buttonAction, indexPath: indexPath) // <- Method on the view controller to handle button presses.
}

通过在此处移动处理程序代码,您可以利用已经存在的indexPath参数。与上面列出的方法相比,这是一种更为安全的方法,因为它不依赖于未记录的特征。


1
投票

我使用convertPoint方法从表视图获取点,并将此点传递给indexPathForRowAtPoint方法以获取indexPath

 @IBAction func newsButtonAction(sender: UIButton) {
        let buttonPosition = sender.convertPoint(CGPointZero, toView: self.newsTableView)
        let indexPath = self.newsTableView.indexPathForRowAtPoint(buttonPosition)
        if indexPath != nil {
            if indexPath?.row == 1{
                self.performSegueWithIdentifier("alertViewController", sender: self);
            }   
        }
    }

1
投票

[尝试使用#selector调用IBaction。在cellforrowatindexpath中

            cell.editButton.tag = indexPath.row
        cell.editButton.addTarget(self, action: #selector(editButtonPressed), for: .touchUpInside)

这样您可以在方法editButtonPressed中访问索引路径

func editButtonPressed(_ sender: UIButton) {

print(sender.tag)//this value will be same as indexpath.row

}

1
投票

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

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

here is my output(screenshote) so see this

这里是我的代码

  1. 创建模型分类]非常简单,请按照以下步骤操作。创建名为“ RNCheckedModel”的swift类,编写如下代码。
  2. 类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!
}
  1. 并且在使用UITableView
时最终使用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()

}

 }

0
投票

在Swift 3中。还使用了警卫声明,避免了长括号。


0
投票

有时按钮可能在UITableViewCell的另一个视图中。在那种情况下,superview.superview可能不会提供单元格对象,因此indexPath将为nil。


0
投票

在我的情况下,我有多个节,因此节和行索引都是至关重要的,因此在这种情况下,我只是在UIButton上创建了一个属性,该属性像这样设置单元格indexPath:


53
投票

我解决此类问题的方法是在单元格和表格视图之间使用委托协议。这使您可以将按钮处理程序保留在单元格子类中,从而使您可以将补习动作处理程序分配给Interface Builder中的原型单元格,同时仍将按钮处理程序逻辑保留在视图控制器中。

它还避免了导航视图层次结构或使用tag属性的潜在脆弱方法,当单元格索引发生更改时(插入,删除或重新排序的结果),该属性会出现问题]

CellSubclass.swift

protocol CellSubclassDelegate: class {
    func buttonTapped(cell: CellSubclass)
}

class CellSubclass: UITableViewCell {

@IBOutlet var someButton: UIButton!

weak var delegate: CellSubclassDelegate?

override func prepareForReuse() {
    super.prepareForReuse()
    self.delegate = nil
}

@IBAction func someButtonTapped(sender: UIButton) {
    self.delegate?.buttonTapped(self)
}

ViewController.swift

class MyViewController: UIViewController, CellSubclassDelegate {

    @IBOutlet var tableview: UITableView!

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! CellSubclass

        cell.delegate = self

        // Other cell setup

    } 

    //  MARK: CellSubclassDelegate

    func buttonTapped(cell: CellSubclass) {
        guard let indexPath = self.tableView.indexPathForCell(cell) else {
            // Note, this shouldn't happen - how did the user tap on a button that wasn't on screen?
            return
        }

        //  Do whatever you need to do with the indexPath

        print("Button tapped on row \(indexPath.row)")
    }
} 

48
投票

UPDATE:获取包含按钮(节和行)的单元格的indexPath:

使用按钮位置

buttonTapped方法内部,您可以获取按钮的位置,将其转​​换为tableView中的坐标,然后在该坐标处获取行的indexPath。

func buttonTapped(_ sender:AnyObject) {
    let buttonPosition:CGPoint = sender.convert(CGPoint.zero, to:self.tableView)
    let indexPath = self.tableView.indexPathForRow(at: buttonPosition)
}

NOTE:有时,当使用函数view.convert(CGPointZero, to:self.tableView)导致在某个点上为一行找到nil时,即使在其中存在tableView单元格,有时也会遇到边缘情况。要解决此问题,请尝试传递一个与原点稍微偏移的实坐标,例如:

let buttonPosition:CGPoint = sender.convert(CGPoint.init(x: 5.0, y: 5.0), to:self.tableView)

上一个答案:使用标签属性(仅返回行)

而不是爬入超级视图树以获取指向持有UIButton的单元格的指针,而是利用一种更安全,更可重复的技术,该技术利用了Antonio上面提到的button.tag属性,在this answer中进行了描述,如下所示:

cellForRowAtIndexPath:中设置标签属性:

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

然后,在buttonClicked:函数中,引用该标记以获取按钮所在的indexPath的行:

func buttonClicked(sender:UIButton) {
    let buttonRow = sender.tag
}

我更喜欢这种方法,因为我发现在超级视图树中摇摆可能是设计应用程序的一种冒险方式。另外,对于Objective-C,我过去使用过this technique,并对结果感到满意。


13
投票

使用UITableView的扩展来获取任何视图的单元格:


@ Paulw11关于使用委托属性设置自定义单元格类型的答案,该属性将消息发送到表视图是一个不错的方法,但是需要进行大量工作才能进行设置。

[我认为遍历表视图单元格的视图层次结构以查找该单元格不是一个好主意。它很脆弱-如果以后出于布局目的将按钮括在视图中,则该代码很可能会中断。

使用视图标签也很脆弱。您必须记住在创建单元时要设置标签,如果您在将视图标签用于其他目的的视图控制器中使用该方法,则可能会有重复的标签编号,并且代码可能无法按预期工作。

我已经创建了UITableView的扩展,该扩展使您可以获取表视图单元格中包含的任何视图的indexPath。如果传入的视图实际上不在表视图单元格之内,它将返回Optional,该值为零。以下是整个扩展源文件。您可以简单地将此文件放入项目中,然后使用随附的indexPathForView(_:)方法查找包含任何视图的indexPath。

//
//  UITableView+indexPathForView.swift
//  TableViewExtension
//
//  Created by Duncan Champney on 12/23/16.
//  Copyright © 2016-2017 Duncan Champney.
//  May be used freely in for any purpose as long as this 
//  copyright notice is included.

import UIKit

public extension UITableView {

  /**
  This method returns the indexPath of the cell that contains the specified view

   - Parameter view: The view to find.

   - Returns: The indexPath of the cell containing the view, or nil if it can't be found

  */

    func indexPathForView(_ view: UIView) -> IndexPath? {
        let center = view.center
        let viewCenter = self.convert(center, from: view.superview)
        let indexPath = self.indexPathForRow(at: viewCenter)
        return indexPath
    }
}

要使用它,您只需在IBAction中为单元格中包含的按钮调用方法:

func buttonTapped(_ button: UIButton) {
  if let indexPath = self.tableView.indexPathForView(button) {
    print("Button tapped at indexPath \(indexPath)")
  }
  else {
    print("Button indexPath not found")
  }
}

((indexPathForView(_:)函数仅在传递的视图对象包含在当前屏幕上的单元格中时才起作用。这是合理的,因为非屏幕上的视图实际上并不属于特定视图indexPath;当包含单元格的回收单元被回收时,可能会将其分配给其他indexPath。)

编辑:

您可以从Github下载使用上述扩展名的有效演示项目:TableViewExtension.git


9
投票

对于Swift2.1

[我找到了一种方法,希望会有所帮助。

let point = tableView.convertPoint(CGPoint.zero, fromView: sender)

    guard let indexPath = tableView.indexPathForRowAtPoint(point) else {
        fatalError("can't find point in tableView")
    }

9
投票

Swift 4解决方案:

您在单元格中有一个按钮(myButton)或任何其他视图。像这样在cellForRowAt中分配标签

cell.myButton.tag = indexPath.row

现在您可以点击功能或任何其他功能。像这样获取它,并将其保存在本地变量中。

currentCellNumber = (sender.view?.tag)!

此后,您可以在任何位置使用此currentCellNumber来获取所选按钮的indexPath.row。

享受!


5
投票

在Swift 4中,只需使用此:

func buttonTapped(_ sender: UIButton) {
        let buttonPostion = sender.convert(sender.bounds.origin, to: tableView)

        if let indexPath = tableView.indexPathForRow(at: buttonPostion) {
            let rowIndex =  indexPath.row
        }
}

3
投票

由于事件处理程序的发送者是按钮本身,因此我将使用按钮的tag属性存储在cellForRowAtIndexPath中初始化的索引。

但是,如果再多做一点,我会以完全不同的方式来做。如果您使用的是自定义单元格,这就是我要解决的问题:

  • 向定制表单元格添加'indexPath`属性
  • cellForRowAtIndexPath中初始化
  • 将轻击处理程序从视图控制器移动到单元实现
  • 使用委托模式来通知视图控制器有关轻击事件的信息,并传递索引路径

2
投票

[看到Paulw11关于使用委托回调的建议后,我想对此进行详细说明/提出另一个类似的建议。如果您不想使用委托模式,可以按如下所示快速使用闭包:

您的单元格类别:

class Cell: UITableViewCell {
    @IBOutlet var button: UIButton!

    var buttonAction: ((sender: AnyObject) -> Void)?

    @IBAction func buttonPressed(sender: AnyObject) {
        self.buttonAction?(sender)
    }
}

您的cellForRowAtIndexPath方法:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! Cell
    cell.buttonAction = { (sender) in
        // Do whatever you want from your button here.
    }
    // OR
    cell.buttonAction = buttonPressed // <- Method on the view controller to handle button presses.
}
© www.soinside.com 2019 - 2024. All rights reserved.