Home > Mobile >  SwiftUI FetchRequest predicate with data from an ObservedObject causes property initialisers run bef
SwiftUI FetchRequest predicate with data from an ObservedObject causes property initialisers run bef

Time:10-28

I use a CoreData Model where a Group Object has GroupMembers(firstName: String) connected with the entity members. GroupMembers have a corresponding property group that connects back to the Group Object. In my Detailview I transfer an group Object. In a FetchRequest I want to filter all GroupMembers that have the same Group as my transfered group.

I use the following code:

struct GroupDetailView: View {
    @ObservedObject var group: Group
    
    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \GroupMember.firstName_, ascending: true)],
        predicate: NSPredicate(format: "group == %@", group),
        animation: .default)
    private var members: FetchedResults<GroupMember>
    
    var body: some View {

    }
}

But I get the following error:

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

CodePudding user response:

It's not straightforward at the moment. You could experiment with defaulting to a false predicate (so no results are returned) and then setting the predicate in onAppear and onChange(group). Or perhaps in task(id: group) which should do both.

.onAppear {
    members.nsPredicate = NSPredicate(format: "group == %@", group)
}

However, this has the major flaw that if your GroupDetailView is init again (without the group changing) the dynamically set predicate is lost. Sadly this is a major design flaw in @FetchRequest, it seems to me they expected it to always be used in a top level ContentView that would never be re-init, i.e. before we got the SwiftUI App Lifecycle.

A hacky workaround I've had some success with is to set the predicate at the top of body, that way it always has the correct predicate. We are not supposed to update state from inside body but you'll notice the comment on nsPredicate setter is marked as nonmutating so maybe it is ok. Give this a try and see if it works for you:

struct GroupDetailView: View {
    let group: Group
    
    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \GroupMember.firstName, ascending: true)],
        predicate: NSPredicate(value: false), // this essentially stops the fetch from working bypassing the database.
        animation: .default)
    private var members: FetchedResults<GroupMember>
    
    var body: some View {
        let _ = members.nsPredicate = NSPredicate(format: "group == %@", group)

        ForEach(members) { member in {

        }
    }

Another, workaround would be to move the @FetchRequest into the parent View and update the predicate when a group is selected and pass the results (i.e. the wrappedValue) into your GroupDetailView however that has the same problem that if the parent of that parent view is re-init then the predicate is still lost.

Another possible way (I have not tried yet) is to use the @FetchRequest(fetchRequest:) initialiser, passing in an NSFetchRequest object that is a global or a singleton and configured from an action in the parent View. That way it's predicate won't be lost. However that goes against the design of the new feature to dynamically update the predicate.

  • Related