0

Edit with minimal reproducible example:

@main
struct SwiftDataTestApp: App {
 var sharedModelContainer: ModelContainer = {
 let schema = Schema([
 Team.self, Player.self
 ])
 let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
 do {
 return try ModelContainer(for: schema, configurations: [modelConfiguration])
 } catch {
 fatalError("Could not create ModelContainer: \(error)")
 }
 }()
 var body: some Scene {
 WindowGroup {
 ContentView()
 }
 .modelContainer(sharedModelContainer)
 }
}
struct ContentView: View {
 @Environment(\.modelContext) private var modelContext
 @Query(sort: \Team.name) var teams: [Team]
 @State private var viewModel = ViewModel()
 var body: some View {
 VStack {
 List {
 ForEach(teams) { team in
 Section(header: Text(team.name)) {
 ForEach(team.players) { player in
 Text("\(player.name) #\(player.number)")
 }
 }
 }
 }
 Button(action: {
 let player1 = Player(name: "John", number: 1)
 let player2 = Player(name: "Joe", number: 2)
 let team = Team(name: "Team1", players: [player1, player2])
 modelContext.insert(team)
 try? modelContext.save()
 }, label: {
 Text("initialize")
 })
 Button(action: testChangeNumber) {
 Label("Change player number", systemImage: "plus")
 }
 }
 }
 private func testChangeNumber() {
 if let team = teams.first, let player = team.players.first {
 viewModel.updatePlayer(playerID: player.id, teamID: team.id)
 }
 }
 
}
@MainActor
@Observable class ViewModel {
 private let modelContext: ModelContext = SwiftDataService.shared.getContext()
 
 func updatePlayer(playerID: UUID, teamID: UUID) {
 
 let descriptor = FetchDescriptor<Team>(predicate: #Predicate { 0ドル.id == teamID })
 
 guard let team = try? modelContext.fetch(descriptor).first else {
 return
 }
 
 if let playerIndex = team.players.firstIndex(where: { 0ドル.id == playerID }) {
 let player = team.players[playerIndex]
 player.number = 7
 
 try? modelContext.save()
 }
 }
}
class SwiftDataService {
 private let modelContainer: ModelContainer
 private let modelContext: ModelContext
 
 @MainActor
 static let shared = SwiftDataService()
 
 @MainActor
 private init() {
 self.modelContainer = try! ModelContainer(
 for: Team.self, Player.self,
 configurations: ModelConfiguration(isStoredInMemoryOnly: false)
 )
 self.modelContext = modelContainer.mainContext
 }
 
 func getContext() -> ModelContext {
 modelContext
 }
}
@Model
class Player {
 @Attribute(.unique) var id: UUID
 var name: String
 var number: Int
 
 @Relationship(inverse: \Team.players) var team: Team?
 
 init(id: UUID = UUID(), name: String, number: Int) {
 self.id = id
 self.name = name
 self.number = number
 }
}
@Model
class Team {
 @Attribute(.unique) var id: UUID
 var name: String
 @Relationship var players: [Player]
 
 init(id: UUID = UUID(), name: String, players: [Player] = []) {
 self.id = id
 self.name = name
 self.players = players
 
 for player in players {
 player.team = self
 }
 }
}

First tap "Initialize". This will create a team with two players, John #1 and Joe #2. Then tap the "Change player number" button. The view will not update. However if you restart the app, the first player will now show with the correct #7.

The question is how to get the view to update when a change to an individual player information occurs.

asked Jun 14, 2025 at 20:41
2
  • I cannot reproduce with minimal assumptions. Please show a minimal reproducible example. Commented Jun 14, 2025 at 21:11
  • @Sweeper I added all the code necessary to reproduce. Commented Jun 14, 2025 at 23:50

1 Answer 1

2

What you have there is a very twisted setup to achieve something very trivial.

The short answer is that if you want to update the player number, then just update the player number:

private func testChangeNumber() {
 if let team = teams.first, let player = team.players.first {
 // viewModel.updatePlayer(playerID: player.id, teamID: team.id)
 player.number = 7
 }
}

The above will update the player number in the view right away. It works because SwiftData models are already Observable, and you're making the change from the view itself, so SwiftUI knows to trigger a view update.

Otherwise, adding @Observable to your ViewModel achieves nothing, because the view model has no properties to observe. @Observable doesn't observe functions, it observes changes to properties (that are part of the class itself).

If you really want to update the player via the view model, at least pass the player as a parameter:

func updatePlayer(player: Player) {
 player.number = 7
}

And then in your testChangeNumber function:

viewModel.updatePlayer(player: player)

This should also trigger a view update right away, but it will work the same whether the ViewModel is marked with @Observable or not.

answered Jun 15, 2025 at 2:03
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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.