To reproduce as little code as possible I created an OwnListView
that gets a title
, subtitle
, List
of Objects, which I can choose from and a Continue button
, but now I have the question how to best change the view using NavigationLinks to get the same view with different data.
Because I want to use the view not only 2 times, but more often...
So now to the execution: I want to display the view with the "Best Male Names" first, then the same view with the "Best Female Names" and then the same view with "Best Colors" and so on.
I don't find something exactly to my problem - I hope someone can help me
Here is my StartView
, which is the first View and my ListModel is initialised
struct StartView: View {
@StateObject var listModel = ListModel()
var body: some View {
NavigationView{
NavigationLink(destination: {
OwnListView(listModel: listModel,
title: "MaleNames",
items: ["Todd", "Liam", "Noah", "Oliver", "James", "William"],
choosedItems: $listModel.maleNames)
}, label: {
Text("Start")
.bold()
})
}
}
}
Then it comes to my ListView
from which I would like to have several so that I can make a "survey" or something like that:
struct OwnListView: View {
//ListModel
@ObservedObject var listModel: ListModel
var title: String
var items: [String]
@Binding var choosedItems: [String]
var body: some View {
VStack{
Text(title)
.font(.largeTitle)
.bold()
ForEach(items, id: \.self){ item in
let alreadyInList: Bool = choosedItems.contains(where: { $0 == item })
Button(action: {
if alreadyInList {
choosedItems.removeAll(where: { $0 == item })
} else {
choosedItems.append(item)
}
}, label: {
//Can be an own View, but for simplicity
ZStack{
Rectangle()
.fill(alreadyInList ? .black : .purple)
.frame(width: 250, height: 50)
Text(item)
.bold()
.foregroundColor(.white)
}
})
}
Spacer()
//After Best Male Names - female names
NavigationLink(destination: {
OwnListView(listModel: listModel,
title: "Best Female Names",
items: ["Jessica", "Monica", "Stephanie"],
choosedItems: $listModel.femaleNames)
}, label: {
Text("Continue")
})
/*
//After Best Female Names - colors
NavigationLink(destination: {
OwnListView(listModel: listModel,
title: "Best Colors",
items: ["Pink", "Blue", "Yellow", "Green"],
choosedItems: listModel.colors)
}, label: {
Text("Continue")
})
*/
Spacer()
}.navigationBarTitleDisplayMode(.inline)
}
}
CodePudding user response:
A way to have only one view that can reload is to have a dynamic way to define its contents. One might use an enum to save the state of the survey :
class ListModel: ObservableObject {
// The enum is the list of tests
enum Choosing {
case male
case female
case color
// Define each test title
var title: String {
switch self {
case .male:
return "Male Names"
case .female:
return "Female Names"
case .color:
return "Color Names"
}
}
// define each test possible values
var items: [String] {
switch self {
case .male:
return ["Todd", "Liam", "Noah", "Oliver", "James", "William"]
case .female:
return ["Jessica", "Monica", "Stephanie"]
case .color:
return ["Pink", "Blue", "Yellow", "Green"]
}
}
// choosing next test
var next: Choosing? {
switch self {
case .male:
return .female
case .female:
return .color
case .color:
return nil
}
}
}
@Published var choosedItems: [Choosing:[String]] = [.male:[], .female:[], .color:[]]
}
struct StartView: View {
@StateObject var listModel = ListModel()
var body: some View {
NavigationView{
NavigationLink(destination: {
// Just give model and first test
OwnListView(listModel: listModel,
choosing: .male)
}, label: {
Text("Start")
.bold()
})
}
}
}
The common view :
struct OwnListView: View {
//ListModel
@ObservedObject var listModel: ListModel
var choosing: ListModel.Choosing
// Use enum var to get title and items
var title: String {
choosing.title
}
var items: [String] {
choosing.items
}
var body: some View {
VStack{
Text(title)
.font(.largeTitle)
.bold()
ForEach(choosing.items, id: \.self){ item in
// Use the current test result
let alreadyInList: Bool = listModel.choosedItems[choosing]?.contains(where: { $0 == item }) ?? false
Button(action: {
if alreadyInList {
listModel.choosedItems[choosing]?.removeAll(where: { $0 == item })
} else {
listModel.choosedItems[choosing]?.append(item)
}
}, label: {
//Can be an own View, but for simplicity
ZStack{
Rectangle()
.fill(alreadyInList ? .black : .purple)
.frame(width: 250, height: 50)
Text(item)
.bold()
.foregroundColor(.white)
}
})
}
Spacer()
// if still somthing test next
if let next = choosing.next {
NavigationLink(destination: {
OwnListView(listModel: listModel,
choosing: next)
}, label: {
Text("Continue")
})
} else {
// Here you can have a button to navigation link to go to end of survey
Text("Finish")
}
Spacer()
}.navigationBarTitleDisplayMode(.inline)
}
}
Note: The enum and title and values could comes from external json file to make it more generic. Here was just a way to do it. To complete survey, just complete the enum definitions.