Swift: как получить indexpath.row при нажатии кнопки в ячейке?
У меня есть tableview с кнопками, и я хочу использовать 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)
}
Ответы
Ответ 1
У giorashc почти все было в ответе, но он упустил из виду тот факт, что в ячейке есть дополнительный слой contentView
. Таким образом, мы должны пойти на один уровень глубже:
guard let cell = sender.superview?.superview as? YourCellClassHere else {
return // or fatalError() or whatever
}
let indexPath = itemTable.indexPath(for: cell)
Это связано с тем, что в иерархии представлений tableView имеет ячейки в качестве подпредставлений, которые впоследствии имеют свои собственные "представления содержимого", поэтому вам необходимо получить суперпредставление этого представления содержимого, чтобы получить саму ячейку. В результате этого, если ваша кнопка содержится в подпредставлении, а не непосредственно в представлении содержимого ячейки, вам придется пройти как можно больше слоев, чтобы получить к ней доступ.
Выше приведен один такой подход, но не обязательно лучший. Несмотря на то, что он функционален, он предполагает подробности о UITableViewCell
которые Apple никогда не обязательно документировала, такие как просмотр иерархии. Это может быть изменено в будущем, и в результате вышеприведенный код может вести себя непредсказуемо.
В связи с вышеизложенным по причинам долговечности и надежности я рекомендую использовать другой подход. В этой теме перечислено много альтернатив, и я рекомендую вам прочитать их, но мой личный фаворит таков:
Держите свойство замыкания в вашем классе ячеек, сделайте так, чтобы метод действия кнопки вызывал это.
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
. Это гораздо более безопасный подход, чем приведенный выше, поскольку он не основан на недокументированных чертах.
Ответ 2
Мой подход к решению проблемы такого рода заключается в использовании протокола делегата между ячейкой и табличным представлением. Это позволяет вам сохранить обработчик кнопки в подклассе ячейки, что позволяет назначить обработчик действия подстройки для ячейки-прототипа в Интерфейсном Разработчике, сохраняя при этом логику обработчика кнопки в контроллере представления.
Это также позволяет избежать потенциально хрупкого подхода к навигации по иерархии представлений или использования свойства 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)")
}
}
Ответ 3
ОБНОВЛЕНИЕ: Получение 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)
случаем, когда использование функции 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, есть более безопасный и более повторяемый метод, использующий свойство 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
}
Я предпочитаю этот метод, так как обнаружил, что колебание в деревьях суперпредставления может быть рискованным способом разработки приложения. Кроме того, для цели C я использовал эту технику в прошлом и был доволен результатом.
Ответ 4
Используйте расширение для UITableView, чтобы получить ячейку для любого представления:
@Paulw11 ответ на вопрос о настройке пользовательского типа ячейки со свойством делегата, которое отправляет сообщения в табличное представление, - это хороший способ, но для его настройки требуется определенное количество работы.
Я думаю, что обход иерархии представления ячейки табличного представления в поисках ячейки - плохая идея. Это хрупко - если вы позже вложите свою кнопку в представление для макета, этот код, скорее всего, сломается.
Использование тегов просмотра также хрупко. Вы должны помнить, чтобы устанавливать теги при создании ячейки, и если вы используете этот подход в контроллере представления, который использует теги представления для другой цели, у вас могут быть повторяющиеся номера тегов, и ваш код может не работать должным образом.
Я создал расширение для UITableView, которое позволяет вам получить indexPath для любого представления, которое содержится в ячейке табличного представления. Он возвращает Optional
который будет иметь значение nil, если переданное представление фактически не попадает в ячейку табличного представления. Ниже приведено полное расширение исходного файла. Вы можете просто поместить этот файл в свой проект и затем использовать включенный 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
Ответ 5
Для Swift2.1
Я нашел способ сделать это, надеюсь, это поможет.
let point = tableView.convertPoint(CGPoint.zero, fromView: sender)
guard let indexPath = tableView.indexPathForRowAtPoint(point) else {
fatalError("can't find point in tableView")
}
Ответ 6
В 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
}
}
Ответ 7
Решение Swift 4:
У вас есть кнопка (myButton) или любой другой вид в ячейке. Назначить тег в cellForRowAt, как это
cell.myButton.tag = indexPath.row
Теперь у вас tapFunction или любой другой. Выполните его так и сохраните в локальной переменной.
currentCellNumber = (sender.view?.tag)!
После этого вы можете использовать в любом месте этот currentCellNumber, чтобы получить indexPath.row выбранной кнопки.
Наслаждайтесь!
Ответ 8
Так как отправитель обработчика события является самой кнопкой, я бы использовал свойство tag
кнопки для хранения индекса, инициализированного в cellForRowAtIndexPath
.
Но с немного большей работой я бы сделал совершенно по-другому. Если вы используете пользовательскую ячейку, я как-то подхожу к проблеме:
- добавить свойство 'indexPath` в пользовательскую ячейку таблицы
- инициализировать его в
cellForRowAtIndexPath
- переместите обработчик крана с контроллера представления на реализацию ячейки.
- используйте шаблон делегирования, чтобы уведомить диспетчер представлений о событии крана, передав путь указателя
Ответ 9
Увидев предложение Paulew11 об использовании обратного вызова делегата, я хотел немного уточнить его/предложить другое, подобное предложение. Если вы не хотите использовать шаблон делегата, вы можете быстро использовать блокировки следующим образом:
Класс вашей ячейки:
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.
}
Ответ 10
Попробуйте использовать #selector для вызова IBaction.In 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
}
Ответ 11
Я использовал метод 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);
}
}
}
Ответ 12
В 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)
}
Ответ 13
Иногда кнопка может находиться внутри другого представления UITableViewCell. В этом случае superiew.superview не может дать объект ячейки, и поэтому indexPath будет равен нулю.
В этом случае мы должны продолжать поиск супервизора, пока не получим объект ячейки.
Функция для получения объекта ячейки с помощью супервизора
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)
}
Ответ 14
Я нашел очень простой и удобный способ управления любой ячейкой в tableView и collectionView с помощью класса Model, и это прекрасно работает.
Существует действительно лучший способ справиться с этим сейчас. Это будет работать для управления ячейкой и значением
вот мой вывод (скриншот), так что смотрите это
вот мой код
- Создать класс модели очень просто, следуйте приведенной ниже процедуре. Создайте swift класс с именем "RNCheckedModel", напишите код, как показано ниже.
class RNCheckedModel: NSObject {
var is_check = false
var user_name = ""
}
- создать свой класс клетки
class InviteCell: UITableViewCell {
@IBOutlet var imgProfileImage: UIImageView!
@IBOutlet var btnCheck: UIButton!
@IBOutlet var lblName: UILabel!
@IBOutlet var lblEmail: UILabel!
}
- и, наконец, используйте класс модели в вашем UIViewController, когда вы используете свой UITableView.
класс 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()
}
}