I'm a newbie developing an app which basically has a fridge class, which contains food. I'm trying to have one single fridge with a list of food. So I set foodList to be a published object and instantiate a @StateObject fridge when the program starts. Basically, from my perspective, whenever the foodList changes, the fridge object will save its state and give me a new view.
In addition, I want to pass this @StateObject fridge to another view so that my another view could modify my foodList in fridge
Below is my code
struct ContentView: View {
@StateObject private var fridge=Fridge();
private var dbStartWith=0;
@State private var selection = 1;
@State private var addFood = false;
var body: some View {
TabView(selection: $selection) {
NavigationView {
List(fridge.container!){
food in NavigationLink(destination: FoodView()) {
Text("HI")
}
}.navigationBarTitle(Text("Fridge Items"), displayMode: .inline)
.navigationBarItems(trailing:
NavigationLink(destination: AddFoodView(fridgeView: fridge)) {
Image(systemName: "plus.circle").resizable().frame(width: 22, height: 22)
} )
}
.tabItem {
Image(systemName: "house.fill")
Text("Home")
}
.tag(1)
Text("random tab")
.font(.system(size: 30, weight: .bold, design: .rounded))
.tabItem {
Image(systemName: "bookmark.circle.fill")
Text("profile")
}
.tag(0)
}
}
}
struct FoodView: View{
var body: some View{
NavigationView{
Text("food destination view ");
}
}
}
struct AddFoodView: View{
@ObservedObject var fridgeView: Fridge;
@State private var name = ""
@State private var count : String = "1"
@State private var category : String = "肉类";
@State var showCategory = false
@State var showCount = false
var body: some View{
ZStack{
NavigationView{
Form{
HStack{
Text("食品")
TextField("请输入材料名", text: $name);
}.padding(.bottom,10).padding(.top,10).padding(.leading,10).padding(.trailing,10)
HStack{
Text("数量")
TextField("请选择数量",text:$count,onEditingChanged:{(changed) in
self.hideKeyboard();
self.showCount=changed;
}){}
}.padding(.bottom,10).padding(.top,10).padding(.leading,10).padding(.trailing,10)
HStack{
Text("种类")
TextField("请选择种类",text: $category,onEditingChanged:{(changed) in
self.hideKeyboard();
self.showCategory=changed;
}){}
}.padding(.bottom,10).padding(.top,10).padding(.leading,10).padding(.trailing,10)
}
}.navigationBarItems(trailing: NavigationLink(destination: FoodView()){
Text("保存").foregroundColor(Color.blue).font(.system(size: 18,design: .default))
}).simultaneousGesture(TapGesture().onEnded{
var tempFood=Food(id: fridgeView);//not completed yet
})
ZStack{
if self.showCount{
Rectangle().fill(Color.gray)
.opacity(0.5)
VStack(){
Spacer(minLength: 0);
HStack{
Spacer()
Button(action: {
self.showCount=false;
}, label: {
Text("Done")
}).frame(alignment: .trailing).offset(x:-15,y:15)
}
Picker(selection: $count,label: EmptyView()) {
ForEach(1..<100){ number in
Text("\(number)").tag("\(number)")
}
}.labelsHidden()
} .frame(minWidth: 300, idealWidth: 300, maxWidth: 300, minHeight: 250, idealHeight: 100, maxHeight: 250, alignment: .top).fixedSize(horizontal: true, vertical: true)
.background(RoundedRectangle(cornerRadius: 27).fill(Color.white.opacity(1)))
.overlay(RoundedRectangle(cornerRadius: 27).stroke(Color.black, lineWidth: 1))
.offset(x:10,y:-10)
Spacer()
}
if self.showCategory{
let categoryArr = ["肉类","蔬菜类","饮料类","调味品类"]
ZStack{
Rectangle().fill(Color.gray)
.opacity(0.5)
VStack(){
Spacer(minLength: 0);
HStack{
Spacer()
Button(action: {
self.showCategory=false;
}, label: {
Text("Done")
}).frame(alignment: .trailing).offset(x:-15,y:15)
}
Picker(selection: $category,label: EmptyView()) {
ForEach(0..<categoryArr.count){ number in
Text(categoryArr[number]).tag(categoryArr[number])
}
}.labelsHidden()
} .frame(minWidth: 300, idealWidth: 300, maxWidth: 300, minHeight: 250, idealHeight: 100, maxHeight: 250, alignment: .top).fixedSize(horizontal: true, vertical: true)
.background(RoundedRectangle(cornerRadius: 27).fill(Color.white.opacity(1)))
.overlay(RoundedRectangle(cornerRadius: 27).stroke(Color.black, lineWidth: 1))
Spacer()
}.offset(x:10,y:20)
}
}
}.animation(.easeInOut)
}
}
Basically I want to pass the @StateObject private var fridge
to the struct AddFoodView: View
so that my AddFoodView
can use the variable. From what I learned online, I think I need to inject @ObservedObjects
like this NavigationLink(destination: AddFoodView(fridgeView: fridge))
. This doesn't give me error ( at least in debugger phase) which confuses me. Isn't fridge
a Food
variable and fridgeView
a @ObservedObject Food
variable ?
If I do something like this AddFoodView(fridgeView: $fridge))
. There is an error saying Cannot convert value '$fridge' of type 'ObservedObject<Fridge>.Wrapper' to expected type 'Fridge', use wrapped value instead
@StateObject
and @ObservedObject
are sources of truth, they are very similar to each other and almost interchangeable.
To share these with other View
s you should pass it to the AddFoodView
using .environmentObject(fridge)
and change the @ObservedObject var fridgeView: Fridge;
in AddFoodView
to @EnvironmentObject var fridgeView: Fridge
.
Look up the Apple Documentation on "Managing Model Data in Your App".
struct BookReader: App {
@StateObject var library = Library()
var body: some Scene {
WindowGroup {
LibraryView()
.environmentObject(library)
}
}
}
struct LibraryView: View {
@EnvironmentObject var library: Library
// ...
}
Another good source are the Apple SwiftUI Tutorials, especially "Handling User Input"