Search code examples
swiftswiftuiswiftdata

SwiftData mode does not update the view when I insert child records


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?
    …

Solution

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

    @Model
    final class Court {
        var tournament: Tournament?
        …