Probleme de refresh data dans un ForEach

Salut la team,

J’ai une incompréhension, je ne comprends pas cela :

J’ai une vue (Content) qui contient un modèle qui hérite d’ObservableObject et une autre vue (TestView) qui instancie le modèle en StateObject.

Le modèle contient une propriété « test » qui est une instance de la classe Post qui d’ailleurs hérite d’ObservableObject.

Mon modèle a une méthode nommée sobrement addItem, il ajoute un string « item » dans la propriété test ( une instance de la classe Post) du modèle.

Donc je teste mon programme, je clique sur le bouton, la fonction addItem se lance, ajout un item dans l’array sur lequel un ForEach itère et là…CE FICHU FOREACH NE SE MET PAS À JOUR :exploding_head:

CleanShot 2022-06-07 at 16.01.41

Désolé je ne touche pas à Swift mais j’ai une petite question. Tu n’utilises pas l’émulateur natif de Xcode n’est-ce pas ? Lequel utilises-tu dans ce cas et a-t-il une camera ? (au contraire des émulateur natif de Xcode)

@AntoLhn C’est l’émulateur du canevas d’Xcode :stuck_out_tongue:

1 « J'aime »

J’ai souvent tout faux, je te prie de bien vouloir m’excuser si nécessaire, mais quand le lis ton code, je vois bien que tu déclares ton tableau de chaînes de caractère, mais je ne vois pas où tu l’initialises ? Je me trompe ? Je ne suis pas sûr qu’il suffise d’initialiser Post pour ça ?

haha il est initialiser vide ligne 52 :stuck_out_tongue:

Bonsoir,

Ce que ça m’inspire, sans avoir testé:

  • dans TestView, tu déclares une variable « model » qui est liée à la vue StateView; quand tu mets à jour la variable « model » de la ContentView, il n’y a aucun lien avec la variable du même nom de testView
  • Essaie avec @Binding au lieu de @StateView; tu seras normalement obligé de rajouter le signe « $ » quand tu passeras la variable « model » de ContentView vers TestView ce qui sera le signe qu’il y a un lien entre les deux

Cordialement,
Nicolas

1 « J'aime »

Merci @ristretto j’ai déjà essayé via un binding malheureusement la vue n’actualise pas :confused:

Bonsoir,

J’arrive à quelque chose qui commence à ressembler à ce que tu cherches à faire; je le poste en espérant que ça te sera utile !
Cordialement,
Nicolas

struct ContentView: View {
    @ObservedObject var model = Model()
    var body: some View {
        VStack {
            TestView(textToDisplay: $model.modelDescription, arrayToDisplay: $model.modelArray)
            Button {
                model.addItem()
            } label: {
                Text("add")
            }
            
        }
    }
}

struct TestView: View {
    @Binding var textToDisplay: String
    @Binding var arrayToDisplay: [String]
    var body: some View {
        Text(textToDisplay)
        ForEach(arrayToDisplay, id:\.self) {element in
            Text(element)
        }
    }
}

class Model: ObservableObject {
    @Published var modelDescription = "0"
    @Published var modelArray = ["0"]
    
    func addItem() {
        print("fonction addItem in progress")
        self.modelDescription += "1"
        self.modelArray.append("1")
    }
}
1 « J'aime »

Et encore plus proche que ce que tu souhaites !

struct ContentView: View {
    @ObservedObject var model = Model()
    var body: some View {
        VStack {
            TestView(postArray: $model.testPost)
            Button {
                model.addItem()
            } label: {
                Text("add")
            }
            
        }
    }
}

struct TestView: View {
    @Binding var postArray: Post
    var body: some View {
        ForEach(postArray.array, id:\.self) {element in
            Text(element)
        }
        
        
    }
}

class Model: ObservableObject {
    var testPost = Post()
    
    func addItem() {
        self.testPost.array.append("newElement")
        objectWillChange.send()
    }
}

class Post {
    var title: String = "test"
    var array: [String] = []
}
1 « J'aime »

Un commentaire qui n’a rien à voir: la syntaxe ci-dessus peut être dangereuse, puisque tes objets dans la boucle ForEach sont censés être identifiés de manière unique. Or un tableau de String peut contenir des objets qui seront considérés comme identiques, ce qui va poser problème s’il s’agit d’en modifier, supprimer, etc…

1 « J'aime »

Salut @Nono92,

Pour commencer, ta ContentView est ta vue principale, contenant ton TestView.

C’est donc là que tu dois declarer ton @StateObject.
« State » étant la « Source Of Truth » de ta variable.

D’ailleurs, on écrit toujours :

@StateObject private var model: Model = Model()

pour l’initialiser et

@ObservedObject var model: Model

sans initialisation (on passera la référence en appelant la vue, ou avec un onAppear par example).

On ne déclare jamais un @ObservedObject de cette façon :

@ObservedObject var model: Model = Model()

Ensuite, ce serait plutôt dans ton Model que tu maintien a jour la liste des différents Posts

@Published var test: [Post] = []

avec la méthode permettant de les rajouter (ou les retirer).

Je te mets un example dans le code final :

// struct plutot que class
// Post conforme au protocole Identifiable
struct Post: Identifiable {
    // Creation d'un unique id pour chaque Post (Identifiable)
    let id: UUID = UUID()
    // Title est par default "test" quand un Post est cree
    var title: String = "test"
}

class Model: ObservableObject {
    // Declaration de la liste de Post
    @Published var test: [Post] = []
    
    func addItem() {
        // Ajout d'un Post a la liste avec le titre "item"
        self.test.append(Post(title: "item"))
    }
    
    func addItem(title: String) {
        // Une methode pour changer le title du Post (avec un TextField par example...)
        self.test.append(Post(title: title))
    }
    
    func removeItem() {
        // Verification que la liste n'est PAS vide
        if (!test.isEmpty) {
            // On enleve le dernier element de la liste
            self.test.remove(at: test.count - 1)
        }
    }
}

struct ContentView: View {
    // Declaration du Model dans la vue principale
    @StateObject private var model: Model = Model()
    
    var body: some View {
        VStack {
            // Affichage de TestView en passant la reference du Model
            TestView(model: model)
            // Je mettrais un Spacer() ici pour que les boutons soient toujours en bas de l'ecran
            // ainsi qu'une ScrollView autour du TestView pour ne pas que les boutons disparaissent
            // une fois la vue est remplie de VStack dans le ForEach...
            HStack(spacing: 50.0) {
                Button {
                    // Appel de la methode addItem()
                    self.model.addItem()
                } label: {
                    Text("Add Item")
                }
                Button {
                    // Appel de la methode removeItem()
                    self.model.removeItem()
                } label: {
                    Text("Remove Last")
                }
            }
        }
    }
}

struct TestView: View {
    // Model recu a la creation de la vue dans ContentView
    @ObservedObject var model: Model
    
    var body: some View {
        VStack {
            // Iteration de chaque Post
            // Si id: \.self, stuct Post doit conformer a Hashable
            // Si id: \.id, struct Post doit conformer a Identifiable
            // id est preferable
            ForEach(model.test, id: \.id) { test in
                // Recuperation et affichage du title du Post
                Text(test.title)
            }
        }
    }
}

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

J’espère que cela te conviendra.

Happy Coding !

1 « J'aime »

Tu as raison. J’ai lu trop vite. Mais j’avais prévenu…

Merci les gars ! Je vais attentivement lire vos réponses, merci vraiment de votre implication !! <3