Comment forcer une vue à reloader avec Core Data

Salut tout le monde,

Je viens de terminer la partie du cours SwiftUI sur Core Data et j’ai décidé d’aller un peu plus loin en créant une vue séparée pour entrer les informations d’une nouvelle « tâche » dans la liste.
Le problème c’est que, malgré le fait que ma nouvelle tâche soit bien enregistrée dans Core Data, la vue contenant la liste ne se met pas à jour en temps réel. Je suis obligé de fermer la preview et de la réouvrir pour que mon nouvel item apparaisse.

Une idée de ce qui coince dans mon code ?

Le code de ma vue qui gère l’affichage de la liste de to do:

import SwiftUI
import CoreData

struct BrainDumpView: View {
    
    @Environment(\.managedObjectContext) var managedObjectContext
    @State var dumplist = DumpManager().dumpList
    @State var dumpManager = DumpManager()
    @State private var showingAddDumpView: Bool = false
//    @State var newDumpName: String
    
    var body: some View {
        NavigationView {
            VStack {
                VStack(alignment: HorizontalAlignment.leading){
                List{
                ForEach(dumplist) { dump in
                    Text(dump.name)
                }
                .onDelete(perform: deleteDump)
                .onTapGesture {
                    updateDump()
                }
            }
                Spacer()
        }
            }
            .navigationBarTitle(Text("Brain Dumps"))
            .navigationBarItems(trailing:
              Button(action:
                showingAddDump
            ) {
                Image(systemName: "plus")
                    .font(.largeTitle)
            }
            )
            }
       
        .sheet(isPresented: $showingAddDumpView, content: {
            NewDumpView(newDumpName: "")
        })
       
    }
    func showingAddDump() {
        showingAddDumpView.toggle()
    }
//    func createNewDump() {
//        if newDumpName.count > 0 {
//            dumpManager.addDump(dumpName: newDumpName)
//        }
//        newDumpName = ""
//    }
    
    func deleteDump(offsets: IndexSet){
        dumpManager.deleteDump(withID: dumpManager.dumpList[offsets[offsets.startIndex]].id)
        }
    }

func updateDump(){
    
}

struct BrainDumpView_Previews: PreviewProvider {
    static var previews: some View {
        BrainDumpView()
    }
}```

Et le code de la vue qui apparaît en tant que sheet: 

import SwiftUI

struct NewDumpView: View {

@Environment(\.managedObjectContext) var managedObjectContext
@Environment(\.presentationMode) var presentationMode
@State var newDumpName: String
@State var dumpManager = DumpManager()

var body: some View {
    NavigationView {
        VStack{
            Spacer()
            TextField("Dump", text: $newDumpName)
                .padding()
                .background(Color(UIColor.tertiarySystemFill))
                .cornerRadius(9)
                .font(.system(size: 24, weight: .bold, design: .default))
                .navigationBarTitle("New Dump", displayMode: .inline)
                .navigationBarItems(trailing:
                  Button(action: {
                    self.presentationMode.wrappedValue.dismiss()
                }) {
                    Image(systemName: "xmark")
                }
                )
       
            Button(action: {
                           self.presentationMode.wrappedValue.dismiss()
                createNewDump()
                         }) {
                           Text("Save")
                         }
        }
        
    }
    .padding()
            .navigationViewStyle(StackNavigationViewStyle())
                                }

func createNewDump() {
    if newDumpName.count > 0 {
        dumpManager.addDump(dumpName: newDumpName)
    }
    newDumpName = ""
}
                                }
                            
    struct NewDumpView_Previews: PreviewProvider {
                static var previews: some View {
                    NewDumpView(newDumpName: "Bite")
                }
            }

Merci de votre aide :) !

Bonjour,

Constat:

  • tu utilises actuellement 3 instances différentes de DumpManager, alors que tu devrais en utiliser une seule
  • les 3 instances: dumplist = DumpManager().dumpList, dumpManager = DumpManager() puis de nouveau dumpManager = DumpManager()

Recommandations:

  • tu devrais utiliser un seul dumpManager = DumpManager() et le déclarer en ObservedObject
  • dans la BrainDumpView, tu utilises alors ForEach(dumpManager.dumpList)
  • dans NewDumpView, tu déclares @Binding dumpManagerSecondView: DumpManager
  • dans BrainDumpView, tu déclares NewDumpView(newDumpName: «  », dumpManagerSecondView: dumpManager)

ça suppose que DumpManager soit déclaré en ObservableObject et que dumplist soit précédé de @Published

Tout ceci ne suffira peut-être pas, mais ça ira dans la bonne direction

Cordialement
Nicolas

Merci Nicolas ! :slight_smile:

J’essaie de l’implémenter mais j’ai une erreur pour l’élément : NewDumpView(newDumpName: « », dumpManagerSecondView: dumpManager)

Pardon, c’est un Binding, il faut passer en argument $dumpManager

J’ai essayé ça, mais j’ai toujours une erreur :

quoi que je rentre comme argument, j’ai une erreur à ce niveau dans ma vue BrainDumpView.

Ca c’est le code pour la seconde vue :

import SwiftUI

struct NewDumpView: View {
    
  
    @Environment(\.presentationMode) var presentationMode
    @State var newDumpName: String
    @Binding var dumpManagerSecondView: DumpManager
  
    
    var body: some View {
        NavigationView {
            VStack{
                Spacer()
                TextField("Dump", text: $newDumpName)
                    .padding()
                    .background(Color(UIColor.tertiarySystemFill))
                    .cornerRadius(9)
                    .font(.system(size: 24, weight: .bold, design: .default))
                    .navigationBarTitle("New Dump", displayMode: .inline)
                    .navigationBarItems(trailing:
                      Button(action: {
                        self.presentationMode.wrappedValue.dismiss()
                    }) {
                        Image(systemName: "xmark")
                    }
                    )
           
                Button(action: {
                               self.presentationMode.wrappedValue.dismiss()
                    createNewDump()
                             }) {
                               Text("Save")
                             }
            }
            
        }
        .padding()
                .navigationViewStyle(StackNavigationViewStyle())
                                    }
    
    func createNewDump() {
        if newDumpName.count > 0 {
            
            dumpManagerSecondView.addDump(dumpName: newDumpName)
        }
        newDumpName = ""
    }
                                    
}
                                
        struct NewDumpView_Previews: PreviewProvider {
                    static var previews: some View {
                        NewDumpView(newDumpName:"", dumpManagerSecondView: $dumpManagerSecondView)
                    }
                }

J’ai une erreur dans les deux vues concernant la partie qui est Bind.

Pourtant j’ai bien changé le code du Manager également :

import Foundation
import SwiftUI

class DumpManager: ObservableObject {
    
    
   @Published var dumpList: [Dump]
    let storage: CoreDataStorage = CoreDataStorage()
    
    init(){
        dumpList = storage.fetchTaskList()
    }
    
    @discardableResult
     func addDump(dumpName: String) -> Dump {
        let newDump = Dump(name: dumpName)
        dumpList.append(newDump)
        storage.addNewDump(dump: newDump)
        return newDump
    }
    
     func deleteDump(withID dumpID:UUID) {
        if let indexDump = dumpList.firstIndex(where: {
            (dump) -> Bool in
            dump.id == dumpID
        }) {
            dumpList.remove(at: indexDump)
            storage.deleteDump(dumpID: dumpID)
        }
    }
    
   
  
  
        
    
}

Une idée de ce que j’ai mal fait ?

Tu peux remettre le code pour ta vue principale (le début, où tu déclares les variables)

Yep, voici :

//
//  BrainDumpView.swift
//  StoriesFromFallacya
//
//  Created by Charlie on 2021-07-16.
//

import SwiftUI
import CoreData

struct BrainDumpView: View {
    
   
   
    @ObservedObject var dumpManager: DumpManager = DumpManager()
    
    
    @State private var showingAddDumpView: Bool = false
//    @State var newDumpName: String
    
    var body: some View {
        NavigationView {
            VStack {
                VStack(alignment: HorizontalAlignment.leading){
                List{
                    ForEach(dumpManager.dumpList) { dump in
                    Text(dump.name)
                }
                .onDelete(perform: deleteDump)
                .onTapGesture {
                    updateDump()
                }
            }
                Spacer()
        }
            }
            .navigationBarTitle(Text("Brain Dumps"))
            .navigationBarItems(trailing:
              Button(action:
                showingAddDump
            ) {
                Image(systemName: "plus")
                    .font(.largeTitle)
            }
            )
            }
       
        .sheet(isPresented: $showingAddDumpView, content: {
            NewDumpView(newDumpName: "", dumpManagerSecond: $dumpManager)

                            })
       
    }
    func showingAddDump() {
        showingAddDumpView.toggle()
    }
//    func createNewDump() {
//        if newDumpName.count > 0 {
//            dumpManager.addDump(dumpName: newDumpName)
//        }
//        newDumpName = ""
//    }
    
    func deleteDump(offsets: IndexSet){
        dumpManager.deleteDump(withID: dumpManager.dumpList[offsets[offsets.startIndex]].id)
        }
    }

func updateDump(){
    
}

struct BrainDumpView_Previews: PreviewProvider {
    static var previews: some View {
        BrainDumpView()
    }
}






















Essaye:

  • dans NewDumpView, tu déclares @Binding dumpList: [Dump] (et tu supprimes @Binding dumpManagerSecondView: DumpManager)
  • dans BrainDumpView, tu déclares NewDumpView(newDumpName: « », dumpList: $dumpManager.dumpList)

C’est fait, mais j’ai toujours une erreur au niveau de la preview, à la fois dans BrainDumpView et dans NewDumpView, quoi que je passe comme binding, j’ai ce message d’erreur :

(Je me sens stupide, mais impossible de comprendre la logique sous-jacente de tout ça =/ )

Bonsoir,

Essaye

@ObservedObject static var dumpManager = DumpManager()

Supprime @State var dumpList:[Dump]

Et il faudra sans doute utiliser $dumpManager.dumpList dans l’appel à BrainDumpView
Nicolas

La logique n’est pas évidente !!!

Tu as été plus rapide que moi, j’étais en train de modifier ma question ! :smiley:

C’est fait, cependant ma liste de tâches ne se reload pas en temps réel avec la nouvelle tâche ajoutée, je dois à chaque fois rebuilder pour que ça aille fetch la nouvelle tâche dans Core Data. Est-ce qu’il y a une erreur ou un élément manquant dans mon forEach ?

Est-ce que tu testes sur ton iPhone ? (par opposition à test sur simulateur ou via les previews)

Si oui, deux pistes:
1- vérifier que, quand tu ajoutes un élément, la dumpList est réellement mise à jour
2- Passer par une architecture différente, où tu utilises un @FetchResult comme source de données (voir CoreData Many-to-Many et mise à jour des données comme exemple de conversation récente)

Pour la piste 1, n’hésite pas à reposter ton code actuel

Nicolas

Dans mon DumpManager j’ai ce code :

import Foundation
import SwiftUI

class DumpManager: ObservableObject {
    
    let storage: CoreDataStorage = CoreDataStorage()
    
   @Published var dumpList: [Dump]
    
    init(){
        dumpList = storage.fetchTaskList()
    }
}

En fait on dirait que l’ajout d’une nouvelle tâche dans la liste ne trigger pas un refresh de la dumpList, malgré le fait qu’elle est publiée et qu’elle dépend d’une fonction qui utilise bien fetch comme tu le mentionnes:


    func fetchTaskList() -> [Dump] {
        
        let dumpList: [Dump]
        let fetchRequest: NSFetchRequest<CDDump> = CDDump.fetchRequest()
        if let rawDumPlist = try? context.fetch(fetchRequest){
            dumpList = rawDumPlist.compactMap({(rawDump:CDDump) -> Dump? in
                Dump(fromCoreDataObject: rawDump)
            })
        }else {
            dumpList = []
        }
        return dumpList
    }

Et dans ma vue principale, voici ce qui est déclaré:

struct BrainDumpView: View {

   
    @ObservedObject var dumpManager: DumpManager = DumpManager()
    @State var theFuckingList = DumpManager().dumpList
 
    
    @State private var showingAddDumpView: Bool = false
//    @State var newDumpName: String
    
    var body: some View {
        NavigationView {
            VStack {
                VStack(alignment: HorizontalAlignment.leading){
                List{
                    ForEach(theFuckingList) { dump in
                    Text(dump.name)
                }

Ca me rend fou :smiley:

Je ne vois pas comment se passe l’ajout d’un élément et en quoi la dumpList de ta vue principale est mise à jour

Désolé, ça sera plus clair avec tout le code !
En gros j’ai 4 fichiers créés avec le tutoriel de Purple Giraffe:

  1. CoreDataStorage
//
//  CoreDataStorage.swift
//  CoreDataStorage
//
//  Created by Charlie Maréchal on 2021-07-22.
//

import Foundation
import CoreData
import UIKit

class CoreDataStorage {
    
    lazy var persistentContainer: NSPersistentContainer = {
            let container = NSPersistentContainer(name: "StoriesFromFallacya")
            container.loadPersistentStores { description, error in
                if let error = error {
                    fatalError("Unable to load persistent stores: \(error)")
                }
            }
            return container
        }()
    
    var context: NSManagedObjectContext {
      return  persistentContainer.viewContext
    }
    
    func fetchTaskList() -> [Dump] {
        
        let dumpList: [Dump]
        let fetchRequest: NSFetchRequest<CDDump> = CDDump.fetchRequest()
        if let rawDumPlist = try? context.fetch(fetchRequest){
            dumpList = rawDumPlist.compactMap({(rawDump:CDDump) -> Dump? in
                Dump(fromCoreDataObject: rawDump)
            })
        }else {
            dumpList = []
        }
        return dumpList
    }
    
    
    func addNewDump(dump: Dump){
        let newDump = CDDump(context: context)
        newDump.id = dump.id
        newDump.name = dump.name
        saveData()
    }
    
  
    
     func fetchCDDump(withId dumpId: UUID) -> CDDump?{
        let fetchRequest: NSFetchRequest<CDDump> = CDDump.fetchRequest()
        fetchRequest.predicate = NSPredicate(format: "id == %@",  dumpId as CVarArg)
        fetchRequest.fetchLimit = 1
        let fetchResult = try? context.fetch(fetchRequest)
        return fetchResult?.first
    }
    
    
    private func saveData(){
        if context.hasChanges {
            do {
                try context.save()
                
            }catch {
                print("Erreur pendant la sauvegarde CoreData: \(error)")
            }
        }
    }
    
    
    
    func deleteDump(dumpID:UUID){
        if let dumpFound = fetchCDDump(withId: dumpID){
            self.context.delete(dumpFound)
            saveData()
        }else {
            print("Pas trouvé")
        }
    }
}

extension Dump {
    init?(fromCoreDataObject coreDataObject: CDDump){
        guard let id = coreDataObject.id, let name = coreDataObject.name else {
            return nil
        }
        self.id = id
        self.name = name
     
    }
}

  1. DumpManager
//
//  DumpManager.swift
//  DumpManager
//
//  Created by Charlie Maréchal on 2021-07-22.
//

import Foundation
import SwiftUI

class DumpManager: ObservableObject {
    
    let storage: CoreDataStorage = CoreDataStorage()
    
   @Published var dumpList: [Dump] 
    
    init(){
        dumpList = storage.fetchTaskList()
    }
    
    
    
    
    
    @discardableResult
     func addDump(dumpName: String) -> Dump {
        let newDump = Dump(name: dumpName)
        dumpList.append(newDump)
        storage.addNewDump(dump: newDump)
        return newDump
    }
    
     func deleteDump(withID dumpID:UUID) {
        if let indexDump = dumpList.firstIndex(where: {
            (dump) -> Bool in
            dump.id == dumpID
        }) {
            dumpList.remove(at: indexDump)
            storage.deleteDump(dumpID: dumpID)
        }
    }
    
   
  
  
        
    
}

  1. BrainDumpView
//
//  BrainDumpView.swift
//  StoriesFromFallacya
//
//  Created by Charlie on 2021-07-16.
//

import SwiftUI
import CoreData

struct BrainDumpView: View {

   
    @ObservedObject var dumpManager: DumpManager = DumpManager()
    @State var theFuckingList = DumpManager().dumpList
 
    
    @State private var showingAddDumpView: Bool = false
//    @State var newDumpName: String
    
    var body: some View {
        NavigationView {
            VStack {
                VStack(alignment: HorizontalAlignment.leading){
                List{
                    ForEach(theFuckingList) { dump in
                    Text(dump.name)
                }
                .onDelete(perform: deleteDump)
                .onTapGesture {
                    updateDump()
                }
            }
                Spacer()
        }
            }
            .navigationBarTitle(Text("Brain Dumps"))
            .navigationBarItems(trailing:
              Button(action:
                showingAddDump
            ) {
                Image(systemName: "plus")
                    .font(.largeTitle)
            }
            )
            }
       
        .sheet(isPresented: $showingAddDumpView, content: {
            NewDumpView(newDumpName: "", dumpList: $dumpManager.dumpList)

                            })
       
    }
    func showingAddDump() {
        showingAddDumpView.toggle()
    }
//    func createNewDump() {
//        if newDumpName.count > 0 {
//            dumpManager.addDump(dumpName: newDumpName)
//        }
//        newDumpName = ""
//    }
    
    func deleteDump(offsets: IndexSet){
        dumpManager.deleteDump(withID: dumpManager.dumpList[offsets[offsets.startIndex]].id)
        }
    }

func updateDump(){
    
}

struct BrainDumpView_Previews: PreviewProvider {
 
    
    static var previews: some View {
        
      
        BrainDumpView()
    }
}






















  1. NewDumpView
//
//  NewDumpView.swift
//  NewDumpView
//
//  Created by Charlie Maréchal on 2021-07-23.
//

import SwiftUI

struct NewDumpView: View {
    
  
    @Environment(\.presentationMode) var presentationMode
    @ObservedObject var dumpManager: DumpManager = DumpManager()
    @State var newDumpName: String
    @Binding var dumpList: [Dump]
  
    var body: some View {
        NavigationView {
            VStack{
                Spacer()
                TextField("Dump", text: $newDumpName, onCommit: createNewDump)
                    
                    .padding()
                    .background(Color(UIColor.tertiarySystemFill))
                    .cornerRadius(9)
                    .font(.system(size: 24, weight: .bold, design: .default))
                    .navigationBarTitle("New Dump", displayMode: .inline)
                    .navigationBarItems(trailing:
                      Button(action: {
                        self.presentationMode.wrappedValue.dismiss()
                    }) {
                        Image(systemName: "xmark")
                    }
                    )
           
                Button(action: {
                               self.presentationMode.wrappedValue.dismiss()
                    createNewDump()
                             }) {
                               Text("Save")
                             }
            }
            
        }
        .padding()
                .navigationViewStyle(StackNavigationViewStyle())
                                    }
    
    func createNewDump() {
        if newDumpName.count > 0 {
            
            dumpManager.addDump(dumpName: newDumpName)
        }
        newDumpName = ""
        self.presentationMode.wrappedValue.dismiss()
    }
                                    
}
                                
//        struct NewDumpView_Previews: PreviewProvider {
//            @ObservedObject var dumpManager: DumpManager = DumpManager()
//            @Binding var dumpList: [Dump]
//                    static var previews: some View {
//                       
//                        NewDumpView(newDumpName: "", dumpList: $dumpList)
//                    }
//                }

Si j’arrête la build et que je reload, je vois que les éléments s’ajoutent et s’effacent correctement de la base de données Core Data, mais ils ne s’ajoutent pas à la vue en temps réel, ni dans le simulateur, ni sur mon iPhone, donc je suppose que le binding est problématique au niveau de la génération de la liste, qui ne reflète pas les changements en temps réel.

J’espère être assez clair ?

Bonjour,

Une (ou la) source du problème est que dans NewDumpView, tu recrées une nouvelle instance de DumpManager; c’est cette instance que tu vas mettre à jour alors que l’instance de DumpManager utilisée par ta vue BrainDumpView ne sera pas mis à jour.

Il faut que, quand tu ajoutes un Dump, tu mettes à jour la variable dumpList passée en @Binding dans NewDumpView, ce qui permettra à BrainDumpView d’en tenir compte.

Dans ton code actuel, tu mets à jour une nouvelle variable dumpList qui n’est pas reliée à BrainDumpView, il n’y a donc aucune chance que BrainDumpView puisse s’en apercevoir.

Pas le temps de te dire quelque chose de plus précis à ce stade !
Cordialement,
Nicolas

En complément, pour éviter tout problème de ce type, supprime dans BrainDumpView

@State var theFuckingList = DumpManager().dumpList

Merci :slight_smile: Tout fonctionne correctement maintenant ! Effectivement j’avais mal compris la relation entre les différents éléments, notamment le fait que je créais à chaque fois de nouvelles instances !

Content pour toi !!!

1 « J'aime »