swift: come ottenere indexpath.row quando si tocca un pulsante in una cella?

Ho una vista tabella con i pulsanti e voglio usare indexpath.row quando uno di essi viene toccato. Questo è ciò che attualmente ho, ma è sempre 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) } 

giorashc quasi ce l’ha con la sua risposta, ma ha trascurato il fatto che le celle hanno un livello contentView extra. Quindi, dobbiamo fare uno strato più profondo:

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

Questo perché all’interno della gerarchia della vista una tabellaView ha celle come visualizzazioni secondarie che successivamente hanno le loro ‘viste dei contenuti’. Ecco perché è necessario ottenere la superview di questa vista del contenuto per ottenere la cella stessa. Di conseguenza, se il tuo pulsante è contenuto in una sottoview anziché direttamente nella visualizzazione del contenuto della cella, dovrai accedere a molti livelli più in profondità per accedervi.

Quanto sopra è uno di questi approcci, ma non necessariamente l’approccio migliore. Sebbene sia funzionale, assume i dettagli di un UITableViewCell che Apple non ha mai necessariamente documentato, come la sua gerarchia delle viste. Questo potrebbe essere cambiato in futuro, e il codice sopra potrebbe comportarsi in modo imprevedibile come risultato.

Come risultato di quanto sopra, per motivi di longevità e affidabilità, suggerisco di adottare un altro approccio. Ci sono molte alternative elencate in questa discussione, e ti incoraggio a leggere, ma il mio preferito è il seguente:

Mantieni una proprietà di una chiusura sulla tua class di celle, chiedi al metodo di azione del pulsante di richiamarla.

 class MyCell: UITableViewCell { var button: UIButton! var buttonAction: ((Any) -> Void)? @objc func buttonPressed(sender: Any) { self.buttonAction?(sender) } } 

Quindi, quando crei la tua cella in cellForRowAtIndexPath , puoi assegnare un valore alla tua chiusura.

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

Spostando qui il codice del gestore, è ansible sfruttare l'argomento indexPath già presente. Questo è un approccio molto più sicuro di quello sopra elencato in quanto non si basa su tratti non documentati.

UPDATE : Ottenere il indexPath della cella contenente il pulsante (sia sezione che riga):

Usando la posizione del pulsante

All’interno del metodo buttonTapped , puoi prendere la posizione del pulsante, convertirla in una coordinata in tableView, quindi ottenere il indexPath della riga in corrispondenza di quella coordinata.

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

NOTA : A volte è ansible imbattersi in un caso limite quando si utilizza la funzione view.convert(CGPointZero, to:self.tableView) risultati nel trovare nil per una riga in un punto, anche se c’è una cella tableView lì. Per risolvere questo problema, prova a passare una coordinata reale leggermente sfalsata rispetto all’origine, ad esempio:

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

Risposta precedente: Utilizzo della proprietà Tag (solo riga restituisce)

Invece di arrampicarsi sugli alberi superview per afferrare un puntatore alla cella che contiene l’UIButton, c’è una tecnica più sicura e ripetibile che utilizza la proprietà button.tag citata da Antonio sopra, descritta in questa risposta , e mostrata sotto:

In cellForRowAtIndexPath: si imposta la proprietà tag:

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

Quindi, nella funzione buttonClicked: fai riferimento a quel tag per afferrare la riga di indexPath in cui si trova il pulsante:

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

Preferisco questo metodo poiché ho scoperto che oscillare tra gli alberi superview può essere un modo rischioso per progettare un’applicazione. Inoltre, per l’objective C ho usato questa tecnica in passato e sono stato soddisfatto del risultato.

Il mio approccio a questo tipo di problema consiste nell’utilizzare un protocollo delegato tra la cella e il tableview. Ciò consente di mantenere il gestore di pulsanti nella sottoclass della cella, che consente di assegnare il gestore di azioni ritouch alla cella prototipo in Interface Builder, mantenendo comunque la logica del gestore di pulsanti nel controller di visualizzazione.

Evita anche l’approccio potenzialmente fragile della navigazione nella gerarchia delle viste o l’uso della proprietà tag , che presenta problemi quando cambiano gli indici delle celle (come risultato dell’inserimento, eliminazione o riordino)

CellSubclass.swift

 protocol CellSubclassDelegate: class { func buttonTapped(cell: CellSubclass) } class CellSubclass: UITableViewCell { @IBOutlet var someButton: UIButton! 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)") } } 

Utilizza un’estensione per UITableView per scaricare la cella per qualsiasi vista:


@ La risposta di Paulw11 relativa all’impostazione di un tipo di cella personalizzato con una proprietà delegata che invia messaggi alla visualizzazione tabella è un buon modo per procedere, ma richiede una certa quantità di lavoro da configurare.

Penso che camminare sulla gerarchia della vista della cella vista tabella non sia una ctriggers idea. È fragile: se in un secondo momento si chiude il pulsante in una vista a scopo di layout, è probabile che il codice si interrompa.

Anche l’utilizzo dei tag di visualizzazione è fragile. È necessario ricordare di impostare i tag quando si crea la cella e, se si utilizza tale approccio in un controller di visualizzazione che utilizza tag di visualizzazione per un altro scopo, è ansible avere numeri di tag duplicati e il codice può non funzionare come previsto.

Ho creato un’estensione per UITableView che ti consente di ottenere indexPath per qualsiasi vista contenuta in una cella di visualizzazione tabella. Restituisce un Optional che sarà nullo se la vista passata effettivamente non rientra in una cella di visualizzazione tabella. Di seguito è riportato il file di origine dell’estensione nella sua interezza. È ansible semplicemente inserire questo file nel progetto e quindi utilizzare il metodo indexPathForView(_:) incluso per trovare IndexPath che contiene qualsiasi vista.

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

Per usarlo, puoi semplicemente chiamare il metodo in IBAction per un pulsante contenuto in una cella:

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

(Si noti che la funzione indexPathForView(_:) funzionerà solo se l’object vista che è passato è contenuto da una cella attualmente sullo schermo. È ragionevole, dal momento che una vista che non è sullo schermo in realtà non appartiene a uno specifico indexPath; è probabile che venga assegnato a un indexPath diverso quando viene riciclata la cella contenente).

MODIFICARE:

Puoi scaricare un progetto dimostrativo funzionante che utilizza l’estensione di cui sopra da Github: TableViewExtension.git

Per Swift2.1

Ho trovato un modo per farlo, si spera, mi aiuterà.

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

Poiché il mittente del gestore eventi è il pulsante stesso, utilizzerei la proprietà tag del pulsante per memorizzare l’indice, inizializzato in cellForRowAtIndexPath .

Ma con un po ‘di lavoro in più farei in un modo completamente diverso. Se stai usando una cella personalizzata, questo è il modo in cui affronterò il problema:

  • aggiungi una proprietà ‘indexPath` alla cella della tabella personalizzata
  • inizializzarlo in cellForRowAtIndexPath
  • spostare il gestore del touch dal controller di visualizzazione all’implementazione della cella
  • utilizzare il modello di delega per notificare al controller di visualizzazione l’evento di touch, passando il percorso dell’indice

Soluzione Swift 4:

Hai un pulsante (myButton) o qualsiasi altra vista nella cella. Assegna tag in cellForRowAt come questo

 cell.myButton.tag = indexPath.row 

Ora in TapFunction o in qualsiasi altro. Scaricalo in questo modo e salvalo in una variabile locale.

 currentCellNumber = (sender.view?.tag)! 

Dopodiché puoi usare ovunque questo currentCellNumber per ottenere indexPath.row del pulsante selezionato.

Godere!

Dopo aver visto il suggerimento di Paulw11 di usare un callback delegato, volevo approfondire leggermente / proporre un altro suggerimento simile. Se non si desidera utilizzare lo schema del delegato, è ansible utilizzare le chiusure in modo rapido come segue:

La tua class di cellulare:

 class Cell: UITableViewCell { @IBOutlet var button: UIButton! var buttonAction: ((sender: AnyObject) -> Void)? @IBAction func buttonPressed(sender: AnyObject) { self.buttonAction?(sender) } } 

Il cellForRowAtIndexPath metodo 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. } 

A volte il pulsante potrebbe trovarsi all’interno di un’altra vista di UITableViewCell. In tal caso superview.superview potrebbe non fornire l’object cella e quindi indexPath sarà nullo.

In tal caso dovremmo continuare a trovare la superview fino a quando non otteniamo l’object cella.

Funzione per ottenere object cella per 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 } 

Ora possiamo ottenere indexPath sul pulsante tap come sotto

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

Ho usato il metodo convertPoint per ottenere il punto da tableview e passare questo punto al metodo indexPathForRowAtPoint per ottenere 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); } } } 

In Swift 3. Utilizzate anche le frasi di guardia, evitando una lunga catena di parentesi graffe.

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

Prova a utilizzare #selector per chiamare l’IBaction.In cellforrowatindexpath

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

In questo modo è ansible accedere al percorso dell’indice all’interno del metodo editButtonPressed

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

In Swift 4, basta usare questo:

 func buttonTapped(_ sender: UIButton) { let buttonPostion = sender.convert(sender.bounds.origin, to: tableView) if let indexPath = tableView.indexPathForRow(at: buttonPostion) { let rowIndex = indexPath.row } }