Mettre à jour un composant parent avec un composant enfant avec un observable

Bonjour,

j’ai crée un composant tag et quand je clique dessus cela dois mêttre à jour la scrollview.
j’utilise firebase pour les datas.
Voici un screen de l’app

j’ai un model Exo qui est une classe Observable comme ceci :


import Foundation

import FirebaseDatabase

class ExoModelFirebase: ObservableObject {
    var ref = Database.database().reference()
    @Published var exos = [ExoModel]()
    
    func getResultSearch(_ section: String){
        self.ref.child("Exercises").queryOrdered(byChild: "pcategory").queryStarting(atValue: section).queryEnding(atValue: section+"\u{f8ff}").observeSingleEvent(of: .value) { snapshot -> Void in
            self.exos.removeAll()
            for exo in snapshot.children {
                
                let exoSnapshot = exo as! DataSnapshot
                let exoArrayData = exoSnapshot.value as? [String:AnyObject]
                self.exos.append(ExoModel(pcategory: exoArrayData?["pcategory"] as! String, pdate: exoArrayData?["pdate"] as! String, pheading: exoArrayData?["pheading"] as! String, piconlink: exoArrayData?["piconlink"] as! String, preadcolor: exoArrayData?["preadcolor"] as! String, psimpleblack: exoArrayData?["psimpleblack"] as! String))
                
            }
            
        }
    }
    
}

le composant tag, J’ai esssayer plein de chose mais rien ne fonctionne.

import SwiftUI

struct TagList: View {
    @State var tags:[String] = []
    @Binding var selectedMenu: String
    @ObservedObject var session = ExoModelFirebase()
    
    var body: some View {
        ScrollView(.horizontal, showsIndicators: false){
            HStack{
                
                ForEach(tags, id: \.self){ m in
                    Button(action: {
                        print(m)
                        session.getResultSearch(m)
                        session.objectWillChange.send()
                    }) {
                        HStack {
                            Text(m)
                        }
                    }
                    .padding()
                    .foregroundColor(.white)
                    .background(Color.orange)
                    .cornerRadius(20)
                    .lineLimit(1)
                }
            }.padding()
            
        }
        
    }
}

La vue

import SwiftUI

struct ExerciseView: View {
    
    @State var showSetting = false
    @State var section = ""
    //MARK: Properties
    @ObservedObject var session = ExoModelFirebase()
    
    var body: some View {
        NavigationView{
            VStack{
                CustomMenu(tags: ["CP", "CE1"], selectedMenu: $section)

                ScrollView {
                    ForEach(self.session.exos){ exo in
                        ListRowExo(exo: exo)
                    }
                    
                }
            }
            
            
            .navigationTitle("Exercises")
            .navigationBarItems(trailing: Button(action: {
                self.showSetting.toggle()
            }, label: {
                Image(systemName: "slider.horizontal.3")
                    .foregroundColor(.black)
                    .font(Font.system(size: 30))
                
            }).fullScreenCover(isPresented: $showSetting, content: {
                SettingView()
            }))
        }.onAppear(perform: {
            getExos()
            
            print("on appear")
        })
    }
    func getExos() {
        session.getResultSearch(section)
    }
}

Donc à l’init tout fonction bien, c’est au moment ou je clique sur CP par example, et la rien ne se passe.
Je ne sais pas comment dire à l’observable de renvoyé les bonnes datas.

Merci d’avance

j’ai trouvé une solution mais je ne sais pas si c’est la bonne solution

je fait passer mon objet session dans mon composant customMenu et la tout fonctionne comme ceci

struct CustomMenu: View {
    @State var tags:[String] = []
    @Binding var selectedMenu: String
    var session: ExoModelFirebase
    
    var body: some View {
        ScrollView(.horizontal, showsIndicators: false){
            HStack{
                
                ForEach(tags, id: \.self){ m in
                    Button(action: {
                        print(m)
                        session.getResultSearch(m)
                    }) {
                        HStack {
                            Text(m)
                        }
                    }
                    .padding()
                    .foregroundColor(.white)
                    .background(Color.orange)
                    .cornerRadius(20)
                    .lineLimit(1)
                }
            }.padding()
            
        }
        
    }
}

@mbritto : il y aurais une autre solution ? ou alors d’avoir un paramètre générique ?

Merci davance

Je pense qu’au lieu d’envoyer ton objet dans ton sous-menu, tu pourrais faire remonter l’info voulue (la catégorie sélectionnée) jusqu’à l’ancêtre commun qui a besoin de l’information. Je crois que c’est ExerciceView dans ton cas.
En utilisant des @Bindings tu peux envoyer juste une variable dans ton menu et quand le menu modifie cette variable, la vue parent saura que la section a changé et peut demander à son modèle de recharger ses données

@mbritto oui j’avais essayé mais cela n’a jamais fonctionné mais je pense que j’utilise mal les observable.
ici c’est le model :

import FirebaseDatabase

class ExoModelFirebase: ObservableObject {
    var ref = Database.database().reference()
    @Published var exos = [ExoModel]() {
        didSet {
                objectWillChange.send()
            print("item")
        }
    }
    
    func getResultSearch(_ section: String){
        self.ref.child("Exercises").queryOrdered(byChild: "pcategory").queryStarting(atValue: section).queryEnding(atValue: section+"\u{f8ff}").observeSingleEvent(of: .value) { snapshot -> Void in
            self.exos.removeAll()
            for exo in snapshot.children {
                
                let exoSnapshot = exo as! DataSnapshot
                let exoArrayData = exoSnapshot.value as? [String:AnyObject]
                self.exos.append(ExoModel(pcategory: exoArrayData?["pcategory"] as! String, pdate: exoArrayData?["pdate"] as! String, pheading: exoArrayData?["pheading"] as! String, piconlink: exoArrayData?["piconlink"] as! String, preadcolor: exoArrayData?["preadcolor"] as! String, psimpleblack: exoArrayData?["psimpleblack"] as! String))
                
            }
            
        }
    }
    
}

ensuite le sous - menu avec le binding

struct CustomMenu: View {
    @State var tags:[String] = []
    @Binding var selectedMenu: String

    
    var body: some View {
        ScrollView(.horizontal, showsIndicators: false){
            HStack{
                
                ForEach(tags, id: \.self){ m in
                    Button(action: {
                        selectedMenu = m
                        
                    }) {
                        HStack {
                            Text(m)
                        }
                    }
                    .padding()
                    .foregroundColor(.white)
                    .background(Color.orange)
                    .cornerRadius(20)
                    .lineLimit(1)
                }
            }.padding()   
        }
    }
}

la vue principale ou j’ai moment composant du sous-menu et la je récupère bien la binding quand je clique sur le menu.

mais j’ai du mal a visualisé comment faire passer la nouvelle valeur a mon observable et lui dire recharge toi.

struct ExerciseView: View {
    
    @State var showSetting = false
    @State var section = ""
    //MARK: Properties
    @ObservedObject var session = ExoModelFirebase()
    
    var body: some View {
        NavigationView{
            VStack{
                CustomMenu(tags: ["CP", "CE1"], selectedMenu: $section)

                ScrollView {
                    ForEach(self.session.exos){ exo in
                        ListRowExo(exo: exo)
                    }
                    
                }
            }
            
            
            .navigationTitle("Exercises")
            .navigationBarItems(trailing: Button(action: {
                self.showSetting.toggle()
            }, label: {
                Image(systemName: "slider.horizontal.3")
                    .foregroundColor(.black)
                    .font(Font.system(size: 30))
                
            }).fullScreenCover(isPresented: $showSetting, content: {
                SettingView()
            }))
        }.onAppear(perform: {
            getExos()
            print("on appear")
        })
    }
    func getExos() {
        session.getResultSearch(section)
    }
}

donc je pense mais sur qu'il y a un  truc que je fait mal mais je ne sais pas quoi .

merci d'avance.

Je ne suis pas certain pas que le didSet soit appelé quand tu modifies l’intérieur d’une propriété. Si tu remplaçais le tableau d’exos par un nouveau tableau oui, mais dans ton cas tu vides le tableau existant et tu lui redonnes des cases.
Je pense aussi qu’il faudrait plutôt utiliser willSet au lieu de didSet pour appeler objectWillChange

1 J'aime

@mbritto ok , mais actuellement quand je clique sur cp , je remonte ma valeur dans la classe mère qui est
ça

struct ExerciseView: View {
    
    @State var showSetting = false
    @State var section = ""
    //MARK: Properties
    @ObservedObject var session = ExoModelFirebase()
    
    var body: some View {
        NavigationView{
            VStack{
                CustomMenu(tags: ["CP", "CE1"], selectedMenu: $section)

                ScrollView {
                    ForEach(self.session.exos){ exo in
                        ListRowExo(exo: exo)
                    }
                    
                }
            }
            
            
            .navigationTitle("Exercises")
            .navigationBarItems(trailing: Button(action: {
                self.showSetting.toggle()
            }, label: {
                Image(systemName: "slider.horizontal.3")
                    .foregroundColor(.black)
                    .font(Font.system(size: 30))
                
            }).fullScreenCover(isPresented: $showSetting, content: {
                SettingView()
            }))
        }.onAppear(perform: {
            print("otot")
            session.getResultSearch(section)
        })
    }
    
}

c’est ici la ou je ne vois pas comment le notifié car il faut que je relance ma fonction session.getResultSearch(section) je suis un peut perdu ou alors il faut que j’arrêt de penser en reactJS lol.
ou alors j’ai louper une compréhension d’un concept :confused:

@mbritto j’ai réussi a mettre à jours le parent de cette manière :
dans ma vue principale :

CustomMenu(tags: self.session.categories, selectedMenu: $section).onChange(of: section, perform: { value in
                    session.getResultSearch(value)
                })

le code complet :

struct ExerciseView: View {
    
    @State var showSetting = false
    @State var section = ""
    //MARK: Properties
    @ObservedObject var session = ExoModelFirebase()
    
    
    var body: some View {
        NavigationView{
            VStack{
                CustomMenu(tags: self.session.categories, selectedMenu: $section).onChange(of: section, perform: { value in
                    session.getResultSearch(value)
                })
                

                ScrollView {
                    ForEach(self.session.exos){ exo in
                        ListRowExo(exo: exo)
                    }
                    
                }
            }
            
            
            .navigationTitle("Exercises")
            .navigationBarItems(trailing: Button(action: {
                self.showSetting.toggle()
            }, label: {
                Image(systemName: "slider.horizontal.3")
                    .foregroundColor(.black)
                    .font(Font.system(size: 30))
                
            }).fullScreenCover(isPresented: $showSetting, content: {
                SettingView()
            }))
        }.onAppear(perform: {
            print("otot")
            session.getResultSearch(section)
            session.getCategories()
        })
    }
    
}

je ne sais pas s’il y a une meilleur solution

1 J'aime