Dans un SwiftUI View
j'ai un List
basé sur l' @FetchRequest
affichage des données d'une Primary
entité et l' Secondary
entité connectée via la relation via . Le View
et son List
est mis à jour correctement lorsque j'ajoute une nouvelle Primary
entité avec une nouvelle entité secondaire associée.
Le problème est que lorsque je mets à jour l' Secondary
élément connecté dans une vue détaillée, la base de données est mise à jour, mais les modifications ne sont pas reflétées dans la Primary
liste. Évidemment, le @FetchRequest
n'est pas déclenché par les changements dans une autre vue.
Par la suite, lorsque j'ajoute un nouvel élément dans la vue principale, l'élément précédemment modifié est enfin mis à jour.
Pour contourner ce problème, je mets également à jour un attribut de l' Primary
entité dans la vue détaillée et les modifications se propagent correctement dans la Primary
vue.
Ma question est: comment puis-je forcer une mise à jour sur tous les éléments liés @FetchRequests
à SwiftUI Core Data? Surtout, quand je n'ai pas d'accès direct aux entités liées / @Fetchrequests
?
import SwiftUI
extension Primary: Identifiable {}
// Primary View
struct PrimaryListView: View {
@Environment(\.managedObjectContext) var context
@FetchRequest(
entity: Primary.entity(),
sortDescriptors: [NSSortDescriptor(key: "primaryName", ascending: true)]
)
var fetchedResults: FetchedResults<Primary>
var body: some View {
List {
ForEach(fetchedResults) { primary in
NavigationLink(destination: SecondaryView(primary: primary)) {
VStack(alignment: .leading) {
Text("\(primary.primaryName ?? "nil")")
Text("\(primary.secondary?.secondaryName ?? "nil")").font(.footnote).foregroundColor(.secondary)
}
}
}
}
.navigationBarTitle("Primary List")
.navigationBarItems(trailing:
Button(action: {self.addNewPrimary()} ) {
Image(systemName: "plus")
}
)
}
private func addNewPrimary() {
let newPrimary = Primary(context: context)
newPrimary.primaryName = "Primary created at \(Date())"
let newSecondary = Secondary(context: context)
newSecondary.secondaryName = "Secondary built at \(Date())"
newPrimary.secondary = newSecondary
try? context.save()
}
}
struct PrimaryListView_Previews: PreviewProvider {
static var previews: some View {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
return NavigationView {
PrimaryListView().environment(\.managedObjectContext, context)
}
}
}
// Detail View
struct SecondaryView: View {
@Environment(\.presentationMode) var presentationMode
var primary: Primary
@State private var newSecondaryName = ""
var body: some View {
VStack {
TextField("Secondary name:", text: $newSecondaryName)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
.onAppear {self.newSecondaryName = self.primary.secondary?.secondaryName ?? "no name"}
Button(action: {self.saveChanges()}) {
Text("Save")
}
.padding()
}
}
private func saveChanges() {
primary.secondary?.secondaryName = newSecondaryName
// TODO: ❌ workaround to trigger update on primary @FetchRequest
primary.managedObjectContext.refresh(primary, mergeChanges: true)
// primary.primaryName = primary.primaryName
try? primary.managedObjectContext?.save()
presentationMode.wrappedValue.dismiss()
}
}
ObservableObject
?Réponses:
Vous avez besoin d'un éditeur qui générerait un événement sur les changements de contexte et une variable d'état dans la vue principale pour forcer la reconstruction de la vue lors de l'événement de réception de cet éditeur.
Important: la variable d'état doit être utilisée dans le code du générateur de vue, sinon le moteur de rendu ne saurait pas que quelque chose a changé.
Voici une simple modification de la partie affectée de votre code, qui donne le comportement dont vous avez besoin.
la source
RefreshView(toggle: Bool)
avec un seul EmptyView dans son corps. L'utilisationList {...}.background(RefreshView(toggle: self.refreshing))
fonctionnera.J'ai essayé de toucher l'objet principal dans la vue de détail comme ceci:
Ensuite, la liste principale sera mise à jour. Mais la vue de détail doit connaître l'objet parent. Cela fonctionnera, mais ce n'est probablement pas la manière SwiftUI ou Combine ...
Éditer:
Sur la base de la solution de contournement ci-dessus, j'ai modifié mon projet avec une fonction de sauvegarde globale (managedObject :). Cela touchera toutes les entités liées, mettant ainsi à jour toutes les @ FetchRequest pertinentes.
la source