web-dev-qa-db-fra.com

détection de boutons pressés dans une table: les meilleures pratiques de Swift

J'ai un tableau avec un nombre variable de cellules représentant les étudiants qui correspondent à leur instructeur particulier. Ce sont des cellules personnalisées avec un bouton qui déclenche le passage à une nouvelle VC, apportant des informations détaillées sur l’élève de la cellule. Ma question est:

Quelle est la meilleure pratique dans Swift pour identifier le bouton sur lequel vous avez appuyé?

Une fois que je connais le chemin de l’index, je peux identifier les informations de l’élève qui doivent être transmises au prochain VC. Le post ci-dessous présente une excellente réponse à l’objectif C, mais je ne sais pas trop comment traduire Swift. Toute aide serait très appréciée.

Détecter quel UIButton a été pressé dans un UITableView

34
jamike

Si votre code le permet, je vous recommande de définir la balise UIButton égale à indexPath.row. Ainsi, lorsque son action est déclenchée, vous pouvez extraire la balise et donc la ligne des données du bouton pendant la méthode déclenchée. Par exemple, dans cellForRowAtIndexPath, vous pouvez définir la balise:

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

puis dans buttonClicked:, vous pouvez récupérer la balise et donc la ligne:

func buttonClicked(sender:UIButton) {

    let buttonRow = sender.tag
}

Sinon, si cela n’est pas favorable à votre code pour une raison quelconque, la traduction Swift de cet Objective-C vous renvoie à :

- (void)checkButtonTapped:(id)sender
{
    CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
    if (indexPath != nil)
    {
     ...
    }
}

est:

func checkButtonTapped(sender:AnyObject) {
      let buttonPosition = sender.convert(CGPoint.zero, to: self.tableView)
    let indexPath = self.tableView.indexPathForRow(at: buttonPosition)
    if indexPath != nil {
        ...
    }
}
78
Lyndsey Scott

Solution Swift 3.0

cell.btnRequest.tag = indexPath.row

    cell.btnRequest.addTarget(self,action:#selector(buttonClicked(sender:)), for: .touchUpInside)

    func buttonClicked(sender:UIButton) {

            let buttonRow = sender.tag
        }
14
Sourabh Sharma

Mise à jour pour Swift 3

Si la seule chose que vous voulez faire est de déclencher une transition sur une touche, il serait contre les bonnes pratiques de le faire via un bouton UIB. Vous pouvez simplement utiliser le gestionnaire intégré de UIKit pour sélectionner une cellule, par exemple func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath). Vous pouvez l'implémenter en procédant comme suit:

Créer un UITableViewCell personnalisé

class StudentCell: UITableViewCell {
    // Declare properties you need for a student in a custom cell.
    var student: SuperSpecialStudentObject!

    // Other code here...
}

Lorsque vous chargez votre UITableView, transmettez les données à la cellule à partir de votre modèle de données:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "StudentCell", for: indexPath) as! StudentCell
    cell.student = superSpecialDataSource[indexPath.row]
    return cell
}

Utilisez ensuite didSelectRow atIndexPath pour détecter le moment où une cellule a été sélectionnée, accédez à la cellule et à ses données, puis transmettez la valeur en tant que paramètre à performSegue.

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let cell = tableView.cellForRow(at: indexPath) as! StudentCell

    if let dataToSend = cell.student {
        performSegue(withIdentifier: "DestinationView", sender: dataToSend)
    }
}

Et enfin dans prepareForSegue:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "DestinationView" {
        let destination = segue.destination as! DestinationViewController
        if let dataToSend = sender as? SuperSpecialStudentObject {
            destination.student = dataToSend
        }
    }
}

Alternativement, si vous souhaitez qu’ils ne sélectionnent qu’une partie de la cellule au lieu d’être en contact direct avec la cellule, vous pouvez ajouter un élément accessoire à votre cellule, tel que l’élément accessoire en détail et) utilisez plutôt override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath).

6
darksinge

Swift 3

@ cellForRowAt indexPath

cell.Btn.addTarget(self, action: #selector(self.BtnAction(_:)), for: .touchUpInside)

Ensuite

func BtnAction(_ sender: Any)
    {

        let btn = sender as? UIButton

    }
2
Shibin k kurian

Une autre solution possible serait d'utiliser dispatch_block_t. Si vous le faites avec Storyboard, vous devez d’abord créer une variable membre dans votre classe UITableViewCell personnalisée.

var tapBlock: dispatch_block_t?

Ensuite, vous devez créer une IBAction et appeler le tapBlock.

@IBAction func didTouchButton(sender: AnyObject) {
    if let tapBlock = self.tapBlock {
        tapBlock()
    }
}

Dans votre contrôleur de vue avec la variable UITableView, vous pouvez simplement réagir aux événements de bouton comme celui-ci.

let cell = tableView.dequeueReusableCellWithIdentifier("YourCellIdentifier", forIndexPath: indexPath) as! YourCustomTableViewCell

cell.tapBlock = {
   println("Button tapped")
}

Cependant, vous devez être conscient lorsque vous accédez à self dans le bloc pour ne pas créer de cycle de conservation. Assurez-vous d'y accéder en tant que [weak self].

2
gpichler

Ce n'est jamais une bonne idée d'utiliser des balises pour identifier les cellules et indexPaths, vous finirez par vous retrouver avec un indexPath erroné et par conséquent une cellule et des informations erronées. 

Je vous suggère d'essayer le code ci-dessous (Travailler avec UICollectionView, je ne l'ai pas testé avec un TableView, mais cela fonctionnera probablement très bien):

Swift 4  

@objc func buttonClicked(_ sender: UIButton) {
    if let tableView = tableViewNameObj {
        let point = tableView.convert(sender.center, from: sender.superview!)

        if let wantedIndexPath = tableView.indexPathForItem(at: point) {
            let cell = tableView.cellForItem(at: wantedIndexPath) as! SpecificTableViewCell

        }
    }
}
1

Pour faire suite aux commentaires de @Lyndsey et de @ longbow, j'ai remarqué que lorsque la transition dans le storyboard passait du bouton au destinataireVC, la fonction prepareForSegue était appelée avant que la fonction buttonClicked puisse mettre à jour la variable urlPath. Pour résoudre ce problème, je règle la séquence directement du premier VC sur la destinationVC. La séquence est exécutée par programme après l'exécution du code dans buttonClicked. Peut-être pas idéal, mais semble fonctionner.

func buttonClicked(sender:UIButton) {
    let studentDic = tableData[sender.tag] as NSDictionary
    let studentIDforTherapyInt = studentDic["ID"] as Int
    studentIDforTherapy = String(studentIDforTherapyInt)
    urlPath = "BaseURL..."+studentIDforTherapy
    self.performSegueWithIdentifier("selectTherapySegue", sender: sender)
}

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
    if (segue.identifier == "selectTherapySegue") {
        let svc = segue.destinationViewController as SelectTherapyViewController;
        svc.urlPath = urlPath
    }
0
jamike

J'allais utiliser l'approche indexPath jusqu'à ce que je comprenne que ce ne serait pas fiable/erroné dans certaines situations (cellule supprimée ou déplacée, par exemple).

Ce que j'ai fait est plus simple. Par exemple, j'affiche une série de couleurs et leurs valeurs RVB, une par cellule de table. Chaque couleur est définie dans un tableau de structures de couleurs. Pour plus de clarté, ce sont:

struct ColorStruct {
    var colorname:String    = ""
    var red:   Int = 0
    var green: Int = 0
    var blue:  Int = 0
}

var colors:[ColorStruct] = []       // The color array

Ma cellule prototype a un var pour contenir l'index/clé dans mon tableau:

class allListsCell: UITableViewCell {
    @IBOutlet var cellColorView: UIView!
    @IBOutlet var cellColorname: UILabel!
    var colorIndex = Int()  // ---> points directly back to colors[]

    @IBAction func colorEditButton(_ sender: UIButton, forEvent event: UIEvent) {
        print("colorEditButton: colors[] index:\(self.colorIndex), \(colors[self.colorIndex].colorname)")
    }
}

Cette solution prend trois lignes de code, une dans la définition de la cellule prototype , la deuxième dans la logique qui remplit une nouvelle cellule, et la troisième dans la fonction IBAction qui est appelée lorsque le bouton d'une cellule est pressé. Comme j'ai effectivement caché la "clé" (index) des données de chaque cellule, alors que je remplis cette nouvelle cellule, aucun calcul n'est requis - et si vous déplacez des cellules, vous n'avez pas besoin de mettre à jour quoi que ce soit.

0
Vic W.

Je le fais via prepareforSegue

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {

            let indexPath = self.tableView.indexPathForSelectedRow()
            let item = tableViewCollection[indexPath!.row].id
            let controller = segue.destinationViewController as? DetailVC

            controller?.thisItem = item
    }

et sur le prochain contrôleur, je ne ferai que recharger les propriétés de l’élément complet, en connaissant son identifiant et en le configurant sur le var thisItem dans DetailVC

0
longbow

Détecter la Section et ligne pour UiTableView indexPath sur clic Cliquez sur un bouton 

//MARK:- Buttom Action Method
    @objc func checkUncheckList(_sender:UIButton)
    {
        if self.arrayRequestList != nil
        {

          let strSection = sender.title(for: .disabled)

          let dict = self.arrayRequestList![Int(strSection!)!]["record"][sender.tag]

          print("dict:\(dict)")

          self.requestAcceptORReject(dict: dict, strAcceptorReject: "1")
        }

    }

Voici UITableView Cell, méthode permettant d’ajouter le targate 

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "OtherPropertySelectiingCell", for: indexPath as IndexPath) as! OtherPropertySelectiingCell
        cell.btnAccept.tag = indexPath.row
        cell.btnAccept.setTitle("\(indexPath.section)", for: .disabled)
        cell.btnAccept.addTarget(self, action: #selector(checkUncheckList(_sender:)), for: .touchUpInside)
        return cell
    }
0
pansora abhay

J'ai trouvé un moyen très simple et sûr d'utiliser pour gérer n'importe quelle cellule dans tableView et collectionView en utilisant une classe Model, ce qui 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 model clas, veuillez suivre la procédure ci-dessous . Créez une 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èles 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