web-dev-qa-db-fra.com

swift: comment obtenir le fichier indexpath.row lorsqu'un bouton d'une cellule est exploité?

J'ai une tableview avec des boutons et je veux utiliser le indexpath.row lorsque l'un d'eux est exploité. C'est ce que j'ai actuellement, mais c'est toujours 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)
}
82
Vincent

giorashc l'avait presque avec sa réponse, mais il avait négligé le fait que les cellules avaient une couche supplémentaire contentView. Ainsi, nous devons aller plus loin:

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

let indexPath = itemTable.indexPath(for: cell)

En effet, dans la hiérarchie des vues, un tableau a des cellules en tant que sous-vues qui ont ensuite leurs propres "vues de contenu". C'est pourquoi vous devez obtenir la vue d'ensemble de cette vue de contenu pour obtenir la cellule elle-même. En conséquence, si votre bouton est contenu dans une sous-vue plutôt que directement dans la vue du contenu de la cellule, vous devrez aller au nombre de couches plus profondes pour y accéder.

Ce qui précède est l’une de ces approches, mais pas nécessairement la meilleure. Bien que fonctionnel, il suppose des détails sur une UITableViewCell que Apple n’a jamais nécessairement documentée, telle que sa hiérarchie de vues. Cela pourrait changer dans le futur et le code ci-dessus pourrait bien se comporter de manière imprévisible.

En raison de ce qui précède, pour des raisons de longévité et de fiabilité, je recommande d’adopter une autre approche. Il existe de nombreuses alternatives répertoriées dans ce fil, et je vous encourage à lire, mais mon préféré est le suivant:

Conservez une propriété de fermeture sur votre classe de cellule et demandez à la méthode d'action du bouton de l'invoquer.

class MyCell: UITableViewCell {
    var button: UIButton!

    var buttonAction: ((Any) -> Void)?

    @objc func buttonPressed(sender: Any) {
        self.buttonAction?(sender)
    }
}

Ensuite, lorsque vous créez votre cellule dans cellForRowAtIndexPath, vous pouvez affecter une valeur à votre fermeture.

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

En déplaçant votre code de gestionnaire ici, vous pouvez tirer parti de l'argument déjà présent indexPath. Cette approche est bien plus sûre que celle indiquée ci-dessus car elle ne repose pas sur des traits non documentés.

138
Jacob King

Mon approche à ce type de problème consiste à utiliser un protocole de délégué entre la cellule et la table. Cela vous permet de conserver le gestionnaire de boutons dans la sous-classe de cellules, ce qui vous permet d'attribuer le gestionnaire d'actions de retouche à la cellule prototype dans Interface Builder, tout en maintenant la logique du gestionnaire de boutons dans le contrôleur de vue.

Cela évite également l'approche potentiellement fragile consistant à naviguer dans la hiérarchie des vues ou à utiliser la propriété tag, qui pose des problèmes lorsque les index de cellules changent (à la suite d'une insertion, d'une suppression ou d'une réorganisation).

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)")
    }
} 
47
Paulw11

UPDATE: Récupération de l'indexPath de la cellule contenant le bouton (section et ligne):

Utilisation de la position du bouton

Dans votre méthode buttonTapped, vous pouvez saisir la position du bouton, le convertir en coordonnée dans la table, puis obtenir le chemin indexPath de la ligne à cette coordonnée.

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

NOTE: Parfois, vous pouvez rencontrer un cas Edge lorsque vous utilisez la fonction view.convert(CGPointZero, to:self.tableView) pour trouver nil pour une ligne. à un moment donné, même s’il existe une cellule tableView. Pour résoudre ce problème, essayez de passer une coordonnée réelle légèrement décalée par rapport à l'origine, telle que:

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

Réponse précédente: Utilisation de la propriété de balise (renvoie uniquement la ligne)

Plutôt que de grimper dans les arbres supérieurs pour saisir un pointeur sur la cellule qui contient l'UIButton, il existe une technique plus sûre et plus reproductible utilisant la propriété button.tag mentionnée par Antonio ci-dessus, décrite dans cette réponse , et montré ci-dessous:

Dans cellForRowAtIndexPath:, vous définissez la propriété de la balise:

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

Ensuite, dans la fonction buttonClicked:, vous référencez cette balise pour saisir la ligne du chemin indexPath où se trouve le bouton:

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

Je préfère cette méthode car j'ai découvert que balancer dans les super arbres peut être une façon risquée de concevoir une application. En outre, pour l’objectif C, j’ai utilisé cette technique dans le passé et je suis satisfait du résultat.

46
Iron John Bonney

Utilisez une extension de UITableView pour récupérer la cellule pour n’importe quelle vue:


La réponse de @ Paulw11 consistant à configurer un type de cellule personnalisé avec une propriété delegate qui envoie des messages à la vue table est une bonne solution, mais elle nécessite un certain travail.

Je pense que parcourir la hiérarchie des vues d'une cellule à la table est une mauvaise idée. Il est fragile - si vous insérez votre bouton ultérieurement dans une vue à des fins de mise en page, ce code risque de se rompre.

L'utilisation des balises de vue est également fragile. N'oubliez pas de configurer les balises lors de la création de la cellule. Si vous utilisez cette approche dans un contrôleur de vue qui utilise les balises de vue à une autre fin, vous pouvez avoir des numéros de balises en double et votre code peut ne pas fonctionner comme prévu.

J'ai créé une extension à UITableView qui vous permet d'obtenir le chemin indexPath pour toute vue contenue dans une cellule de vue tableau. Il retourne un Optional qui sera nul si la vue transmise ne fait pas partie d'une cellule de la vue tableau. Ci-dessous, le fichier source d'extension dans son intégralité. Vous pouvez simplement placer ce fichier dans votre projet, puis utiliser la méthode indexPathForView(_:) incluse pour rechercher le chemin indexPath contenant une vue.

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

Pour l'utiliser, vous pouvez simplement appeler la méthode dans IBAction pour un bouton contenu dans une cellule:

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

(Notez que la fonction indexPathForView(_:) ne fonctionnera que si l'objet de vue transmis est contenu dans une cellule actuellement à l'écran. C'est raisonnable, car une vue qui n'est pas à l'écran n'appartient pas à un objet spécifique. indexPath; il est susceptible d'être affecté à un autre indexPath lorsqu'il contient la cellule est recyclée.)

MODIFIER:

Vous pouvez télécharger un projet de démonstration qui utilise l’extension ci-dessus à partir de Github: TableViewExtension.git

13
Duncan C

Pour Swift2.1

J'ai trouvé un moyen de le faire, j'espère que ça va aider.

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

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

Solution rapide 4:

Vous avez un bouton (myButton) ou toute autre vue dans la cellule. Attribuer un tag dans cellForRowAt comme ceci

cell.myButton.tag = indexPath.row

Maintenant, dans vous appuyez surFonction ou tout autre. Récupérez-le comme ceci et enregistrez-le dans une variable locale.

currentCellNumber = (sender.view?.tag)!

Après cela, vous pouvez utiliser n’importe où ce currentCellNumber pour obtenir le indexPath.row du bouton sélectionné.

Prendre plaisir!

5
Sajid Zeb

Dans Swift 4, utilisez simplement ceci:

func buttonTapped(_ sender: UIButton) {
        let buttonPostion = sender.convert(sender.bounds.Origin, to: tableView)

        if let indexPath = tableView.indexPathForRow(at: buttonPostion) {
            let rowIndex =  indexPath.row
        }
}
5
DEEPAK KUMAR

Étant donné que l'expéditeur du gestionnaire d'événements est le bouton lui-même, j'utiliserais la propriété tag du bouton pour stocker l'index, initialisé dans cellForRowAtIndexPath.

Mais avec un peu plus de travail, je le ferais de manière complètement différente. Si vous utilisez une cellule personnalisée, voici comment aborder le problème:

  • ajouter une propriété 'indexPath` à la cellule de tableau personnalisée
  • l'initialiser dans cellForRowAtIndexPath
  • déplacez le gestionnaire de prises du contrôleur de vue vers l'implémentation de cellule
  • utiliser le modèle de délégation pour informer le contrôleur de la vue de l'événement tap en passant le chemin de l'index
3
Antonio

Après avoir vu la suggestion de Paulw11 d’utiliser un rappel de délégué, je souhaitais en dire un peu plus ou avancer une autre suggestion similaire. Si vous ne souhaitez pas utiliser le modèle de délégué, vous pouvez utiliser les fermetures dans Swift comme suit:

Votre classe de cellule:

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

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

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

Votre méthode 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.
}
2
Jacob King

Essayez d’utiliser #selector pour appeler IBaction.In the cellforrowatindexpath

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

De cette façon, vous pouvez accéder au chemin d'index dans la méthode editButtonPressed

func editButtonPressed(_ sender: UIButton) {

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

}
1
Vineeth Krishnan

J'ai trouvé un moyen très simple et sûr à utiliser pour gérer n'importe quelle cellule dans tableView et collectionView en utilisant une classe Model et cela fonctionne parfaitement.

Il existe en effet une bien meilleure façon de gérer cela maintenant. Cela fonctionnera pour gérer la cellule et la valeur

voici ma sortie (capture d'écran) alors voyez ceci

voici mon code

  1. Il est très simple de créer des classes de modèles , veuillez suivre la procédure ci-dessous. Créez la classe Swift avec le nom "RNCheckedModel", écrivez le code comme ci-dessous.

classe RNCheckedModel: NSObject {

var is_check = false
var user_name = ""

}
  1. créez votre classe de cellule

classe InviteCell: UITableViewCell {

@IBOutlet var imgProfileImage: UIImageView!
@IBOutlet var btnCheck: UIButton!
@IBOutlet var lblName: UILabel!
@IBOutlet var lblEmail: UILabel!
}
  1. et enfin utiliser la classe de modèle dans votre UIViewController lorsque vous utilisez votre UITableView .

classe 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()

}

 }
0
Paresh Mangukiya

Dans Swift 3. Également utilisé des instructions de garde, en évitant une longue chaîne d'accolades.

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

    print(indexPath)
}
0
sean

J'ai utilisé la méthode convertPoint pour obtenir un point à partir de la table et passez ce point à la méthode indexPathForRowAtPoint pour obtenir 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);
            }   
        }
    }
0
Avijit Nagare

Parfois, le bouton peut être à l'intérieur d'une autre vue de UITableViewCell. Dans ce cas, superview.superview peut ne pas donner l'objet cellule et, par conséquent, indexPath sera nul.

Dans ce cas, nous devrions continuer à rechercher le super aperçu jusqu'à ce que nous obtenions l'objet cellule.

Fonction pour obtenir un objet cellule par 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
}

Maintenant, nous pouvons obtenir indexPath sur le bouton du robinet comme ci-dessous

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