skip to Main Content

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)
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


  1. 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 the deck property might change. Only the DecksView has this context. Once it navigates to a DeckView child the view doesn’t receive updates and DecksView, 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 an id to its constructor and it queries itself. This way, any changes trigerred on the data DeckView is using will trigger a new render with updated data.

    Login or Signup to reply.
  2. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search