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