Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
570 views
in Technique[技术] by (71.8m points)

core data - How to update @FetchRequest, when a related Entity changes in SwiftUI?

In a SwiftUI View i have a List based on @FetchRequest showing data of a Primary entity and the via relationship connected Secondary entity. The View and its List is updated correctly, when I add a new Primary entity with a new related secondary entity.

The problem is, when I update the connected Secondary item in a detail view, the database gets updated, but the changes are not reflected in the Primary List. Obviously, the @FetchRequest does not get triggered by the changes in another View.

When I add a new item in the primary view thereafter, the previously changed item gets finally updated.

As a workaround, i additionally update an attribute of the Primary entity in the detail view and the changes propagate correctly to the Primary View.

My question is: How can I force an update on all related @FetchRequests in SwiftUI Core Data? Especially, when I have no direct access to the related entities/@Fetchrequests?

Data Structure

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()
    }
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I also struggled with this and found a very nice and clean solution:

You have to wrap the row in a separate view and use @ObservedObject in that row view on the entity.

Here's my code:

WineList:

struct WineList: View {
    @FetchRequest(entity: Wine.entity(), sortDescriptors: [
        NSSortDescriptor(keyPath: Wine.name, ascending: true)
        ]
    ) var wines: FetchedResults<Wine>

    var body: some View {
        List(wines, id: .id) { wine in
            NavigationLink(destination: WineDetail(wine: wine)) {
                WineRow(wine: wine)
            }
        }
        .navigationBarTitle("Wines")
    }
}

WineRow:

struct WineRow: View {
    @ObservedObject var wine: Wine   // !! @ObserveObject is the key!!!

    var body: some View {
        HStack {
            Text(wine.name ?? "")
            Spacer()
        }
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...