Un prototype de cellule personnalisé dans une table

J’a i les plus grandes peines du monde pour réussir à faire avaler à Xcode un modèle de prototype de celllule de UITableView pour un petit jeu à la con . Il y a dans ce prototype 4 UIImageView de petite taille et un label. Il se noie avec, refuse de suivre les contraintes et me superpose le label et les imageviews ou bien refuse de reconnaître la subclass de UITableViewCell que j’ai fini par faire pour essayer de l’amadouer. Je suis sur Mojave, avec Xcode 10.1 pour cette appli, et non pas sur Catalina avec Xcode 11.4.1. Mais je suis au bord de la reddition ou du fichier xib pour voir…
Voici le bout de code qui coince :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
tableView.register( Celle.self, forCellReuseIdentifier:« Celle »)
// c’est la ligne que voici qui coince :
let cell:Celle = tableView.dequeueReusableCell(withIdentifier:« Celle », for: indexPath) as! Celle
/* je précise que Celle est le nom de la cellule prototype dans le storyboard, mais aussi le nom de la sous-classe de UITableViewCell que j’ai créée pour connecter les imageViews et le label */
// print(« case recherchée : (hitsPlayed[indexPath.row][0]) »)
var name = hitsPlayed[indexPath.row]
cell.image1?.image = UIImage(named: name[0])
cell.image2?.image = UIImage(named: name[1])
cell.image3?.image = UIImage(named: name[2])
cell.image4?.image = UIImage(named: name[3])
cell.textLabel!.text = hitsPlayed[indexPath.row][4]

    return cell

}

Voici aussi une copie d’écran d’un bout du storyboard

Hello,

Commence peut-être par renommer ta classe CelleTableViewCell ou quelque chose du genre pour différencier la classe de l’identifier de ta cellule.
D’expérience, c’est jamais bon de nommer pareil deux choses différentes.

J’essaye derechef. Subséquemment sous peu, et je reviens dire quoi.

Oui, excellent, je passe la ligne en question, mais je coince trois lignes plus loin, sur image4, nil found unwrapping an optional (image4View): why ?

un if let sur l’image4 à mettre dans ta fonction TableViewCell peut-être ?

Mais dans ce cas, certes, il n’y aura pas de crash, mais je resterai grosjean comme devant et ma cellule ne se peuplera pas de ce qu’elle doit. Mon problème n’est pas de gérer un optionnel. Cela ne résoud pas mon problème. Je dois savoir pourquoi il considère la cellule retournée comme vide et non pourvue des imageView et label dont elle est sensée être pourvue. C’est ça que je n’arrive pas à trouver.

J’y vais à tâtons car je n’ai pas le code de ta TableViewCell donc compliqué de savoir ce qui se passe dedans…

Mon code a beaucoup changé depjis hier soir, car j’essaye des trucs, j’ai même tenté de coller ma cellule prototype dans un nib. Mais les essais passent, l’erreur, reste, au même endroit, ce qui montre un défaut sans doute assez global et facile à corriger, quand on sait. Moi, je sèche. voilà l’image de mon .xib :Capture d’écran 2020-05-12 à 18.41.39
le code de ma classe :
class CelleTableViewCell: UITableViewCell {

    @IBOutlet weak var tryLabel: UILabel!
    @IBOutlet weak var image1: UIImageView!
    @IBOutlet weak var image2: UIImageView!
    @IBOutlet weak var image3: UIImageView!
    @IBOutlet weak var image4: UIImageView!

static let reuseIdentifier = "Cellela"

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    setUpObjects()
}
required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    setUpObjects()
}
private func setUpObjects(){
    contentView.addSubview(image1)
    contentView.addSubview(image2)
    contentView.addSubview(image3)
    contentView.addSubview(image4)
    contentView.addSubview(tryLabel)
}

}

et enfin, le bout de code dans lequel se produit l’erreur :

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let theNib = UINib.init(nibName: "Cellela", bundle: nil)
    mytableView.register(theNib, forCellReuseIdentifier: "Cellela")
    mytableView.register(CelleTableViewCell.self, forCellReuseIdentifier: "Cellela")
    
    let cell:CelleTableViewCell = tableView.dequeueReusableCell(withIdentifier:"Cellela", for: indexPath) as! CelleTableViewCell
    
    
    var name = hitsPlayed[indexPath.row]
    cell.image1!.image = UIImage(named: name[0]) //"unexpectedly found nil while unwrapping an optional(image1)"
    cell.image2!.image = UIImage(named: name[1])
    cell.image3!.image = UIImage(named: name[2])
    cell.image4!.image = UIImage(named: name[3])
    cell.tryLabel!.text = name[4]
    
    return cell

}
Je ne doute pas de n'être qu'un con fini, mais au moins… déconfiné

A première vue, name[0] ne contient rien, ou un nom d’image avec une erreur dans la désignation.

J’aimerai bien ! Mais non, il contient un nom d’image valide, qui a déjà servi à remplir une autre imageView, c’est image1, l’imageView, qui pointe sur nil (d’après debug) :Capture d’écran 2020-05-12 à 19.07.01

T’as pensé à ça ? une solution possible

Je suis encore moins compétent en SwiftUI qu’en UIKit ; je ne suis pas enclin à croire qu’on puisse corriger une ignorance ici par une incompétence ailleurs. Quand j’aurais compris ce qui ne va pas et que je l’aurais corrigé, je m’en rappellerai avec SwiftUI que j’apprendrai plus vite, non ?
À moins que j’aie tout faux et que tu sois en mesure de me démontrer que ça irait beaucoup plus vite …

Démontrer … je ne connais pas encore assez bien l’outil pour faire ça. Mais sincèrement je pense que SwiftUI est vraiment un énorme pas en avant pour les novices (et les autres).

Je le pense aussi un peu, mais ça ne résoud pas mon problème pour le moment ! :slight_smile:

Bonjour,

Une suggestion: il se pourrait que le lien entre le storyboard et le code soit « corrompu »; essaye donc :

  1. de supprimer complètement tous les liens et de les recréer
  2. voire de supprimer les 4 vues image1 à image4 ainsi que leurs références dans le code, puis de tout recréer

Cordialement,
Nicolas

Et en complément, je suis surpris que tu aies besoin de la fonction SetUpObjects: pour moi,

  • soit on crée les vues dans le StoryBoard avec les liens vers les IBOutlet et IBAction, et les sous-vues sont automatiquement intégrées dans les vues
  • soit on crée tout dans le code, et dans ce cas seulement on a besoin d’ajouter les sous-vues ainsi créées à la vue « parent »

Cordialement
Nicolas

Ceci ne vas pas résoudre ton problème non plus, mais ça fonctionne :

Environ une heure de travail (y compris la réalisation des magnifiques ressources graphiques, réalisées à la MAIN sur un iPad Pro). Et c’était la première fois que je créais une cellule personnalisée et même une liste en SwiftUI !

import SwiftUI

struct MonContenu : Identifiable {
  var id = UUID()
  var img1 = ""
  var img2 = ""
  var img3 = ""
  var img4 = ""
  var texte = ""
}

// Générateur de contenus aléatoires
class GenerateurContenu {
  
  private let listeImages = ["bleu", "bleu2", "vert", "jaune", "rouge", "gris", "noir"]
  
  func creer() -> MonContenu {
    var contenu = MonContenu()
    contenu.img1 = listeImages.randomElement()!
    contenu.img2 = listeImages.randomElement()!
    contenu.img3 = listeImages.randomElement()!
    contenu.img4 = listeImages.randomElement()!
    contenu.texte = "Contenu aléatoire"
    return contenu
  }
}


struct PetiteImage: View {
  var nom:String
  var body: some View {
    Image(nom)
      .resizable()
      .aspectRatio(contentMode: .fit)
      .frame(height:50)
      .padding(.all, 0)
  }
}

struct MonContenuCellule : View {
  var contenu : MonContenu
  var body: some View {
    HStack(alignment:.center, spacing: 0) {
      PetiteImage(nom:contenu.img1)
      PetiteImage(nom:contenu.img2)
      PetiteImage(nom:contenu.img3)
      PetiteImage(nom:contenu.img4)
      Spacer(minLength: 5)
      Text(contenu.texte)
        .bold()
      Spacer()
      }
  }
}

struct ContentView: View {

  var generateur = GenerateurContenu()
  @State var listeContenus = [MonContenu]()
  
    var body: some View {
        // Affichage des cellules
        VStack {
          List {
            ForEach(listeContenus) { contenu in
              MonContenuCellule(contenu: contenu)
              }
          }
          Spacer()
          
          // Bouton pour ajouter du contenu aléatoire
          Button(action: {
            var contenu = self.generateur.creer()
            contenu.texte = "Cellule n° " + String(self.listeContenus.count)
            self.listeContenus.append(contenu)
          }) {
            Text("Créer une cellule")
              .font(.largeTitle)
          }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Tu as tout à fait raison, après avoir erré dans les sept cercles de l’enfer…

J’ai trouvé la solution ! J’ai cessé de retourner aux anciens fichiers .xib, de compliquer des classes effectivement auxquelles manquent les contraintes, de surcharger des fonctions qui n’en ont pas besoin, et de faire compliqué quand on peut faire simple !
la classe de la cellule prototype :

class CelleTableViewCell: UITableViewCell {

@IBOutlet weak var tryLabel: UILabel!
@IBOutlet weak var image1: UIImageView!
@IBOutlet weak var image2: UIImageView!
@IBOutlet weak var image3: UIImageView!
@IBOutlet weak var image4: UIImageView!

}

et rien d’autre !
et dans le même fichier que le ViewController, hors de sa classe, pour éviter d’en faire une classe intégrée ;
Du coup, dans le ViewController :

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCell(withIdentifier:"Cellela", for: indexPath) as! CelleTableViewCell
    
    
    var name = hitsPlayed[indexPath.row]
    
    cell.image1?.image = UIImage(named: name[0])
    cell.image2?.image = UIImage(named: name[1])
    cell.image3?.image = UIImage(named: name[2])
    cell.image4?.image = UIImage(named: name[3])
    cell.tryLabel?.text = name[4]
    
    return cell
    

}

Un code un peu plus simple, aéré et lisible. Et ça marche. Je découvre tes suggestions un peu après la bataille, mais si j’avais pu les lire il y a deux jours, je ne sais pas si j’aurais compris ; là, ça fait sens pour moi, et je te remercie d’avoir pris le temps de lire mon code un peu pourri et de m’avoir conseillé aussi juste et aussi aimablement ! Merci, ristretto.

C’est écœurant, ton truc. En une heure. Évidemment, je comprends ce que tu veux dire, passer à SwiftUI, oui, oui, oui… Bon, je finis de dernier truc en UIKit, et je décroche, c’est sûr. Mais j’imagine bien qu’avant d’en arriver à faire ça en une heure, il a fallu une certaine courbe d’apprentissage qui n’a pas dû ne prendre aucune durée, hein ? Combien de temps que tu es sur SwiftUI ? Six mois ?

Beaucoup moins que ça. Je me suis réellement mis à SwiftUI il y a environ 1 mois, en bricolant un peu et regardant à nouveau les vidéos de Maxime et d’un de ses collègues sur Udemy.

Mais je fait partie de ces gens qui ne peuvent apprendre qu’en faisant des choses par eux-mêmes. J’ai besoin d’un objectif précis à accomplir. J’ai réellement compris les principes de base en réalisant une version SwiftUI de la calculatrice UIKit de Maxime.

J’ai présenté cette première réalisation dans ce topic, il y a 15 jours :

Depuis, je cherche des idées de projet pour avancer : un p’tit jeu, un convertisseur décimal/binaire/hexa, ton problème de cellules personnalisées, etc …

La courbe d’apprentissage de SwiftUI est très rapide, pour quelqu’un connaissant déjà Swift.