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)
}
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.
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)")
}
}
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.
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.)
Vous pouvez télécharger un projet de démonstration qui utilise l’extension ci-dessus à partir de Github: TableViewExtension.git
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")
}
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!
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
}
}
É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:
cellForRowAtIndexPath
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.
}
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
}
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
classe RNCheckedModel: NSObject {
var is_check = false
var user_name = ""
}
classe InviteCell: UITableViewCell {
@IBOutlet var imgProfileImage: UIImageView!
@IBOutlet var btnCheck: UIButton!
@IBOutlet var lblName: UILabel!
@IBOutlet var lblEmail: UILabel!
}
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()
}
}
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)
}
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);
}
}
}
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)
}