【问题标题】:How to get the indexpath.row when an element is activated?激活元素时如何获取 indexpath.row?
【发布时间】:2015-04-23 22:53:37
【问题描述】:

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

【问题讨论】:

  • 我应该使用 IndexPathForSelectedRow() 代替点变量吗?或者我应该在哪里使用它?

标签: ios swift iphone uitableview


【解决方案1】:

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

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

  • 向自定义表格单元格添加“indexPath”属性
  • cellForRowAtIndexPath中初始化它
  • 将点击处理程序从视图控制器移动到单元实现
  • 使用委托模式通知视图控制器点击事件,传递索引路径

【讨论】:

  • Antonio,我有一个自定义单元格,我很乐意按照您的方式进行操作。但是,它不起作用。我希望我的“滑动以显示删除按钮”代码运行,即 tableView commitEditingStyle 方法。我从 mainVC 类中删除了该代码并将其放入 customCell 类中,但现在该代码不再有效。我错过了什么?
  • 我认为这是获取具有 x 部分的单元格的 indexPath 的最佳方法,但是我认为在 MVC 方法中不需要要点 3 和 4
【解决方案2】:

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 的详细信息,例如它的视图层次结构。这可能会在未来发生变化,因此上述代码的行为可能会出现不可预测的结果。

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

在你的单元格类中保存一个闭包的属性,让按钮的动作方法调用它。

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

【讨论】:

  • 很好看。我保证,我是一个称职的开发人员;) - 修改了我的答案。
  • 这不是从按钮获取单元格的正确方法。多年来,单元格的布局发生了变化,当这种情况发生时,这样的代码将无法工作。不要使用这种方法。
  • 这是一个糟糕的解决方案。它假设了 Apple 从未同意过的有关 UITableViewCells 的详细信息。虽然 UITableViewCells 具有 contentView 属性,但不能保证 contentView 的超级视图始终是 Cell。
  • @PintuRajput 您能向我描述一下您的视图层次结构吗?您可能会看到这种情况,因为您的按钮不是单元格内容视图的直接子视图。
  • @ymutlu 我完全同意,我确实在答案中说明了这一点。我还提出了一个更强大的解决方案。我保留原件的原因是因为我觉得用一种方法向其他开发人员展示问题比完全躲避它更好,这不会教给他们任何你看到的东西。 :)
【解决方案3】:

我使用 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);
            }   
        }
    }

【讨论】:

    【解决方案4】:

    对于Swift2.1

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

    let point = tableView.convertPoint(CGPoint.zero, fromView: sender)
    
        guard let indexPath = tableView.indexPathForRowAtPoint(point) else {
            fatalError("can't find point in tableView")
        }
    

    【讨论】:

    • 如果错误运行是什么意思?在tableView中找不到点是什么原因?
    • 这(或类似的,使用 UIView 转换方法)应该是公认的答案。不知道为什么它目前是#4,因为它没有对表格视图的私有层次结构做出假设,它不使用标签属性(几乎总是一个坏主意),并且不涉及很多额外的代码。
    【解决方案5】:

    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)
    

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

    与其爬进超级视图树来获取指向包含 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,并且对结果感到满意。

    【讨论】:

    • 这是一个很好的方法,我会赞成它让你的代表开始一点,但唯一的缺陷是它不能访问 indexPath.section 如果这也是需要。不过答案很好!
    • 感谢雅各布!我很欣赏代表业力。如果您想在indexPath.row 之外获得indexPath.section(不将标签属性重置为indexPath.section),在cellForRowAtIndexPath: 中您只需将标签更改为button.tag = indexPath,然后在buttonClicked:您可以使用sender.tag.rowsender.tag.section 访问这两个函数。
    • 这是一个新功能吗,因为我确定我记得标签属性是 Int 类型而不是 AnyObject 类型,除非在 swift 2.3 中更改?
    • @JacobKing 你是对的!糟糕的是,我在写该评论时完全隔开,并认为该标签是 AnyObject 类型。 Derp - 别介意我。如果您可以将 indexPath 作为标签传递,那将会很有用......
    • 也不是一个好方法。一方面,它只适用于只有一个部分的表格视图。
    【解决方案6】:

    我解决这类问题的方法是在单元格和表格视图之间使用委托协议。这允许您将按钮处理程序保留在单元子类中,从而使您能够将修饰操作处理程序分配给 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)")
        }
    } 
    

    【讨论】:

    • buttonTapped 是委托函数,位于视图控制器中。在我的示例中,someButtonTapped 是单元格中的操作方法
    • @paulw11 我得到单元格没有成员按钮在此方法中被点击@IBAction func someButtonTapped(sender: UIButton) { self.delegate?.buttonTapped(self) }
    • 这是一个相当不错的解决方案(远不及目前拥有更多投票的两个,使用查看超级视图的标签),但感觉需要添加太多额外代码。
    • 这是正确的解决方案,应该是公认的答案。它不会滥用标签属性,不会假设单元格的构造(Apple 可以轻松更改)并且在移动单元格或在现有单元格之间添加新单元格时仍然可以工作(无需额外编码)。
    • @Paulw11 我最初认为这是很多代码,但事实证明它比我以前使用的更有弹性。感谢您发布这个强大的解决方案。
    【解决方案7】:

    在看到 Paulw11 关于使用委托回调的建议后,我想稍微详细说明一下/提出另一个类似的建议。如果您不想使用委托模式,您可以在 swift 中使用闭包,如下所示:

    你的细胞类别:

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

    【讨论】:

      【解决方案8】:

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

      【讨论】:

      • 这行不通。按钮的超级视图将不是单元格。
      • 这确实有效。您唯一应该注意的是每个人的视图堆栈都是不同的。它可以是 sender.superview、sender.superview.superview 或 sender.superview.superview.superview。但是效果真的很好。
      【解决方案9】:

      使用 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

      【讨论】:

      • 感谢我使用扩展来获取单元格中 textview 的 indexPath - 完美运行。
      • 工作就像一个魅力!
      【解决方案10】:

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

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

      通过superview获取cell对象的功能

      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
      }
      

      现在我们可以在点击按钮时获得 indexPath,如下所示

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

      【讨论】:

        【解决方案11】:

        解决方案:

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

        cell.myButton.tag = indexPath.row
        

        现在在您的 tapFunction 或任何其他功能中。像这样取出它并保存在一个局部变量中。

        currentCellNumber = (sender.view?.tag)!
        

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

        享受吧!

        【讨论】:

        • 这种方法有效,但视图标签很脆弱,正如我在回答中提到的那样。例如,简单的整数标记不适用于分段表视图。 (一个 IndexPath 它是 2 个整数。)我的方法将始终有效,并且无需在按钮(或其他可点击视图)中安装标签。
        • 您可以轻松地将部分和行/项目存储在一个整数中。看我的回答...
        【解决方案12】:

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

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

        这样你就可以访问方法editButtonPressed中的indexpath

        func editButtonPressed(_ sender: UIButton) {
        
        print(sender.tag)//this value will be same as indexpath.row
        
        }
        

        【讨论】:

        • 最合适的答案
        • 不,当用户添加或删除单元格时,标签将关闭。
        • @koen:如果在插入或删除后重新加载 tableView 则不会;-)
        【解决方案13】:

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

        【讨论】:

        • 最干净的答案,应该被选中。唯一需要注意的是,tableView 是一个出口变量,在此答案生效之前需要先引用它。
        • 像魅力一样工作!!
        【解决方案14】:

        我找到了一种非常简单的方法,可以通过使用 Model 类来管理 tableView 和 collectionView 中的任何单元格,而且效果非常好。

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

        这是我的输出(屏幕截图),请看:

        1. 创建模型类非常简单,请按照以下步骤操作。 创建名为RNCheckedModel的swift类,代码如下。
        class RNCheckedModel: NSObject {
        
            var is_check = false
            var user_name = ""
        
            }
        
        1. 创建您的单元类
        class InviteCell: UITableViewCell {
        
            @IBOutlet var imgProfileImage: UIImageView!
            @IBOutlet var btnCheck: UIButton!
            @IBOutlet var lblName: UILabel!
            @IBOutlet var lblEmail: UILabel!
            }
        
        1. 最后在您使用 UITableView 时在 UIViewController 中使用模型类。
            class 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()
        
            }
        
             }
        

        【讨论】:

        • 要走的路。谢谢...重点是将@objc func btnCheck(_ sender: UIButton) 设置为单元格内按钮的addTarget 并相应地设置按钮标签。
        【解决方案15】:

        在我的情况下,我有多个部分,并且部分和行索引都很重要,所以在这种情况下,我只是在 UIButton 上创建了一个属性,我将单元格 indexPath 设置为:

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

        然后像这样在 cellForRowAt 中设置属性:

        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
        
        }
        

        【讨论】:

          【解决方案16】:

          Swift 4 和 5

          使用协议委托的方法 1

          例如,您有一个名为 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
              }
          }
          

          【讨论】:

            【解决方案17】:

            快速获取索引路径非常简单 4、5

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

            如何在 Btn Click 中获取 IndexPath:

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

            【讨论】:

              【解决方案18】:
              // 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)
                  }
              
              }
              

              【讨论】:

                【解决方案19】:

                对行和部分使用单个标记

                有一种简单的方法可以使用标签同时传输行/项目和 TableView/CollectionView 的部分。

                编码 cellForRowAtIndexPath 中的 UIView.tag 的 IndexPath

                buttonForCell.tag = convertIndexPathToTag(with: indexPath)
                

                解码目标选择器中来自您的发件人的IndexPath

                    @IBAction func touchUpInsideButton(sender: UIButton, forEvent event: UIEvent) {
                
                        var indexPathForButton = convertTagToIndexPath(from: sender)
                
                    }
                

                编码器解码器:

                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 个部分,每行 100_000 个项目
                • 4294 个部分,每行 1_000_000 个项目 - (如上例中
                • 429 个部分,每行 10_000_000 个项目

                ——

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

                ——

                【讨论】:

                  【解决方案20】:

                  扩展 UITableView 以创建获取视图索引路径的函数:

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

                  使用方法:

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

                  【讨论】:

                    【解决方案21】:

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

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

                    要处理 UITableViewCell 或其子类中的按钮点击,我肯定会建议上面介绍的 delegation 模式有一些 cellviewController 的关注点分离

                    如何查找单元格的 indexPath

                    但是,如果由于某些其他原因您需要在点击按钮或其中的任何其他 UIView 子类时找到单元格的 indexPath,我建议使用 类扩展。这样您就可以稍微实现Interface SegregationSOLIDify您的代码。

                    其他解决方案的问题:

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

                    • 使用 superView 属性: 无论如何它都不整洁。您应该通过多少层视图才能到达cell 本身或包含tableView。您最终可能会在代码中出现这样的不漂亮的东西:

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

                    我的建议:

                    首先

                    UIResponder 上创建一个扩展,以获取视图层次结构中 view 类型为 T 的第一个 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
                        }
                    }  
                    

                    就是这样。简洁明了。

                    像这样在任何你想要的地方使用它。

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

                    【讨论】:

                    • 这符合我的需要。谢谢大佬。
                    猜你喜欢
                    • 2012-11-17
                    • 1970-01-01
                    • 2022-11-20
                    • 2017-03-19
                    • 2016-12-20
                    • 2020-03-27
                    • 2019-05-03
                    • 1970-01-01
                    相关资源
                    最近更新 更多