-1

I am having some issues using SwiftUI whith CoreData. My app has a list of items coming from a core-data entity.

The initial (template) code -provided by Xcode when starting a project- works.

But problems come when I want to set a predicate in order to select which items ought to be listed.

At this point I can have the app start with a given selection. In other words I am able to set a predicate to begin with.

Problems appear when the predicate should be updated while the app is running to select a different set of items to be listed.

Here is how the code currently looks like:

import SwiftUI
import CoreData

struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext
    .....
    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \TheEntity. sortField, ascending: true)],
        animation: .default)

    private var items: FetchedResults<TheEntity>
    @FetchRequest var items: FetchedResults<TheEntity>
    .....
    init() {
        let cntxt = PersistenceController.shared.container. viewContext,
            theMedia = HearText.currentMedia(inMOContext: cntxt),
            predicate = NSPredicate(format: "media==%@", theMedia)

        self._items = FetchRequest(entity: TheEntity.entity(),
                                   sortDescriptors: 
                                    [NSSortDescriptor(keyPath:  \TheEntity.sortField,
                                                      ascending:    true)],
                                   predicate: predicate)
    }
    .....
}

And it is working, except that when currentMedia() returns a different value due to some action inside the app the list is not updated accordingly.

Though I have tried out some solutions, I have at this point nothing working. There must be a way to solve this, but my main problem is that it is impossible to use any instance variable inside init(). Any relevant tip on how to handle this issue would be highly appreciated.

Michel
  • 10,303
  • 17
  • 82
  • 179
  • The main question here is that `currentMedia` is a function you call but how do you know when to call it, how do you know it will return a different result? I would look into moving the fetch request into a class conforming to ObservableObject and make the result array a @Published property but I have no idea how to make that work because of my previous question. Side note, please don't add extra spaces in your code specially not after `.` since it makes the code harder to read and also in the init you get a viewContext which is pointless since you have viewContext as an @Environment property – Joakim Danielson Oct 10 '21 at 07:06
  • I know when simply because I know in my app what causes the change (This is the easy part of the problem). Your comment about viewContext as an @Environment property does not work here ()because there is no access to instance variables inside init. This is exacly why I do it that way. – Michel Oct 10 '21 at 08:58
  • 1
    Something like this - https://www.hackingwithswift.com/books/ios-swiftui/dynamically-filtering-fetchrequest-with-swiftui maybe? – shufflingb Oct 10 '21 at 10:19
  • Yes, this is exactly the kind of thing I needed. I just solved my problem following this. – Michel Oct 10 '21 at 10:55
  • 1
    I looked at that article but didn't think it would help here, good that it did though. And what is easy for you isn't so easy for us when you haven't explained it, not so important now but maybe think about that for any future question. Finally, if the question has been answered then it's best to either post your solution or remove the question. – Joakim Danielson Oct 10 '21 at 11:27
  • https://developer.apple.com/wwdc21/10017 – lorem ipsum Oct 10 '21 at 13:24
  • 1
    @Joakim_Danielson. You're totally right. Actually I had to customize the article for my needs but it obviously gave me the right impulse. And I was going to post my own answer mentioning this artlicle. But this comment came in the meanwhile, so I wanted to be fare and give it credit. – Michel Oct 11 '21 at 13:31

1 Answers1

0

My guess is that currentMedia() uses .items without showing SwiftUI the dependency between them.

You can pass the items variable into the function currentMedia(items: items) instead of accessing the items variable directly from inside the currentMedia function. This creates a dependency between items and the function, making SwiftUI re-call currentMedia in case the items variable gets refreshed. Note that this works because .items notifies SwiftUI in case it is used inside a View body.

It is similar with dynamic filters that depend on user input. If the filter is applied with a predicate then change the predicate on the @FetchRequest. If the filter works on the resulting items then store the filter variables inside @State or @Published variables and also pass them into currentMedia to see currentMedia called when they change. Storing the filter variables in @State or @Published and using them inside a View body enables SwiftUI to see change notifications and makes SwiftUI re-evaluate the View body, allowing the View to re-draw on filter changes.

Fabian
  • 5,040
  • 2
  • 23
  • 35