Come ottenere indexpath.row quando un elemento è attivato?


104

Ho una tableview con pulsanti e voglio usare indexpath.row quando uno di essi viene toccato. Questo è quello che ho attualmente, 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)
}

devo usare IndexPathForSelectedRow () invece della variabile point? o dove dovrei usarlo?
Vincent

Risposte:


164

giorashc l'ha quasi capito con la sua risposta, ma ha trascurato il fatto che le celle hanno uno contentViewstrato in più. Quindi, dobbiamo andare uno strato più in profondità:

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 di visualizzazione una tableView ha celle come sottoview che successivamente hanno le proprie "visualizzazioni del contenuto", ecco perché è necessario ottenere la superview di questa visualizzazione del contenuto per ottenere la cella stessa. Di conseguenza, se il tuo pulsante è contenuto in una sottoview invece che direttamente nella visualizzazione del contenuto della cella, dovrai andare in profondità di molti livelli per accedervi.

Quanto sopra è uno di questi approcci, ma non necessariamente l'approccio migliore. Sebbene sia funzionale, presuppone dettagli su un UITableViewCellche Apple non ha mai necessariamente documentato, come la sua gerarchia di visualizzazione. Ciò potrebbe essere modificato in futuro e il codice precedente potrebbe comportarsi in modo imprevedibile di conseguenza.

Come risultato di quanto sopra, per motivi di longevità e affidabilità, consiglio di adottare un altro approccio. Ci sono molte alternative elencate in questo thread e ti incoraggio a leggere, ma la mia preferita è la seguente:

Mantieni una proprietà di chiusura sulla classe della tua cella, 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 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 il codice del gestore qui, puoi sfruttare l' indexPathargomento già presente . Questo è un approccio molto più sicuro di quello sopra elencato poiché non si basa su tratti non documentati.


2
Ben individuato. Sono uno sviluppatore competente, lo prometto;) - Ho modificato la mia risposta.
Jacob King,

12
Questo non è il modo corretto per ottenere la cella da un pulsante. Il layout di una cella è cambiato nel corso degli anni e un codice come questo non funzionerà quando ciò accadrà. Non utilizzare questo approccio.
rmaddy

11
Questa è una cattiva soluzione. Presume dettagli su UITableViewCells che Apple non ha mai necessariamente accettato. Sebbene UITableViewCells abbia una proprietà contentView, non vi è alcuna garanzia che la superview di contentView sarà sempre la cella.
bpapa

1
@PintuRajput Puoi descrivermi la gerarchia delle viste per favore? Probabilmente lo vedrai perché il tuo pulsante non è una sottoview diretta della visualizzazione del contenuto della cella.
Jacob King,

2
@ymutlu Sono totalmente d'accordo, l'ho detto nella risposta. Ho anche proposto una soluzione molto più robusta. Il motivo per cui ho lasciato l'originale al suo posto è perché sento che è meglio mostrare agli altri sviluppatori i problemi con un approccio piuttosto che schivarli del tutto, questo non insegna loro nulla che vedi. :)
Jacob King

61

Il mio approccio a questo tipo di problema consiste nell'usare un protocollo delegato tra la cella e il tableview. Ciò consente di mantenere il gestore del pulsante nella sottoclasse della cella, che consente di assegnare il gestore dell'azione di ritocco alla cella prototipo in Interface Builder, pur mantenendo la logica del gestore del pulsante nel controller di visualizzazione.

Evita anche l'approccio potenzialmente fragile di navigare nella gerarchia della vista o l'uso della tagproprietà, che ha problemi quando gli indici delle celle cambiano (a seguito di inserimento, cancellazione o riordino)

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è la funzione delegato e si trova nel controller di visualizzazione. Nel mio esempio, someButtonTappedè il metodo di azione nella cella
Paulw11

@ paulw11 Il cellulare non ha nessun pulsante membro Toccato in questo metodo@IBAction func someButtonTapped(sender: UIButton) { self.delegate?.buttonTapped(self) }
EI Captain v2.0

1
Questa è una soluzione abbastanza buona (neanche lontanamente cattiva come le due attualmente con più voti, usando il tag che guarda la superview) ma sembra che sia troppo codice extra da aggiungere.
bpapa

2
Questa è la soluzione corretta e dovrebbe essere la risposta accettata. Non abusa della proprietà del tag, non presuppone la costruzione di celle (che possono essere facilmente modificate da Apple) e continuerà a funzionare (senza codifica aggiuntiva) quando le celle vengono spostate o vengono aggiunte nuove celle tra celle esistenti.
Gatto robotico

1
@ Paulw11 Inizialmente pensavo che si trattasse di un sacco di codice, ma si è dimostrato molto più resistente di quello che stavo usando prima. Grazie per aver pubblicato questa solida soluzione.
Adrian

53

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

Utilizzo della posizione del pulsante

All'interno del tuo buttonTappedmetodo, puoi prendere la posizione del pulsante, convertirlo in una coordinata nella tableView, quindi ottenere l'indicePath della riga in 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 è possibile incorrere in un caso limite quando si utilizzano i view.convert(CGPointZero, to:self.tableView)risultati della funzione nilper trovare una riga in un punto, anche se è presente una cella tableView. Per risolvere questo problema, prova a passare una coordinata reale leggermente spostata dall'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 (restituisce solo riga)

Invece di arrampicarsi sugli alberi di superview per afferrare un puntatore alla cella che contiene il pulsante UIB, esiste una tecnica più sicura e ripetibile che utilizza la proprietà button.tag menzionata da Antonio sopra, descritta in questa risposta e mostrata di seguito:

In cellForRowAtIndexPath:si imposta la proprietà del tag:

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

Quindi, nella buttonClicked:funzione, 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 negli alberi di superview può essere un modo rischioso per progettare un'app. Inoltre, per l'obiettivo-C ho usato questa tecnica in passato e sono rimasto soddisfatto del risultato.


5
Questo è un bel modo per farlo, e lo voterò per far iniziare un po 'il tuo rappresentante, tuttavia l'unico difetto è che non dà accesso alla sezione indexPath.section se anche questo è necessario. Ottima risposta però!
Jacob King,

Grazie Jacob! Apprezzo il karma di ripetizione. Se si desidera ottenere il indexPath.sectionin aggiunta al indexPath.row(senza reimpostare la proprietà del tag come indexPath.section), in cellForRowAtIndexPath:è possibile modificare il tag in button.tag = indexPath, quindi nella buttonClicked:funzione è possibile accedere sia utilizzando sender.tag.rowche sender.tag.section.
Iron John Bonney

1
È una nuova funzionalità, perché sono sicuro di ricordare che la proprietà del tag è di tipo Int e non di tipo AnyObject, a meno che non sia cambiato in swift 2.3?
Jacob King,

@JacobKing hai ragione! Colpa mia, mi sono completamente distanziata durante la scrittura di quel commento e pensavo che il tag fosse di tipo AnyObject. Derp - non badare a me. Sarebbe utile se potessi passare indexPath come tag però ...
Iron John Bonney,

3
Neanche un buon approccio. Per prima cosa, funzionerebbe solo nelle viste tabella in cui è presente una singola sezione.
bpapa

16

Usa un'estensione per UITableView per recuperare la cella per qualsiasi vista:


La risposta di @ Paulw11 di impostare un tipo di cella personalizzato con una proprietà delegato che invia messaggi alla visualizzazione tabella è un buon modo per procedere, ma richiede una certa quantità di lavoro per la configurazione.

Penso che camminare nella gerarchia della vista della cella della vista tabella alla ricerca della cella sia una cattiva idea. È fragile: se successivamente si racchiude il pulsante in una vista per motivi di layout, è probabile che il codice si interrompa.

Anche l'utilizzo dei tag di visualizzazione è fragile. Devi ricordarti di impostare i tag quando crei la cella e, se utilizzi questo approccio in un controller di visualizzazione che utilizza i tag di visualizzazione per un altro scopo, puoi avere numeri di tag duplicati e il tuo codice potrebbe non funzionare come previsto.

Ho creato un'estensione per UITableView che ti consente di ottenere indexPath per qualsiasi vista contenuta in una cella della vista tabella. Restituisce un valore Optionalnullo se la vista passata non rientra effettivamente in una cella della vista tabella. Di seguito è riportato il file sorgente dell'estensione nella sua interezza. Puoi semplicemente inserire questo file nel tuo progetto e quindi usare il indexPathForView(_:)metodo 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 nell'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")
  }
}

(Nota che la indexPathForView(_:)funzione funzionerà solo se l'oggetto di visualizzazione che viene passato è contenuto da una cella che è attualmente sullo schermo. È ragionevole, poiché una visualizzazione che non è sullo schermo non appartiene effettivamente a uno specifico indexPath; è probabile che essere assegnato a un indexPath diverso quando la cella contenente viene riciclata.)

MODIFICARE:

Puoi scaricare un progetto demo funzionante che utilizza l'estensione sopra da Github: TableViewExtension.git


Grazie, ho usato l'estensione per ottenere indexPath di una visualizzazione di testo in una cella - ha funzionato perfettamente.
Jeremy Andrews

9

Per Swift2.1

Ho trovato un modo per farlo, spero che possa aiutare.

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

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

Cosa significa se l'errore si verifica? Quali sono alcuni motivi per cui non riesce a trovare il punto in tableView?
OOProg

Questa (o simile, utilizzando i metodi di conversione UIView) dovrebbe essere la risposta accettata. Non sono sicuro del motivo per cui è attualmente n. 4, poiché non fa supposizioni sulla gerarchia privata di una vista tabella, non usa la proprietà tag (quasi sempre una cattiva idea) e non coinvolge molto codice extra.
bpapa

9

Soluzione Swift 4:

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

cell.myButton.tag = indexPath.row

Ora in te tocca Funzione o qualsiasi altra. Prendilo 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!


Questo approccio funziona, ma i tag di visualizzazione sono fragili, come menzionato nella mia risposta. Ad esempio, un semplice tag intero non funzionerà per una vista tabella in sezioni. (un IndexPath è composto da 2 numeri interi.) Il mio approccio funzionerà sempre e non è necessario installare un tag nel pulsante (o in un'altra visualizzazione selezionabile).
Duncan C

6

In Swift 4, usa 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
        }
}

Risposta più pulita, dovrebbe essere selezionata. L'unica cosa da notare è che tableViewè una variabile di uscita a cui è necessario fare riferimento prima che questa risposta funzioni.
10000RubyPools

Funziona come un fascino !!
Parthpatel1105

4

Molto semplice ottenere Index Path swift 4, 5

 let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! Cell
  cell.btn.tag = indexPath.row


  cell.btn.addTarget(self, action: "buttonTapped:", forControlEvents: 
UIControlEvents.TouchUpInside)

Come ottenere IndexPath Inside Btn Click:

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


}

3

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

Ma con un po 'più di lavoro lo farei in un modo completamente diverso. Se stai usando una cella personalizzata, ecco come affronterei il problema:

  • aggiungere una proprietà 'indexPath` alla cella della tabella personalizzata
  • inizializzalo in cellForRowAtIndexPath
  • spostare il tap handler dal controller di visualizzazione all'implementazione della cella
  • utilizzare il modello di delega per notificare al controller della vista l'evento tap, passando il percorso dell'indice

Antonio, ho una cella personalizzata e mi piacerebbe farlo a modo tuo. Tuttavia, non funziona. Voglio che il mio codice "scorri per rivelare il pulsante di eliminazione" venga eseguito, ovvero il metodo tableView commitEditingStyle. Ho rimosso quel codice dalla classe mainVC e l'ho inserito nella classe customCell, ma ora il codice non funziona più. Cosa mi sto perdendo?
Dave G

Penso che questo sia il modo migliore per ottenere indexPath di una cella con sezioni x, tuttavia non vedo la necessità di punti elenco 3 e 4 in un approccio MVC
Edward

2

Dopo aver visto il suggerimento di Paulw11 di utilizzare una richiamata delegata, ho voluto elaborarlo leggermente / avanzare un altro suggerimento simile. Se non si desidera utilizzare il modello delegato, è possibile utilizzare le chiusure in swift come segue:

La tua classe cellulare:

class Cell: UITableViewCell {
    @IBOutlet var button: UIButton!

    var buttonAction: ((sender: AnyObject) -> Void)?

    @IBAction func buttonPressed(sender: AnyObject) {
        self.buttonAction?(sender)
    }
}

Il tuo cellForRowAtIndexPathmetodo:

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

2

Ho trovato un modo molto semplice da utilizzare per gestire qualsiasi cella in tableView e collectionView utilizzando una classe Model e questo funziona perfettamente.

C'è davvero un modo molto migliore per gestirlo ora. Questo funzionerà per gestire cella e valore.

Ecco il mio output (screenshot) quindi guarda questo:

inserisci qui la descrizione dell'immagine

  1. È molto semplice creare una classe modello , segui la procedura seguente. Crea una classe rapida con il nome RNCheckedModel, scrivi il codice come sotto.
class RNCheckedModel: NSObject {

    var is_check = false
    var user_name = ""

    }
  1. crea la tua classe cellulare
class InviteCell: UITableViewCell {

    @IBOutlet var imgProfileImage: UIImageView!
    @IBOutlet var btnCheck: UIButton!
    @IBOutlet var lblName: UILabel!
    @IBOutlet var lblEmail: UILabel!
    }
  1. e infine usa la classe del modello nel tuo UIViewController quando usi il tuo UITableView .
    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()

    }

     }

1

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

1

Prova a utilizzare #selector per chiamare IBaction.In cellforrowatindexpath

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

In questo modo puoi accedere all'indexpath all'interno del metodo editButtonPressed

func editButtonPressed(_ sender: UIButton) {

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

}

Risposta più appropriata
Amalendu Kar

No, i tag saranno disattivati ​​quando l'utente aggiunge o rimuove celle.
koen

1

Nel mio caso ho più sezioni e sia la sezione che l'indice di riga sono vitali, quindi in tal caso ho appena creato una proprietà su UIButton che ho impostato l'indice di cella in questo modo:

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

Quindi imposta la proprietà in cellForRowAt in questo modo:

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

Quindi in handleTapAction puoi ottenere indexPath in questo modo:

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

}

1

Swift 4 e 5

Metodo 1 utilizzando protocollo delegato

Ad esempio, hai un UITableViewCellcon nomeMyCell

class MyCell: UITableViewCell {
    
    var delegate:MyCellDelegate!
    
    @IBAction private func myAction(_ sender: UIButton){
        delegate.didPressButton(cell: self)
    }
}

Ora crea un file protocol

protocol MyCellDelegate {
    func didPressButton(cell: UITableViewCell)
}

Passaggio successivo, crea un'estensione di UITableView

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

Nel tuo UIViewControllerimplementare il protocolloMyCellDelegate

class ViewController: UIViewController, MyCellDelegate {
     
    func didPressButton(cell: UITableViewCell) {
        if let indexpath = self.myTableView.returnIndexPath(cell: cell) {
              print(indexpath)
        }
    }
}

Metodo 2 utilizzando chiusure

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

Nota: - se vuoi ottenere UICollectionViewindexPath puoi usarlo UICollectionView extensione ripetere i passaggi precedenti

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

0

In Swift 3. Utilizzato anche dichiarazioni 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)
}

Questo non funzionerà. La superview del pulsante non sarà la cella.
rmaddy

Questo funziona. L'unica cosa a cui dovresti prestare attenzione è che lo stack di visualizzazione di tutti è diverso. Potrebbe essere sender.superview, sender.superview.superview o sender.superview.superview.superview. Ma funziona davvero bene.
Sean

0

A volte il pulsante può trovarsi all'interno di un'altra vista di UITableViewCell. In tal caso, superview.superview potrebbe non fornire l'oggetto cell e quindi indexPath sarà nullo.

In tal caso dovremmo continuare a trovare la superview finché non otteniamo l'oggetto cell.

Funzione per ottenere l'oggetto cella da 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 al tocco del pulsante come di seguito

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

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

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.