I am inserting a new item into the modelContext (while on the single Deck) and the view is supposed to update automatically, but it does not.
It refreshes only when I navigate back from a single Deck view to the list of Decks view. I am not sure what I am doing wrong here.
*It also does not work when I delete a card from the deck
It just seems like I am passing a value instead of a reference somewhere. But I cannot spot where.
My Views structure looks like this:
- Decks View (list of decks)
- Deck View (list of cards in a deck – I am adding a new card from this view)
- Card View (single card)
- Deck View (list of cards in a deck – I am adding a new card from this view)
private func addCard() {
let newCard = Card(
front: front,
deck: cardModel.deck!
)
withAnimation {
modelContext.insert(newCard)
}
}
CardModel stores the deck (I make sure the Deck is not nil before I try to insert a new Card into context):
@Observable
final class CardModel {
var deck: Deck? = nil
public func showAddSheet(deck: Deck) -> Void {
self.deck = deck
}
}
Deck model:
@Model
final class Deck {
@Relationship(.cascade)
var cards: [Card]? = [Card]()
var name: String = ""
init(name: String) {
self.cards = [Card]()
self.name = name
}
}
Card model:
@Model
final class Card {
@Relationship(inverse: Deck.cards)
var deck: Deck? = nil
var front: String = ""
init(front: String, deck: Deck) {
self.deck = deck
self.front = front
}
}
Router class:
enum Path: Hashable {
case decks
case deck(Deck)
}
@Observable
final class RouterModel {
var path: [Path] = [Path]()
}
Decks view:
struct DecksView: View {
@Query() var decks: [Deck]
var body: some View {
List {
NavigationStack(path: $routerModel.path) {
ForEach(decks, id: .self) { deck in
NavigationLink(value: Path.deck(deck)) {
Text(deck.name)
}
}
}
}
.navigationDestination(for: Path.self, destination: { path in
switch path {
case .decks:
DecksView()
case let .deck(deck):
DeckView(deck: deck)
case let .card(card):
CardView(card: card)
}
})
}
}
Deck view:
struct DeckView: View {
var deck: Deck
var body: some View {
List {
ForEach(cards) { card in
VStack {
Text(card.front)
}
}
}
}
}
2
Answers
Views in SwiftUI are kind of "static" and are constructed, destroyed and reconstructed by the system multiple times in the view rendering cycle.
To be really really fast in this process, all SwiftUI views are a
struct
, and by definition, structs in Swift always have their values passed by copy. Keep that in mind when designing a SwiftUI app.In your case the
DeckView
doesn’t have any clue that it’s part of a bigger architecture and that thedeck
property might change. Only theDecksView
has this context. Once it navigates to aDeckView
child the view doesn’t receive updates andDecksView
, along with its properties will probably be destroyed as soon as it leaves the foreground.A good alternative would be
DeckView
to have its own@Query
and you only pass anid
to its constructor and it queries itself. This way, any changes trigerred on the dataDeckView
is using will trigger a new render with updated data.You’re missing a
Query
for Cards but SwiftData doesn’t currently support querying for relationships, i.e. the cards in a deck, so instead you need to use Core Data@FetchRequest
with a predicate for the relationship instead. It’s quite complicated to run Core Data and Swift Data side by side so you might be better just using Core Data because there will likely be other features you need that are missing.