How do I pass in an Environment Object to my ViewModel SwiftUI
EnvironmentObjects must be supplied by anscestor views!
import SwiftUI
@main
struct pro2App: App { // <<: Here: your app name!
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(MealViewModel()) // <<: Here!
.environmentObject(UserInfoModel.shared) // <<: Here!
}
}
}
class MealViewModel: ObservableObject {
@Published var nutrients: [RecipieAPI] = []
let person: UserInfoModel.shared // <<: Here
@State public var fat = 0
@State public var carbs = 0
@State public var protein = 0
init() {
fetchNutrients()
}
func fetchNutrients() {
NetworkServices.fetchNutrients(maxProtein: self.person.recipeNutrientsSearch.protein , maxFat: self.person.recipeNutrientsSearch.fat, maxCarbs: self.person.recipeNutrientsSearch.carb, number: 4) { (nutrients, error) in
if let error = error {
print(error)
} else {
if let nutrientList = nutrients as? [RecipieAPI] {
self.nutrients = nutrientList
}
}
}
}
}
class UserInfoModel: ObservableObject {
static let shared: UserInfoModel = UserInfoModel() // <<: Here
struct UserInfo: Identifiable {
var id = UUID()
var firstName: String
var height: Double
var weight: Double
var gender: String
var age: Double
var activityLevel: String
var BMR: Double
}
struct AddedFoods:Identifiable{
var name: String = ""
var totalCals: Double = 0
var totalProtein: Double = 0
var totalCarbs: Double = 0
var totalFat: Double = 0
var id = UUID().uuidString
//Your other properties
}
struct DailyCalorieGoals: Identifiable{
var id = UUID()
var calorieGoal: Double
var fatGoal: Double
var proteinGoal: Double
var carbGoal: Double
}
struct CurrentCalorieProgress: Identifiable{
var id = UUID()
var calorieProgress: Double
var fatProgress: Double
var carbProgress: Double
var proteinProgress: Double
}
struct SearchRecipeCalories: Identifiable{
var id = UUID()
var fat: Int
var carb: Int
var protein: Int
}
@Published var personUserInfo = UserInfo.init(firstName: "", height: 0, weight: 0, gender: "", age: 0, activityLevel: "", BMR: 0)
@Published var personDailyCalorieGoals = DailyCalorieGoals.init(calorieGoal: 2400, fatGoal: 40, proteinGoal: 40, carbGoal: 40)
@Published var personCurrentCalorieProgress = CurrentCalorieProgress.init(calorieProgress: 1200, fatProgress: 12, carbProgress: 5, proteinProgress: 30)
@Published var recipeNutrientsSearch = SearchRecipeCalories.init(fat: 0, carb: 0, protein: 0)
}
How to inject a Model from the Environment into a ViewModel in SwiftUI
There are a few different issues here and multiple strategies to handle them.
From the top, yes, you can create your data model at the App
level:
@main
struct DIApp: App {
var dataModel = DataModel()
var body: some Scene {
WindowGroup {
ListView(dataModel: dataModel)
.environmentObject(dataModel)
}
}
}
Notice that I've passed dataModel
explicitly to ListView
and as an environmentObject
. This is because if you want to use it in init
, it has to be passed explicitly. But, perhaps subviews will want a reference to it as well, so environmentObject
will get it sent down the hierarchy automatically.
The next issue is that your ListView
won't update because you have nested ObservableObject
s. If you change the child object (DataModel
in this case), the parent doesn't know to update the view unless you explicitly call objectWillChange.send()
.
struct ListView: View {
@StateObject private var vm: ViewModel
init(dataModel: DataModel) {
_vm = StateObject(wrappedValue: ViewModel(dataModel: dataModel))
}
var body: some View {
NavigationView {
List {
ForEach(vm.dataModel.items) { item in
Text(item.title)
}
}
.navigationTitle("List")
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(trailing:
Button(action: {
addItem()
}) {
Image(systemName: "plus.circle")
}
)
}
.navigationViewStyle(StackNavigationViewStyle())
}
func addItem() {
vm.addRandomItem()
}
}
extension ListView {
class ViewModel: ObservableObject {
let dataModel: DataModel
init(dataModel: DataModel) {
self.dataModel = dataModel
}
func addRandomItem() {
let newID = Int.random(in: 100..<999)
let newItem = Item(id: newID, title: "New Item \(newID)")
dataModel.addItem(newItem)
self.objectWillChange.send()
}
}
}
An alternate approach would be including DataModel
on your ListView
as an @ObservedObject
. That way, when it changes, the view will update, even if ViewModel
doesn't have any @Published
properties:
struct ListView: View {
@StateObject private var vm: ViewModel
@ObservedObject private var dataModel: DataModel
init(dataModel: DataModel) {
_dataModel = ObservedObject(wrappedValue: dataModel)
_vm = StateObject(wrappedValue: ViewModel(dataModel: dataModel))
}
var body: some View {
NavigationView {
List {
ForEach(vm.dataModel.items) { item in
Text(item.title)
}
}
.navigationTitle("List")
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(trailing:
Button(action: {
addItem()
}) {
Image(systemName: "plus.circle")
}
)
}
.navigationViewStyle(StackNavigationViewStyle())
}
func addItem() {
vm.addRandomItem()
}
}
extension ListView {
class ViewModel: ObservableObject {
let dataModel: DataModel
init(dataModel: DataModel) {
self.dataModel = dataModel
}
func addRandomItem() {
let newID = Int.random(in: 100..<999)
let newItem = Item(id: newID, title: "New Item \(newID)")
dataModel.addItem(newItem)
}
}
}
Yet another object would be using Combine
to automatically send objectWilLChange
updates when items
is updated:
struct ListView: View {
@StateObject private var vm: ViewModel
init(dataModel: DataModel) {
_vm = StateObject(wrappedValue: ViewModel(dataModel: dataModel))
}
var body: some View {
NavigationView {
List {
ForEach(vm.dataModel.items) { item in
Text(item.title)
}
}
.navigationTitle("List")
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems(trailing:
Button(action: {
addItem()
}) {
Image(systemName: "plus.circle")
}
)
}
.navigationViewStyle(StackNavigationViewStyle())
}
func addItem() {
vm.addRandomItem()
}
}
import Combine
extension ListView {
class ViewModel: ObservableObject {
let dataModel: DataModel
private var cancellable : AnyCancellable?
init(dataModel: DataModel) {
self.dataModel = dataModel
cancellable = dataModel.$items.sink { [weak self] _ in
self?.objectWillChange.send()
}
}
func addRandomItem() {
let newID = Int.random(in: 100..<999)
let newItem = Item(id: newID, title: "New Item \(newID)")
dataModel.addItem(newItem)
}
}
}
As you can see, there are a few options (these, and others). You can pick the design pattern that works best for you.
Access EnvironmentObject from ViewModel in SwiftUI
Since your token
is somewhat dynamic, I would suggest that you shouldn't pass it directly to your view models. Rather, pass the AppState
object and retrieve the token
when needed.
If you detect an expired token you can call a function on the AppState
that obtains a refresh token and updates its token
property.
SwiftUI : I get lost when I try to pass EnvironmentObject to another View when using MVVM
You have to inject your dataModel from the root of you app, that’s from Main root struct.
import SwiftUI
struct Payer: Identifiable {
var id = UUID().uuidString
var name : String = "ABCD"
var offset : CGFloat = 0
}
class BaseViewModel: ObservableObject {
//* The problem * //
@Published var payer: Payer
//* The problem * //
@Published var payers = [
Payer( name: "AA"),
Payer( name: "BaB"),
Payer( name: "CC"),
]
init(payer: Payer) {
self.payer = payer
}
}
struct ContentView: View {
@EnvironmentObject var vm : BaseViewModel
var body: some View {
Text(vm.payer.name)
}
}
@Main-:
import SwiftUI
@main
struct WaveViewApp: App {
@StateObject var dataController:BaseViewModel
init() {
let payer = Payer()
let model = BaseViewModel(payer:payer)
_dataController = StateObject(wrappedValue: model)
}
var body: some Scene {
WindowGroup {
ContentView().environmentObject(dataController)
}
}
}
Related Topics
I Have Real Misunderstanding With Mfmailcomposeviewcontroller in Swift (Ios8) in Simulator
How to Locate the Cgrect For a Substring of Text in a Uilabel
Allow Only Numbers For Uitextfield Input
How to Upload Images to a Server in iOS With Swift
Command Failed Due to Signal: Segmentation Fault: 11
Swift - How to Get the File Path Inside a Folder
Issue Detecting Button Cellforrowat
How to Play a Local Video With Swift
Shouldautorotatetointerfaceorientation Not Being Called in iOS 6
How to Add Image and Text in Uitextview in Ios
In Ios13 the Status Bar Background Colour Is Different from the Navigation Bar in Large Text Mode
Replacement For Deprecated -Sizewithfont:Constrainedtosize:Linebreakmode: in iOS 7
Uiwebview: Html5 Audio Pauses in iOS 6 When App Enters Background
A Complete Solution to Locally Validate an In-App Receipts and Bundle Receipts on iOS 7
Do Something Every X Minutes in Swift