1

This is more of a code architecture question.

I have a Game View with a deck of 52 cards. The model is:

struct Card {
    let suit: String
    let value: String
}

The simplified ViewModel is:

class GameViewModel: ObservableObject {
    let numberOfCards: Int
    @Published var cards: [Card] = []

    init(numberOfCards: Int) {
        self.numberOfCards = numberOfCards
        for _ in 0..<numberOfCards {
            cards.append(Card(suit: "", value: ""))
        }
    }
//Other logic methods
}

And the View:

struct GameView: View {
    @StateObject var gameVM = GameViewModel(numberOfCards: 52)
...
}

I've kept all game logic in GameViewModel, even manipulated cards via its self.cards[i], but now I've realized that I will need more control over each Card and thus probably a ViewModel for each Card.

Question: is creating 52 more ViewModels a good MVVM design pattern? Or one View = one ViewModel?

And where should I create Cards ViewModels? In the View? Or GameViewModel should create them like:

class GameViewModel: ObservableObject {
    let numberOfCards: Int    
    @Published var cards: [CardViewModel] = []

    init(numberOfCards: Int) {
        self.numberOfCards = numberOfCards
        for _ in 0..<numberOfCards {
            cards.append(CardViewModel(suit: "", value: ""))
        }
    }
}

P.S. I feel like this question can be expanded to any example where we have One-Many relations like CategoryVM->Items, or GroupVM->Individuals, or FamilyVM->People.

Stan
  • 77
  • 1
  • 7
  • Put the game logic in a Game model. Make ViewModels that format just the bits of the model that each View needs – Shadowrun Sep 11 '22 at 07:02
  • I'm afraid this is opinion-based question. In my opinion 52 instance is not an issue, as long as this is what you really need and you keep roles & responsibilities separation clean. – Dima G Sep 11 '22 at 09:51
  • https://stackoverflow.com/questions/4807101/viewmodel-to-viewmodel-communication – lorem ipsum Sep 11 '22 at 10:53

1 Answers1

0

You can add funcs to the Card, e.g.

struct Card {
    let suit: String
    let value: String
    var somethingYouWantToChange
 
    mutating func someMethod() {

    }
}

In swiftUI we usually use the names "Model" or "Store" for the single object that stores the model structs, e.g.

class GameModel: ObservableObject {
    @Published var cards: [Card] = []

When you want to pass the card along and require write access, we use @Binding which converts the View struct into a View model struct, e.g.

struct CardView: View {
    @Binding var card: Card

    var body: some View {
        Text(card.value, format: .number) // Text automatically reformats the UILabel when region settings change.
        Button("Some action") {
            card.someMutatingFunc()
        }
    }

It's passed in like:

CardView(card: $card)

ForEach is usually the view model that creates and manages the bindings, e.g.

ForEach($gameModel.cards) $card in {

To make this word you have to make Card conform to Identifiable and give it an id var, usually let id = UUID() but in your case it might be var id: String { suit + value }.

malhal
  • 26,330
  • 7
  • 115
  • 133
  • Thank you! 1. Doesn't adding methods to a Model break the idea of MVVM? That data and code layers should be separated? 2. I wish to use Combine and it will be hard to use it in a struct as I can't make structs variables @Published. – Stan Sep 11 '22 at 16:41
  • 1
    MVVM objects aren't really compatible with SwiftUI it would slow it down and cause bugs. I recommend learning structs and property wrappers state and binding which are designed to completely replace view controllers. – malhal Sep 11 '22 at 19:58