0

Is there any way to force the refresh of a FetchedResults, so the view updates?

@FetchRequest(sortDescriptors: [SortDescriptor(\.label)])
    var tokens: FetchedResults<Token>

In the body of the view I call:

let _ = tokens.nsPredicate = NSPredicate(format:  "NOT (id IN %@)", scenario.scenarioTokenArray.compactMap { $0.token?.id?.uuidString })

..and loop through the tokens. When one of the tokens is selected I add it to the scenario relationship:

func addTokenToScenario(token: Token) {

    let scenarioToken = ScenarioToken(context: moc)
    scenarioToken.token = token
    scenario.addToScenarioToken(scenarioToken) 
}

It does add the token to the scenario through the intermediate table (ScenarioToken), but I want it to immediately be removed from the list. The update doesn't trigger a published change in the observable object:

@ObservedObject var scenario: Scenario

...because of the nested observable objects challenge, even though the getTokenIds does return the new list properly...if it were called. I tested this to see:

func getTokenIds() -> [String] {
    scenario.scenarioTokenArray.compactMap { $0.token?.id?.uuidString }
}

Again, even though Scenario is an ObservedObject this part of the predicate does not get called:

scenario.scenarioTokenArray.compactMap

I read this post, but I am using an observable object. My problem relates to nested observable objects. I have found my way around the challenge by following the advice to "look closely at your views, and revise them to make more, and more targeted views" for most of the challenges. In this case however I cannot because the scenario (which is the observable object) is needed when adding tokens it (creating relationships).

UPDATED

Core data model. Essentially Scenario has 1:many with ScenarioTokens which has a many:1 with Token.

I put this at the top of the view and it updates appropriately when a token is selected. It's just the @FetchRequest doesn't re-run:

Text("\(getTokenIds().joined(separator: ", "))")
    .font(.footnote)
lcj
  • 1,355
  • 16
  • 37
  • It's not clear from your question, but if `Scenario` has a published property of a token array, why not update the predicate in an `onChange` of that? Doing "stuff" in the body of a view is not a good idea. You can also get a binding to the configuration which you can pass to the scenario and have it update when the model changes. – jrturton Jul 01 '23 at 20:15
  • Scenario is a core data entity so I believe all are observable and published, right? At least that's the way they behave in my experience. – lcj Jul 01 '23 at 21:31
  • @jrturton, from the nested object post: "The issue is when you update that nested element’s property, even though it’s listed as Published, the change doesn’t propagate to the view." – lcj Jul 03 '23 at 12:36
  • Can you include a diagram of your core data model? I can't figure out what you're updating and where without knowing how these types relate to each other – jrturton Jul 03 '23 at 13:03
  • @jrturton, added it. It's very basic. – lcj Jul 03 '23 at 16:08

2 Answers2

1

In trying to reproduce your problem, I couldn't get the predicate to work at all as you had written it (it always returned all tokens), so I replaced it which this one, which I made as a calculated property of Scenario:

public class Scenario: NSManagedObject {
    var unusedTokenPredicate: NSPredicate {
        NSPredicate(format: "NOT (SELF IN %@)", scenarioTokens.map { $0.token })
    }
}

I use this in the initial setup of the view that holds the available token list for a scenario:

init(_ scenario: Scenario) {
    self.scenario = scenario
    _tokens = FetchRequest(sortDescriptors: [SortDescriptor(\.label)], predicate: scenario.unusedTokenPredicate)
}

Having used that updated predicate, it would sort-of-work for the first token selected, but then stop updating, but if you left the view and came back the correct information would be displayed.

I then added a modifier to the list to update the predicate whenever the scenario is updated (which happens when you add or remove scenario token objects from the scenario):

.onReceive(scenario.objectWillChange) { _ in
    tokens.nsPredicate = scenario.unusedTokenPredicate
}

This updates the list as you select tokens.

jrturton
  • 118,105
  • 32
  • 252
  • 268
  • This was great. Actually all I needed to do was to extend the Scenario as you with your predicate, and I use it in the view body (just after a VStack) and it worked. I had the onReceive in there but removed it, so I'm not sure it is even required. Are you sure you need the .onReceive? I have not been able to solve this problem for over a week. Thank you!!!! – lcj Jul 05 '23 at 13:58
  • doing anything other than returning view types in a view body is A Bad Idea, you don't have control over when or how often the body is executed and you can end up setting off runtime warnings or infinite loops. The onReceive makes your intentions and code clear. – jrturton Jul 05 '23 at 15:05
  • Ok. let me move. – lcj Jul 06 '23 at 15:48
0

You need another @FetchRequest for ScenarioToken and a predicate "scenario == %@, scenario"

malhal
  • 26,330
  • 7
  • 115
  • 133
  • I am looking for all the tokens not used by the passed in Scenario. The predicate pulls the right record, just only on the initial call, not after they are selected from this list. – lcj Jul 02 '23 at 10:28