Search code examples
swiftuicore-data

Is there a way to force a FetchedResult refresh other than using an @ObservedObject which isn't working due to many to many nesting


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)

Solution

  • 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.