0

I'm building an app using SwiftUI / Combine and trying to do so in an MVVM pattern. I'm getting a little confused as to how best to expose certain properties and in particular, in relation to the Core Data implementation.

In the main app file, I have set up an environmnt object as follows (I'll come to why later):

struct Football_GuruApp: App {
    let persistenceController = PersistenceController.shared
    @StateObject var favouritePlayers = FavouritePlayersViewModel()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, persistenceController.container.viewContext)
                .environmentObject(favouritePlayers)
        }
    }
}

The app has 3 main views:

ContentView: this contains a TabView with 2 subviews: FetchedResultsView() and FavouritesView()

FetchedResultsView: this contains a subview FetchedPlayers() which looks like this:

struct FetchedPlayersView: View {
    @EnvironmentObject var fetchedResultsVM: FetchedResultsViewModel
    
    var body: some View {
        Section(header: Text("Players")) {
            ForEach(fetchedResultsVM.fetchedPlayers, content: { player in
                PlayerView(player: player)
            })
            
            if fetchedResultsVM.playersExpandable {
                MoreResultsButton(action: fetchedResultsVM.getMorePlayers, buttonTitle: "More players")
            }
        }
    }
}

And finally FavouritesView:

struct FavouritesView: View {
    @EnvironmentObject var favouritePlayersVM: FavouritePlayersViewModel
    var context = PersistenceController.shared.container.viewContext
    
    var body: some View {
        List {
            ForEach(context.fetchAll(PlayerCD.self)) { player in
                PlayerView(player: PlayerViewModel.mapFromCoreData(player: player))
            }
        }
    }
}

Within the PlayerView() (subview of FetchedPlayersView) we have a button:

FavouritesButton(playerViewModel: player)

When tapped we set a property on the PlayerViewModel to true:

playerViewModel.favourite = true

And then a didSet method on PlayerViewModel triggers the player to be stored to core data:

var favourite = false {
    didSet {
        self.mapToCoreData()
    }
}

   func mapToCoreData() {
        let storedPlayer = PlayerCD(context: context)
        
        storedPlayer.id = self.id
        storedPlayer.firstName = self.firstName
        storedPlayer.secondName = self.secondName
        
        try? PersistenceController.shared.container.viewContext.save()
        favouritePlayersVM.updateFavourites()
    }

We have the following env object on the PlayerViewModel

@EnvironmentObject var favouritePlayersVM: FavouritePlayersViewModel

Finally, FavouritePlayersViewModel looks like this:

class FavouritePlayersViewModel: ObservableObject {
    @Published var players = [PlayerViewModel]()

func updateFavourites() {
    let context = PersistenceController.shared.container.viewContext
    let savedPlayers = context.fetchAll(PlayerCD.self)
    self.players = [PlayersViewModel]()
    
    savedPlayers.forEach {
        players.append(PlayerViewModel.mapFromCoreData(player: $0))
        }
    }
}

So the idea is that when the button is tapped, we store to core data and then at the same time we update the players list on FavouritePlayersViewModel which should then update the FavouritesView with the latest players. However, clearly I am struggling with the implementation of the environment object. I had thought that by exposing this at the root of the app, I would be able to access everywhere, but I guess that as PlayerViewModel, for example, is not a direct descendent, I can't access (as I'm getting a crash).

Perhaps using the env object is not the best fit here, but I'm struggling to work out how best to have it so that I can update the FavouritesViewModel players list from the PlayerViewModel whilst using the same instance of this FavouritesViewModel to update the FavouritesView.

By the same token, I'm also not able to access the NSManagedObjectContext that I set as @Environment in the root file in the view models either which is why I'm using the singleton of the persistent container to store my core data, which is not what I really wanted to do.

DevB1
  • 1,235
  • 3
  • 17
  • 43

0 Answers0