web-dev-qa-db-fra.com

Développer la cellule lorsque vous appuyez sur dans Swift

J'ai essayé d'implémenter une fonctionnalité dans mon application afin que lorsqu'un utilisateur appuie sur une cellule de la vue de tableau, la cellule se développe vers le bas pour afficher les notes. J'ai trouvé beaucoup d'exemples de cela dans Objective-C mais je n'ai pas encore trouvé de solution pour Swift.

Cet exemple semble parfait: Cellule de table accordéon - Comment développer/contracter dynamiquement uitableviewcell?

J'ai essayé de le traduire en Swift:

var selectedRowIndex = NSIndexPath()
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    selectedRowIndex = indexPath
    tableView.beginUpdates()
    tableView.endUpdates()
}

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    if selectedRowIndex == selectedRowIndex.row && indexPath.row == selectedRowIndex.row {
        return 100
    }
    return 70
}

Cependant, cela semble juste bloquer l'application.

Des idées?

Modifier:

Voici mon code cellForRowAtIndexPath:

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    var cell:CustomTransactionTableViewCell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as CustomTransactionTableViewCell

    cell.selectionStyle = UITableViewCellSelectionStyle.None

    if tableView == self.searchDisplayController?.searchResultsTableView {
        cell.paymentNameLabel.text = (searchResults.objectAtIndex(indexPath.row)) as? String
        //println(searchResults.objectAtIndex(indexPath.row))
        var indexValue = names.indexOfObject(searchResults.objectAtIndex(indexPath.row))
        cell.costLabel.text = (values.objectAtIndex(indexValue)) as? String
        cell.dateLabel.text = (dates.objectAtIndex(indexValue)) as? String

        if images.objectAtIndex(indexValue) as NSObject == 0 {
            cell.paymentArrowImage.hidden = false
            cell.creditArrowImage.hidden = true
        } else if images.objectAtIndex(indexValue) as NSObject == 1 {
            cell.creditArrowImage.hidden = false
            cell.paymentArrowImage.hidden = true
        }
    } else {
        cell.paymentNameLabel.text = (names.objectAtIndex(indexPath.row)) as? String
        cell.costLabel.text = (values.objectAtIndex(indexPath.row)) as? String
        cell.dateLabel.text = (dates.objectAtIndex(indexPath.row)) as? String

        if images.objectAtIndex(indexPath.row) as NSObject == 0 {
            cell.paymentArrowImage.hidden = false
            cell.creditArrowImage.hidden = true
        } else if images.objectAtIndex(indexPath.row) as NSObject == 1 {
            cell.creditArrowImage.hidden = false
            cell.paymentArrowImage.hidden = true
        }
    }
    return cell
}

Voici les paramètres de sortie:

enter image description here

13
user3746428

La première comparaison dans votre instruction if ne peut jamais être vraie, car vous comparez un chemin indexPath à un entier. Vous devez également initialiser la variable selectedRowIndex avec une valeur de ligne qui ne peut pas être dans la table, telle que -1, afin que rien ne soit développé lors du premier chargement de la table. 

var selectedRowIndex: NSIndexPath = NSIndexPath(forRow: -1, inSection: 0)

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    if indexPath.row == selectedRowIndex.row {
        return 100
    }
    return 70
}

Swift 4.2 var selectedRowIndex: NSIndexPath = NSIndexPath(row: -1, section: 0)

5
rdelmar

Cela m'a pris pas mal d'heures pour que cela fonctionne. Voici comment je l'ai résolu.

PS: le problème avec le code de @ rdelmar est qu’il suppose que vous n’avez qu’une variable section dans votre tableau. Il ne fait donc que comparer le indexPath.row. Si vous avez plusieurs sections (ou si vous souhaitez déjà prendre en compte le développement ultérieur du code), vous devez comparer l’index complet, comme suit:

1) Vous avez besoin d'une variable pour indiquer quelle ligne est sélectionnée. Je vois que vous l'avez déjà fait, mais vous devrez redonner à la variable l'état cohérent "rien de sélectionné" (lorsque l'utilisateur ferme toutes les cellules). Je crois que la meilleure façon de faire est via une option:

var selectedIndexPath: NSIndexPath? = nil

2) Vous devez identifier le moment où l'utilisateur sélectionne une cellule. didSelectRowAtIndexPath est le choix évident. Vous devez prendre en compte trois résultats possibles:

  1. l'utilisateur appuie sur une cellule et une autre cellule est développée 
  2. l'utilisateur tape sur une cellule et aucune cellule n'est développée
  3. l'utilisateur appuie sur une cellule déjà développée

Pour chaque cas, nous vérifions si selectedIndexPath est égal à nil (aucune cellule développée), égal à indexPath de la ligne sélectionnée (même cellule déjà développée) ou différent de indexPath (une autre cellule est développée). Nous ajustons le selectedIndexPath en conséquence. Cette variable sera utilisée pour vérifier la bonne valeur de rowHeight pour chaque ligne. Vous avez mentionné dans vos commentaires que didSelectRowAtIndexPath "ne semblait pas être appelé". Utilisez-vous un println () et vérifiez la console pour voir si elle a été appelée? J'en ai inclus un dans le code ci-dessous.

PS: cela ne fonctionne pas avec tableView.rowHeight car, apparemment, rowHeight n’est vérifié qu’une seule fois par Swift avant de mettre à jour TOUTES les lignes du tableau.

Enfin, j’utilise reloadRowsAtIndexPath pour ne recharger que les lignes nécessaires. Mais aussi, parce que je sais que cela va redessiner la table, relancer si nécessaire et même animer les changements. Notez que [indexPath] est entre crochets car cette méthode demande un tableau de NSIndexPath:

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        println("didSelectRowAtIndexPath was called")
        var cell = tableView.cellForRowAtIndexPath(indexPath) as! MyCustomTableViewCell
        switch selectedIndexPath {
        case nil:
            selectedIndexPath = indexPath
        default:
            if selectedIndexPath! == indexPath {
                selectedIndexPath = nil
            } else {
                selectedIndexPath = indexPath
            }
        }
        tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
}

3) Troisième et dernière étape, Swift doit savoir quand passer chaque valeur à la hauteur de la cellule. Nous effectuons un contrôle similaire ici, avec if/else. Je sais que vous pouvez raccourcir beaucoup le code, mais je saisis tout pour que les autres puissent le comprendre facilement:

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        let smallHeight: CGFloat = 70.0
        let expandedHeight: CGFloat = 100.0
        let ip = indexPath
        if selectedIndexPath != nil {
            if ip == selectedIndexPath! {
                return expandedHeight
            } else {
                return smallHeight
            }
        } else {
            return smallHeight
        }
    }

Maintenant, quelques notes sur votre code qui pourraient être la cause de vos problèmes, si ceci ne résout pas le problème:

var cell:CustomTransactionTableViewCell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as CustomTransactionTableViewCell

Je ne sais pas si c'est le problème, mais self ne devrait pas être nécessaire, car vous insérez probablement ce code dans votre (Custom) TableViewController. De plus, au lieu de spécifier votre type de variable, vous pouvez faire confiance à l'inférence de Swift si vous forcez correctement la cellule à partir de la file d'attente. Cette diffusion forcée est le as! dans le code ci-dessous:

var cell = tableView.dequeueReusableCellWithIdentifier("CellIdentifier" forIndexPath: indexPath) as! CustomTransactionTableViewCell

Cependant, vous devez ABSOLUMENT définir cet identifiant. Accédez à votre storyboard, sélectionnez le tableau qui contient la cellule dont vous avez besoin, pour la sous-classe de TableViewCell dont vous avez besoin (probablement CustomTransactionTableViewCell, dans votre cas). Sélectionnez maintenant la cellule dans TableView (vérifiez que vous avez sélectionné le bon élément. Il est préférable d’ouvrir le plan du document via Editeur> Afficher le plan du document). Une fois la cellule sélectionnée, ouvrez l'inspecteur d'attributs à droite et entrez le nom Identifier.

Vous pouvez également essayer de commenter le cell.selectionStyle = UITableViewCellSelectionStyle.None pour vérifier si cela bloque la sélection de quelque manière que ce soit (de cette manière, les cellules changeront de couleur si elles sont sélectionnées si elles sont sélectionnées).

Bonne chance mec.

14
Lucas Cerro

Une approche différente consisterait à insérer un nouveau contrôleur de vue dans la pile de navigation et à utiliser la transition pour l’effet en expansion. Les avantages seraient SoC (séparation des préoccupations). Exemple de projets Swift 2.0 pour les deux modèles.

4
Justin Fischer

Je suggère de résoudre ceci avec la contrainte de mise en page de hauteur de modyfing

class ExpandableCell: UITableViewCell {

@IBOutlet weak var img: UIImageView!


@IBOutlet weak var imgHeightConstraint: NSLayoutConstraint!


var isExpanded:Bool = false
    {
    didSet
    {
        if !isExpanded {
            self.imgHeightConstraint.constant = 0.0

        } else {
            self.imgHeightConstraint.constant = 128.0
        }
    }
}

}

Ensuite, dans ViewController:

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

@IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
    super.viewDidLoad()
    self.tableView.delegate = self
    self.tableView.dataSource = self
    self.tableView.estimatedRowHeight = 2.0
    self.tableView.rowHeight = UITableViewAutomaticDimension
    self.tableView.tableFooterView = UIView()
}


// TableView DataSource methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 3
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell:ExpandableCell = tableView.dequeueReusableCell(withIdentifier: "ExpandableCell") as! ExpandableCell
    cell.img.image = UIImage(named: indexPath.row.description)
    cell.isExpanded = false
    return cell
}

// TableView Delegate methods
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    guard let cell = tableView.cellForRow(at: indexPath) as? ExpandableCell
        else { return }

        UIView.animate(withDuration: 0.3, animations: {
            tableView.beginUpdates()
            cell.isExpanded = !cell.isExpanded
            tableView.scrollToRow(at: indexPath, at: UITableViewScrollPosition.top, animated: true)
            tableView.endUpdates()

        })

    }




func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
    guard let cell = tableView.cellForRow(at: indexPath) as? ExpandableCell
        else { return }
    UIView.animate(withDuration: 0.3, animations: {
        tableView.beginUpdates()
        cell.isExpanded = false
        tableView.endUpdates()
    })
}
}

Didacticiel complet disponible ici

2
DCDC

Après avoir obtenu le chemin d'index dans didSelectRowAtIndexPath, rechargez simplement la cellule avec la méthode suivante reloadCellsAtIndexpath Et dans heightForRowAtIndexPathMethod, vérifiez la condition suivante 

if selectedIndexPath != nil && selectedIndexPath == indexPath {    
       return yourExpandedCellHieght
}
0
Abhishek