提问人:Vincent 提问时间:2/22/2015 最后编辑:Vincent 更新时间:2/15/2022 访问量:161513
当一个元素被激活时,如何获取indexpath.row?
How to get the indexpath.row when an element is activated?
问:
我有一个带有按钮的表视图,我想在点击其中一个按钮时使用 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)
}
答:
由于事件处理程序的发送方是按钮本身,因此我将使用按钮的属性来存储索引,并在 中初始化。tag
cellForRowAtIndexPath
但是,如果再多做一些工作,我会以完全不同的方式去做。如果您使用的是自定义单元格,这就是我解决问题的方式:
- 将“indexPath”属性添加到自定义表单元格
- 在
cellForRowAtIndexPath
- 将 Tap 处理程序从 View Controller 移动到单元实现
- 使用委派模式通知视图控制器有关 Tap 事件的信息,并传递索引路径
评论
Giorasc的回答几乎做到了,但他忽略了一个事实,即Cell有一个额外的层。因此,我们必须更深入一层: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
评论
我使用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);
}
}
}
为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:
使用按钮位置
在方法中,可以获取按钮的位置,将其转换为 tableView 中的坐标,然后获取该坐标处行的 indexPath。buttonTapped
func buttonTapped(_ sender:AnyObject) {
let buttonPosition:CGPoint = sender.convert(CGPoint.zero, to:self.tableView)
let indexPath = self.tableView.indexPathForRow(at: buttonPosition)
}
注意:有时,当使用该函数导致在某个点查找行时,即使那里有一个 tableView 单元格,您也会遇到边缘情况。要解决此问题,请尝试传递一个与原点略有偏移的实际坐标,例如:view.convert(CGPointZero, to:self.tableView)
nil
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)
然后,在函数中,引用该标记来获取按钮所在的 indexPath 行:buttonClicked:
func buttonClicked(sender:UIButton) {
let buttonRow = sender.tag
}
我更喜欢这种方法,因为我发现在超视图树中摆动可能是设计应用程序的一种冒险方式。此外,对于objective-C,我过去使用过这种技术,并且对结果感到满意。
评论
indexPath.section
indexPath.row
indexPath.section
cellForRowAtIndexPath:
button.tag = indexPath
buttonClicked:
sender.tag.row
sender.tag.section
我解决此类问题的方法是在单元格和表视图之间使用委托协议。这允许您将按钮处理程序保留在单元子类中,从而使您能够将修饰操作处理程序分配给 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)
}
视图控制器.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
@IBAction func someButtonTapped(sender: UIButton) { self.delegate?.buttonTapped(self) }
在看到 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.
}
在 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)
}
评论
使用 UITableView 的扩展来提取包含任何视图的单元格:
@Paulw11 的答案是设置具有将消息发送到表视图的委托属性的自定义单元格类型,这是一个很好的方法,但它需要一定的工作量来设置。
我认为遍历表视图单元格的视图层次结构以查找单元格是一个坏主意。它很脆弱 - 如果以后出于布局目的将按钮包含在视图中,则该代码可能会中断。
使用视图标签也很脆弱。您必须记住在创建单元格时设置标记,如果在将视图标记用于其他目的的视图控制器中使用该方法,则可能会有重复的标记号,并且代码可能无法按预期工作。
我创建了一个 UITableView 扩展,它允许您获取表视图单元格中包含的任何视图的 indexPath。如果传入的视图实际上不在表视图单元格内,则返回一个 null。下面是完整的扩展源文件。只需将此文件放入项目中,然后使用包含的方法查找包含任何视图的 indexPath。Optional
indexPathForView(_:)
//
// 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")
}
}
(请注意,仅当它传递的视图对象包含在当前屏幕上的单元格中时,该函数才起作用。这是合理的,因为不在屏幕上的视图实际上并不属于特定的 indexPath;当它包含单元格被回收时,它可能会被分配给不同的 indexPath。indexPathForView(_:)
编辑:
您可以从 Github 下载使用上述扩展的工作演示项目:TableViewExtension.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
}
现在我们可以在按钮点击上获取 indexPath,如下所示
@IBAction func tapButton(_ sender: UIButton)
{
let cell = getCellForView(view: sender)
let indexPath = myTabelView.indexPath(for: cell)
}
溶液:
单元格中有一个按钮 (myButton) 或任何其他视图。像这样在 cellForRowAt 中分配标签
cell.myButton.tag = indexPath.row
现在,在tapFunction或任何其他方法中。像这样取出它并将其保存在局部变量中。
currentCellNumber = (sender.view?.tag)!
在此之后,您可以在任何位置使用此 currentCellNumber 来获取所选按钮的 indexPath.row。
享受!
评论
尝试使用 #selector 调用 cellforrowatindexpath IBaction.In
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
}
评论
在 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
就我而言,我有多个部分,并且部分和行索引都至关重要,因此在这种情况下,我只是在 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
}
Swift 4 和 5
方法 1:使用协议委托
例如,您有一个 with 名称UITableViewCell
MyCell
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)
}
}
注意:-如果你想获取indexPath,你可以使用它并重复上述步骤
UICollectionView
UICollectionView extension
extension UICollectionView {
func returnIndexPath(cell: UICollectionViewCell) -> IndexPath?{
guard let indexPath = self.indexPath(for: cell) else {
return nil
}
return indexPath
}
}
非常简单地获取索引路径 swift 4, 5
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! Cell
cell.btn.tag = indexPath.row
cell.btn.addTarget(self, action: "buttonTapped:", forControlEvents:
UIControlEvents.TouchUpInside)
如何在 Btn 中获取 IndexPath 点击:
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)
}
}
对行和部分使用单个标记
有一种简单的方法可以使用标记来同时传输 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 中删除或插入行/项时,必须在插入/删除点之后重新加载所有行/项,以使按钮的标记号与模型保持同步。
—-
扩展 UITableView 以创建获取视图索引路径的函数:
extension UITableView {
func indexPath(for view: UIView) -> IndexPath? {
self.indexPathForRow(at: view.convert(.zero, to: self))
}
}
如何使用:
let row = tableView.indexPath(for: sender)?.row
看来我来晚了一点,但我带来了一些有趣的代码。
如何处理单元格中的按钮点击
为了处理 UITableViewCell
或其子类中的按钮点击,我肯定会建议使用上面介绍的委托
模式,以便对 和 .cell
viewController
如何查找单元格的索引路径
但是,如果由于其他一些原因,您需要在点击按钮或其中的任何其他 UIView 子类时找到单元格的 indexPath
,我建议使用类扩展。这样,您可以稍微实现接口隔离和 SOLIDify 您的代码。
其他解决方案的问题:
标签:如上所述,当您插入或删除行时,它们很脆弱
使用 superView 属性:它无论如何都不整洁。您应该传递多少层视图才能到达本身或包含 .你最终可能会在你的代码中出现这样的东西,这并不漂亮:
cell
tableView
let tableView = view.superView.superView.superView.superView
我的建议:
第一
创建一个扩展,以获取视图层次结构中的第一个类型。UIResponder
superView
view
T
extension UIResponder {
func next<T: UIResponder>(_ type: T.Type) -> T? {
self.next as? T ?? self.next?.next(type)
}
}
这将迭代整个视图层次结构,直到它找到给定类型的视图或它将返回 nil 的层次结构的末尾。
下一个
在 UITableViewCell
上编写扩展,并使用 next
方法查找单元格所属的 tableView 和单元格的 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
}
评论