当一个元素被激活时,如何获得 indexpath.row?

我有一个带按钮的表格视图,当其中一个按钮被点击时,我想使用 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)
}
155511 次浏览

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

但如果再多做一点工作,我会用完全不同的方式来做。如果您使用的是自定义单元格,下面是我处理这个问题的方法:

  • 将“ indexPath”属性添加到自定义表单元格中
  • cellForRowAtIndexPath中初始化它
  • move the tap handler from the view controller to the cell implementation
  • 使用委托模式通知视图控制器关于点击事件,传递索引路径

Giorashc 的回答差点就成功了,但他忽略了一个事实,细胞有一个额外的 contentView层。因此,我们必须深入一层:

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


let indexPath = itemTable.indexPath(for: cell)

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

The above is one such approach, but not necessarily the best approach. Whilst it is functional, it assumes details about a UITableViewCell that Apple have never necessarily documented, such as it's view hierarchy. This could be changed in the future, and the above code may well behave unpredictably as a result.

由于上述原因,出于寿命和可靠性的原因,我建议采用另一种方法。这个帖子列出了很多选择,我鼓励你读下去,但我个人最喜欢的是:

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

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参数。与上面列出的方法相比,这是一种更安全的方法,因为它不依赖于未记录的特征。

我使用 ConvertPoint 方法从 tableview 获取点,并将该点传递给 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);
}
}
}

For Swift2.1

我找到办法了,希望能有帮助。

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


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

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

注意 : 有时候,当使用函数 view.convert(CGPointZero, to:self.tableView)导致在某一点为一行查找 nil时,您可能会遇到边缘情况,即使那里有一个 tableView 单元格。要解决这个问题,请尝试传递一个与原点略有偏移的实际坐标,例如:

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

上一个答案: 使用 Tag 属性 (只返回行)

与其爬进 Superview 树去抓取一个指向 UIButton 单元格的指针,不如使用一种更安全、更可重复的技术,利用上面 Antonio 提到的 button.tag 属性,如 这个答案所述,如下所示:

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,我过去使用过 这种技巧,并且对结果感到满意。

我解决这类问题的方法是在计算单元和表视图之间使用委托协议。这允许你将按钮处理程序保留在 cell 子类中,这使得你可以将触摸动作处理程序分配给原型单元 Interface Builder,同时将按钮处理程序逻辑保留在 view 控制器中。

它还避免了导航视图层次结构或使用 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)")
}
}

After seeing Paulw11's suggestion of using a delegate callback, I wanted to elaborate on it slightly/put forward another, similar suggestion. Should you not want to use the delegate pattern you can utilise closures in swift as follows:

你的牢房:

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.
}

在 Swift 3中,也使用了守卫语句,避免了一长串花括号。

func buttonTapped(sender: UIButton) {
guard let cellInAction = sender.superview as? UITableViewCell else { return }
guard let indexPath = tableView?.indexPath(for: cellInAction) else { return }


print(indexPath)
}

Use an extension to UITableView to fetch the cell that contains any view:


@ Paulw11设置一个自定义单元格类型,该类型带有一个委托属性,可以将消息发送到表视图,这是一个很好的方法,但是需要进行一定的设置工作。

我认为遍历表视图单元格的视图层次结构寻找单元格是一个坏主意。它是脆弱的——如果您稍后出于布局目的将按钮包含在视图中,那么该代码很可能会中断。

使用视图标记也很脆弱。在创建单元格时,必须记住设置标记,如果在视图控制器中使用这种方法,而视图控制器将视图标记用于其他用途,则可能会出现重复的标记号,代码可能无法按预期工作。

I have created an extension to UITableView that lets you get the indexPath for any view that is contained in a table view cell. It returns an Optional that will be nil if the view passed in actually does not fall within a table view cell. Below is the extension source file in it's entirety. You can simply put this file in your project and then use the included indexPathForView(_:) method to find the indexPath that contains any view.

//
//  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: Git下载使用上述扩展的工作演示项目

有时按钮可能位于 UITableViewCell 的另一个视图内。在这种情况下,superview.superview 可能不会给出单元格对象,因此 indexPath 将为 nil。

在这种情况下,我们应该继续寻找超视图,直到我们得到单元格对象。

函数通过 Superview 获取单元格对象

func getCellForView(view:UIView) -> UITableViewCell?
{
var superView = view.superview


while superView != nil
{
if superView is UITableViewCell
{
return superView as? UITableViewCell
}
else
{
superView = superView?.superview
}
}


return nil
}

现在我们可以得到按钮点击下面的索引路径

@IBAction func tapButton(_ sender: UIButton)
{
let cell = getCellForView(view: sender)
let indexPath = myTabelView.indexPath(for: cell)
}

解决方案:

单元格中有一个按钮(myButton)或任何其他视图

cell.myButton.tag = indexPath.row

现在在你的 tapFunction 或者其他任何程序中,像这样取出它并保存到一个局部变量中。

currentCellNumber = (sender.view?.tag)!

之后,您可以在任何地方使用 currentCellNumber 来获取所选按钮的 indexPath.row。

好好享受吧!

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

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

通过这种方式,您可以访问 edButtonPress 方法中的 indexpath

func editButtonPressed(_ sender: UIButton) {


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


}

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

In my case i have multiple sections and both the section and row index is vital, so in such a case i just created a property on UIButton which i set the cell indexPath like so:

fileprivate struct AssociatedKeys {
static var index = 0
}


extension UIButton {


var indexPath: IndexPath? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.index) as? IndexPath
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.index, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}

Then set the property in cellForRowAt like this :

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! Cell
cell.button.indexPath = indexPath
}

然后在 handleTapAction 中你可以得到像下面这样的 indexPath:

@objc func handleTapAction(_ sender: UIButton) {
self.selectedIndex = sender.indexPath


}

斯威夫特4号和5号

Method 1 using Protocol delegate

例如,您有一个名为 MyCellUITableViewCell

class MyCell: UITableViewCell {
    

var delegate:MyCellDelegate!
    

@IBAction private func myAction(_ sender: UIButton){
delegate.didPressButton(cell: self)
}
}

现在创建一个 protocol

protocol MyCellDelegate {
func didPressButton(cell: UITableViewCell)
}

下一步,创建 UITableView的扩展

extension UITableView {
func returnIndexPath(cell: UITableViewCell) -> IndexPath?{
guard let indexPath = self.indexPath(for: cell) else {
return nil
}
return indexPath
}
}

UIViewController中实现协议 MyCellDelegate

class ViewController: UIViewController, MyCellDelegate {
     

func didPressButton(cell: UITableViewCell) {
if let indexpath = self.myTableView.returnIndexPath(cell: cell) {
print(indexpath)
}
}
}

使用闭包的方法2

UIViewController

override func viewDidLoad() {
super.viewDidLoad()
//using the same `UITableView extension` get the IndexPath here
didPressButton = { cell in
if let indexpath = self.myTableView.returnIndexPath(cell: cell) {
print(indexpath)
}
}
}
 var didPressButton: ((UITableViewCell) -> Void)


class MyCell: UITableViewCell {


@IBAction private func myAction(_ sender: UIButton){
didPressButton(self)
}
}

注意:-如果你想得到 UICollectionView的 indexPath,你可以使用这个 UICollectionView extension并重复上面的步骤

extension UICollectionView {
func returnIndexPath(cell: UICollectionViewCell) -> IndexPath?{
guard let indexPath = self.indexPath(for: cell) else {
return nil
}
return indexPath
}
}

非常简单得到指数路径快速4,5

let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! Cell
cell.btn.tag = indexPath.row
cell.btn.addTarget(self, action: "buttonTapped:", forControlEvents:
UIControlEvents.TouchUpInside)

How to get IndexPath Inside Btn 点击:

func buttonTapped(_ sender: UIButton) {
print(sender.tag)
}
// CustomCell.swift


protocol CustomCellDelegate {
func tapDeleteButton(at cell: CustomCell)
}


class CustomCell: UICollectionViewCell {
    

var delegate: CustomCellDelegate?
    

fileprivate let deleteButton: UIButton = {
let button = UIButton(frame: .zero)
button.setImage(UIImage(named: "delete"), for: .normal)
button.addTarget(self, action: #selector(deleteButtonTapped(_:)), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
    

@objc fileprivate func deleteButtonTapped(_sender: UIButton) {
delegate?.tapDeleteButton(at: self)
}
    

}


//  ViewController.swift


extension ViewController: UICollectionViewDataSource {


func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: customCellIdentifier, for: indexPath) as? CustomCell else {
fatalError("Unexpected cell instead of CustomCell")
}
cell.delegate = self
return cell
}


}


extension ViewController: CustomCellDelegate {


func tapDeleteButton(at cell: CustomCell) {
// Here we get the indexPath of the cell what we tapped on.
let indexPath = collectionView.indexPath(for: cell)
}


}

对行和节使用单一标记

There is a simple way to use tags for transmitting the row/item AND the section of a TableView/CollectionView at the same time.

CellForRowAtIndexPath 中为您的 UIView.tag 编码 IndexPath:

buttonForCell.tag = convertIndexPathToTag(with: indexPath)

解码 目标选择器中发件人的 IndexPath:

    @IBAction func touchUpInsideButton(sender: UIButton, forEvent event: UIEvent) {


var indexPathForButton = convertTagToIndexPath(from: sender)


}

编码器 和 < em > < strong > 译码器:

func convertIndexPathToTag(indexPath: IndexPath) -> Int {
var tag: Int = indexPath.row + (1_000_000 * indexPath.section)
    

return tag
}


func convertTagToIndexPath(from sender: UIButton) -> IndexPath {
var section: Int = Int((Float(sender.tag) / 1_000_000).rounded(.down))
var row: Int = sender.tag - (1_000_000 * section)


return IndexPath(row: row, section: section)
}

前提是你在一个32位设备上不需要超过4294967296行/项; ——例如。

  • 42949 sections with 100_000 items/rows
  • 具有1 _ 000 _ 000条目/行-(就像上面的例子)的4294个节
  • 有10 _ 000 _ 000个条目/行的429个部分

ーー

请记住,当在 TableView/CollectionView 中删除或插入行/项时,必须在插入/删除点之后重新加载所有行/项,以保持按钮的标签编号与模型同步。

ーー

Extend UITableView to create function that get indexpath for a view:

extension UITableView {
func indexPath(for view: UIView) -> IndexPath? {
self.indexPathForRow(at: view.convert(.zero, to: self))
}
}

使用方法:

let row = tableView.indexPath(for: sender)?.row

看来我来晚了点,但我带来了一些有趣的代码。

如何处理单元格中的按钮点击

为了处理按钮在 UITableViewCell或其子类,我肯定会建议 delegation模式,这是涵盖以上有一些 关注点分离cellviewController

如何查找单元格的 indexPath

但是,如果出于其他原因,当一个按钮或任何其他 UIView 子类被点击时,你需要找到单元格的 indexPath,我建议使用 类扩展。这样你可以实现 Interface SegregationSOLIDify你的代码一点点。

其他解决方案的问题:

  • 标签: 如上所示,当您插入或删除一行时,它们是脆弱的

  • 使用 superView 属性: 无论如何都不整洁。要到达 cell本身或包含 tableView的视图,应该传递多少层视图。你可能会在你的代码中出现这样的东西,这并不漂亮:

        let tableView = view.superView.superView.superView.superView
    

我的建议是:

首先

UIResponder上创建一个扩展,以获得视图层次结构中类型为 Tview的第一个 superView

extension UIResponder {
func next<T: UIResponder>(_ type: T.Type) -> T? {
self.next as? T ?? self.next?.next(type)
}
}

这将迭代整个视图层次结构,直到找到给定类型的视图或层次结构的末尾,在这个末尾它将返回 nil。

下一个

UITableViewCell上写一个扩展,并使用 next方法找到细胞所属的 tableView和细胞的 indexPath

extension UITableViewCell {
var tableView: UITableView? {
return next(UITableView.self)
}


var indexPath: IndexPath? {
return tableView?.indexPathForRow(at: self.center)
//return tableView?.indexPath(for: self) // Note: This will return nil if the cell is not visible yet
}
}

就是这样,简单明了。

Use it wherever you want like this.

func buttonTapped(_ sender: UIButton) {
guard let cell = sender.next(YourCellType.self), let indexPath = cell.indexPath else {
return
}
    

// Use indexPath here
}