0

How can I use @FetchRequest in SwiftUI with a fetch request based on a variable being passed in from a parent view, while also ensuring that the view updates based on Core Data changes?


I have this issue where using @FetchRequest in SwiftUI while passing in a variable to the fetch request doesn't work. It results in the following error:

Cannot use instance member 'name' within property initializer; property initializers run before 'self' is available

struct DetailView: View {
    @FetchRequest(fetchRequest: Report.fetchRequest(name: name), animation: .default)
    private var data: FetchedResults<Report>

    let name: String

    var body: some View {
        Text("\(data.first?.summary.text ?? "")")
            .navigationTitle(name)
            .onAppear {
                ReportAPI.shared.fetch(for: name) // Make network request to get Report data then save to Core Data
            }
    }
}
extension Report {
    @nonobjc public class func fetchRequest(name: String) -> NSFetchRequest<Report> {
        let fetchRequest = NSFetchRequest<Report>(entityName: "Report")

        fetchRequest.fetchLimit = 1
        fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Report.createdAt, ascending: true)]
        fetchRequest.predicate = NSPredicate(format: "details.name == %@", name)

        return fetchRequest
    }
}

This view is pushed using a NavigationLink from a previous view. Like: NavigationLink(destination: DetailView(name: item.name)).

I've looked at this answer and I don't think that it will work for my use case since normally the @FetchRequest property wrapper automatically listens for data changes and updates automatically. Since I'm doing an async call to make a network request and update Core Data, I need the data variable to update dynamically once that is complete.


I've also looked at this answer but I think it has some of the same problems as mentioned above (although I'm not sure about this). I'm also getting an error using that code on the self.data = FetchRequest(fetchRequest: Report.fetchRequest(name: name), animation: .default) line. So I'm not sure how to test my theory here.

Cannot assign to property: 'data' is a get-only property

struct DetailView: View {
    @FetchRequest
    private var data: FetchedResults<Report>

    let name: String

    var body: some View {
        Text("\(data.first?.summary.text ?? "")")
            .navigationTitle(name)
            .onAppear {
                ReportAPI.shared.fetch(for: name) // Make network request to get Report data then save to Core Data
            }
    }

    init(name: String) {
        self.name = name
        self.data = FetchRequest(fetchRequest: Report.fetchRequest(name: name), animation: .default)
    }
}

Therefore, I don't think that question is the same as mine, since I need to be sure that the view updates dynamically based on Core Data changes (ie. when the fetch method saves the new data to Core Data.

Charlie Fish
  • 18,491
  • 19
  • 86
  • 179
  • 1
    IMO `@FetchRequest` is pretty awful. Put your logic in the model where it belongs – Paulw11 Jul 25 '21 at 00:14
  • Until iOS15 `@FetchRequest` is not dynamic. See the What is new in SwiftUI WWDC 2021 video – lorem ipsum Jul 25 '21 at 14:22
  • @loremipsum At 13:15 of the `What's new in SwiftUI` WWDC 21 video, it talks about both Tables with sortDescriptors and sectioned fetch requests. Nothing about using a variable passed into the `@FetchRequest`. Am I missing something? – Charlie Fish Jul 25 '21 at 15:38
  • I would use @FetchRequest in views only for rapid prototyping. Refactor it into a Model or ViewModel and make it testable and dependency injectable. You will be much faster in developing individual components than trying to stir all the things together into one view. This also solves your problem. – CouchDeveloper Jul 25 '21 at 15:45

0 Answers0