[Pic 1 AS IS]
[Pic 2 TO BE]
Hi there, I am just starting to learn Swift an I would like my app users to build their own list of items (first level) where each item again contains a list of items (second level). Important is that each of the individually created lists in the second level is like no other of the individually created lists. (see picture)
Is anyone aware of which approach I need to take to solve this?
I am myself able to build the list within the list within the NavigationView, but how can I make each list individual?
Here is my code:
struct ItemModel: Hashable {
let name: String
}
struct ProductModel: Hashable {
let productname: String
}
class ListViewModel: ObservableObject {
@Published var items: [ItemModel] = []
}
class ProductlistViewModel: ObservableObject {
@Published var products: [ProductModel] = []
}
struct ContentView: View {
@StateObject private var vm = ListViewModel()
@StateObject private var pvm = ProductlistViewModel()
@State var firstPlusButtonPressed: Bool = false
@State var secondPlusButtonPressed: Bool = false
var body: some View {
NavigationView {
List {
ForEach(vm.items, id: \.self) { item in
NavigationLink {
DetailView() //The DetailView below
.navigationTitle(item.name)
.navigationBarItems(
trailing:
Button(action: {
secondPlusButtonPressed.toggle()
}, label: {
NavigationLink {
AddProduct() //AddProduct below
} label: {
Image(systemName: "plus")
}
})
)
} label: {
Text(item.name)
}
}
}
.navigationBarItems(
trailing:
Button(action: { firstPlusButtonPressed.toggle()
}, label: {
NavigationLink {
AddItem() //AddItem below
} label: { Image(systemName: "plus")
}
})
)
}
.environmentObject(vm)
.environmentObject(pvm)
}
}
struct AddItem: View {
@State var textFieldText: String = ""
@Environment(\.presentationMode) var presentationMode
@EnvironmentObject var vm: ListViewModel
var body: some View {
NavigationView {
VStack {
TextField("Add an item...", text: $textFieldText)
Button(action: {
vm.addItem(text: textFieldText)
presentationMode.wrappedValue.dismiss()
}, label: {
Text("SAVE")
})
}
}
}
}
struct DetailView: View {
@StateObject private var pvm = ProductlistViewModel()
@Environment(\.editMode) var editMode
var body: some View {
NavigationView {
List {
ForEach(pvm.products, id: \.self) { product in
Text(product.productname)
}
}
}
.environmentObject(pvm)
}
}
struct AddProduct: View {
@State var textFieldText: String = ""
@Environment(\.presentationMode) var presentationMode
@EnvironmentObject var pvm: ProductlistViewModel
var body: some View {
NavigationView {
VStack {
TextField("Add a product", text: $textFieldText)
Button(action: {
pvm.addProduct(text: textFieldText)
presentationMode.wrappedValue.dismiss()
}, label: {
Text("SAVE")
})
}
}
}
}
1条答案
按热度按时间igetnqfo1#
This is going to be long but here it goes. The issue is the whole ViewModel setup. You detail view now is only using the product view model, you need to rethink your approach.
But what makes the whole thing "complicated" is the 2 different types, Item and Product which you seem to want to combine into one list and use the same subviews for them both.
In swift you have
protocol
that allows this, protocols requirestruct
andclass
"conformance".Then your models start looking something like this. Notice the conformance to the protocols.
Now your views can look something like this
If you notice the
List
in theComboView
you will notice that theitems
andproducts
are separated into 2 loop. That is because SwiftUI requires concrete types for most views, view modifiers and wrappers.You can have a list of
[any ListModelProtocol]
but at some point you will have to convert from an existential to a concrete type. In your case theForEach
in deDetailView
requires a concrete type.I know its long but this all compiles so you should be able to put it in a project and disect it.
Also, at the end of all of this you can create specific views for the model type.