swiftswiftuiscopeviewmodelenvironmentobject

SwiftUI How to have class viewable from all views


I am fairly new to SwiftUI and I am trying to build an app where you can favorite items in a list. It works in the ContentView but I would like to have the option to favorite and unfavorite an item in its DetailView.

I know that vm is not in the scope but how do I fix it? Here is some of the code in the views. The file is long so I am just showing the relevant code



struct ContentView: View {
    @StateObject private var vm = ViewModel()

//NavigationView with a List {

//This is the code I call for showing the icon. The index is the item in the list
Image(systemName: vm.contains(index) ? "heart.fill" : "heart")
.onTapGesture{
vm.toggleFav(item: index)
}
}

struct DetailView: View {
Hstack{
Image(systemName: vm.contains(entry) ? "heart.fill" : "heart") //Error is "Cannot find 'vm' in scope"
}
}

Here is the code that that vm is referring to


import Foundation
import SwiftUI

extension ContentView {
    
    final class ViewModel: ObservableObject{
        @Published var items = [Biase]()
        @Published var showingFavs = false
        @Published var savedItems: Set<Int> = [1, 7]
        
        // Filter saved items
        var filteredItems: [Biase]  {
            if showingFavs {
                return items.filter { savedItems.contains($0.id) }
            }
            return items
        }
        
        private var BiasStruct: BiasData = BiasData.allBias
        private var db = Database()
        
        init() {
            self.savedItems = db.load()
            self.items = BiasStruct.biases
        }
        
        func sortFavs(){
            withAnimation() {
                showingFavs.toggle()
            }
        }
        
        func contains(_ item: Biase) -> Bool {
                savedItems.contains(item.id)
            }
        
        // Toggle saved items
        func toggleFav(item: Biase) {
            if contains(item) {
                savedItems.remove(item.id)
            } else {
                savedItems.insert(item.id)
            }
            db.save(items: savedItems)
        }
    }
}

This is the list view... enter image description here

Detail view... enter image description here

I tried adding this code under the List(){} in the ContentView .environmentObject(vm) And adding this under the DetailView @EnvironmentObject var vm = ViewModel() but it said it couldn't find ViewModel.


Solution

  • To put the view model inside the ContentView struct is wrong. Delete the enclosing extension.

    If the view model is supposed to be accessed from everywhere it must be on the top level.

    In the @main struct create the instance of the view model and inject it into the environment

    @main
    struct MyGreatApp: App {
        @StateObject var viewModel = ViewModel()
            
        var body: some Scene {
            WindowGroup {
                ContentView()
                    .environmentObject(viewModel)
                    
            }
        }
    } 
    

    And in any struct you want to use it add

    @EnvironmentObject var vm : ViewModel
    

    without parentheses.