0

I have a problem with SwiftData and inserting child records. The insert itself works but the views are not updated. If I add a new court record in DetailView, tournament.title in ContentView changes to Tournament with new court but tournament.courts.count is still 1 and not 2, 3, … Also the List in DetailView shows only the initial court and not the new added.

The ist only a view problem. If I start the app again, it looks fine and all added courts are shown.

Any idea how I can refresh the view after adding or changing child records?

This is like my models and code looks like:

Tournament model:

@Model
final class Tournament {
    var title: String
    @Relationship(deleteRule: .cascade, inverse: \Court.tournament) var courts: [Court]
    
    init(title: String, courts: [Court] = []) {
        self.title = title
        self.courts = courts
    }
}

Court model:

@Model
final class Court {
    @Relationship var tournament: Tournament
    var number: Int
    
    init(tournament: Tournament, number: Int) {
        self.tournament = tournament
        self.number = number
    }
}

ContentView:

struct ContentView: View {
    @Environment(\.modelContext) private var modelContext
    @Query var tournaments: [Tournament]
    @State var selectedTournament: Tournament?
    
    var body: some View {
        NavigationSplitView {
            List(selection: $selectedTournament) {
                ForEach(tournaments) { tournament in
                    NavigationLink(value: tournament) {
                        HStack {
                            Text("\(tournament.title)")
                            Spacer()
                            Text("(\(tournament.courts.count))")
                        }
                    }
                }
            }
            .toolbar {
                ToolbarItem {
                    Button(action: {
                        addTournament()
                    }) {
                        Label("Add tournament", systemImage: "plus")
                    }
                }
            }
        } detail: {
            DetailView(tournament: selectedTournament)
        }
    }
    
    private func addTournament() {
        let newTournament = Tournament(title: "New tournament")
        modelContext.insert(newTournament)
        
        let court = Court(tournament: newTournament, number: 1)
        modelContext.insert(court)
        
        try? modelContext.save()
    }
}

DetailView:

struct DetailView: View {
    @Environment(\.modelContext) private var modelContext
    var tournament: Tournament?
    
    var body: some View {
        if let tournament {
            List {
                Section(tournament.title) {
                    ForEach(tournament.courts) { court in
                        Text("\(court.number)")
                    }
                }
            }
            Button {
                addCourt()
            } label: {
                Text("Add court")
            }
        } else {
            emptyView // ContentUnavailableView
        }
    }
    
    func addCourt() {
        if let tournament {
            let newCourt = Court(tournament: tournament, number: tournament.courts.count+1)
            modelContext.insert(newCourt)
        
            tournament.title = "Tournament with new court"
        
            try? modelContext.save()
        }
    }
}

Update

I found a solution but I don’t know why this works. If I change both relations to optional, the views will be refreshed on adding records.

@Model
final class Tournament {
    var title: String
    @Relationship(deleteRule: .cascade, inverse: \Court.tournament) var courts: [Court]?
    …
}
@Model
final class Court {
    var tournament: Tournament?
    …
2
  • 1
    You only need to make tournament in Court optional- Commented Aug 4, 2024 at 17:20
  • Thanks! You are right and with this it’s easier to handle the courts. Commented Aug 4, 2024 at 20:58

3 Answers 3

2

The problem is solved with defining the tournament property in Court as optional.

@Model
final class Court {
    var tournament: Tournament?
    …
Sign up to request clarification or add additional context in comments.

2 Comments

If this answer solved the problem then mark it as accepted please so it’s clear the question has been properly answered.
Done! But I was able to mark the correct answer after three days.
0

This is a bit tricky.

Try to take the set out of the tournament add the court to the set and use tournament.courts = newCourts

The problem is, that the setter is on the court side. I’m actually not sure if it helps, but this is how I understand the underlying fetch result.

5 Comments

Settings tournament.courts directly will cause a fatal error: Fatal error: Unsupported relationship key path ReferenceWritableKeyPath<Court, Tournament>
Oh man, check this one out: stackoverflow.com/questions/76987282/…
Or try to mark it as bindable.
Thanks, but I know this answer and the given answer is wrong. Any @Model is also @Observable by default. There is no different if I wrap the model in a view model or not. The result is the same. What did you mean with bindable in context of a SwiftData model?
Sometimes it’s enough to do @Bindable let tournament before passing
0

You are missing another @Query for courts with a predicate to find the courts in the specific tournament, e.g.


    @Query var courts: [Court]

    init(tournamentID: Tournament.ID) {
        let predicate = #Predicate<Court> {
            $0.tournament.persistentModelID = tournamentID
        }
        _courts = Query(filter: predicate, sort: \Court.date)
    }

8 Comments

@Query gets all courts but I need only the courts for the current tounament record. So this is not the right way. See my update at the end of the question.
No your @Query is for tournaments, you are missing another @Query for courts
Please look at the code. There is a relationship between Tournament and Court. With @Query I can’t get the related courts for this tournament. BTW: I have updated the question and the solition for the problem is that one oft the realtionships must be optional.
You can use the Query predicate for that
I can’t use the predicate in this case and there is no reason to add an additional @Query. I’ve posted the solution as extra answer to my question. The problem was that the one-property of a one-to-many relationship must be optional.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.