I would like to implement a multi view questionnaire for the user to enter details about themselves. Therefore, I need a persistent storage about what the user picks preferably in an object that I can pass through different views which each mutate a detail about the user object.
The process:
Video of issue:
My approach: I have already tried using stateobjects in order to pass the state down like in React but that didn't work.
This is the code as of now:
struct Names: View {
private let names = ["xyz", "zfx", "abc", "def", "ghij", "klm"]
var body: some View {
NavigationView{
VStack{
Text("Choose your name")
.font(.largeTitle)
.foregroundColor(Color("LightBlue"))
.padding(.bottom, 40)
.padding(.top, 40)
VStack(spacing: 20){
ForEach(names, id: \.self){ name in
CustomButton(name: name)
}
}
}
}
}
}
struct CustomButton: View {
@State private var clicked = false
let name:String
init(name:String){
self.name = name
}
var body: some View {
Button{
self.clicked.toggle()
}label: {
NavigationLink(destination: ChooseSports()){
Text(self.name)
.font(.subheadline)
.frame(width: 250, height: 60)
.background(self.clicked ? Color("DarkBlue") : .white)
.foregroundColor(self.clicked ? .white : Color("DarkBlue"))
.cornerRadius(50)
.overlay(
RoundedRectangle(cornerRadius: 50)
.stroke(Color("DarkBlue"), lineWidth: 2)
)
}
}
}
}
struct CustomButton2: View {
@State private var clicked = false
let name:String
init(name:String){
self.name = name
}
var body: some View {
Button{
self.clicked.toggle()
}label: {
Text(self.name)
.font(.subheadline)
.frame(width: 250, height: 60)
.background(self.clicked ? Color("DarkBlue") : .white)
.foregroundColor(self.clicked ? .white : Color("DarkBlue"))
.cornerRadius(50)
.overlay(
RoundedRectangle(cornerRadius: 50)
.stroke(Color("DarkBlue"), lineWidth: 2)
)
}
}
}
struct ChooseSports: View {
private let sports = ["tennis", "football", "golf", "basketball", "squash", "badminton", "swimming", "skiing"]
var body: some View {
ScrollView{
VStack{
Text("Choose your favourite sports")
.font(.largeTitle)
.foregroundColor(Color("LightBlue"))
.padding(.bottom, 40)
.padding(.top, 40)
VStack(spacing: 20){
ForEach(sports, id: \.self){ sport in
CustomButton2(name:sport)
}
}
}
}
}
}
Expected process:
State
is a source of truth that lives as long as the View
lives. When you go to the next CustomButton
the old one gets redrawn/recreated.
What you need some kind of continuity.
You can achieve that by putting everything that goes together into a struct
/Model
struct NameModel{
var name: String
var clicked: Bool = false
}
Then then the value of clicked
will be able to survive when Names
redraws the body
struct NamesView: View {
@State private var names:[NameModel] = [.init(name: "xyz"), .init(name: "zfx"), .init(name: "abc"), .init(name: "def"), .init(name: "ghij"), .init(name: "klm")]
var body: some View {
NavigationView{
VStack{
Text("Choose your name")
.font(.largeTitle)
.foregroundColor(Color.blue)
.padding(.bottom, 40)
.padding(.top, 40)
VStack(spacing: 20){
ForEach($names, id: \.name){ $model in
CustomButton(model: $model)
}
}
}
}
}
}
struct CustomButton: View {
@Binding var model : NameModel
var body: some View {
NavigationLink(destination: ChooseSports()
.onAppear(){
model.clicked = true
}){
Text(model.name)
.font(.subheadline)
.frame(width: 250, height: 60)
.background(model.clicked ? Color.blue : .white)
.foregroundColor(model.clicked ? .white : Color.blue)
.cornerRadius(50)
.overlay(
RoundedRectangle(cornerRadius: 50)
.stroke(Color.blue, lineWidth: 2)
)
}
}
}
But since you have sports that are specific to a name you may watt to adjust to something like the code below.
import SwiftUI
struct NameModel{
var name: String
var favoriteTeams: [String]? //Will only be nil of sports has not been visited
}
struct NamesView: View {
@State private var names:[NameModel] = [.init(name: "xyz"), .init(name: "zfx"), .init(name: "abc"), .init(name: "def"), .init(name: "ghij"), .init(name: "klm")]
var body: some View {
NavigationView{
VStack{
Text("Choose your name")
.font(.largeTitle)
.foregroundColor(Color.blue)
.padding(.bottom, 40)
.padding(.top, 40)
VStack(spacing: 20){
ForEach($names, id: \.name){ $model in
CustomButton(model: $model)
}
}
}
}
}
}
struct CustomButton: View {
@Binding var model : NameModel
var body: some View {
NavigationLink(destination: ChooseSports(model: $model)
.onAppear(){
if model.favoriteTeams == nil{ //Change to empty to symbolize that the user has not selected any teams
model.favoriteTeams = []
}
}){
Text(model.name)
.font(.subheadline)
.frame(width: 250, height: 60)
.background(model.favoriteTeams != nil ? Color.blue : .white)
.foregroundColor(model.favoriteTeams != nil ? .white : Color.blue)
.cornerRadius(50)
.overlay(
RoundedRectangle(cornerRadius: 50)
.stroke(Color.blue, lineWidth: 2)
)
}
}
}
struct CustomButton2: View {
@Binding var model : NameModel
let name: String
var body: some View {
Button{
//Select or deselect based on contents of array
if let idx = model.favoriteTeams?.firstIndex(where: { str in
str == name
}){
model.favoriteTeams?.remove(at: idx)
}else{
if model.favoriteTeams == nil{
model.favoriteTeams = []
}
model.favoriteTeams?.append(name)
}
}label: {
Text(self.name)
.font(.subheadline)
.frame(width: 250, height: 60)
.background(model.favoriteTeams?.contains(name) ?? false ? Color.blue : .white)
.foregroundColor(model.favoriteTeams?.contains(name) ?? false ? .white : Color.blue)
.cornerRadius(50)
.overlay(
RoundedRectangle(cornerRadius: 50)
.stroke(Color.blue, lineWidth: 2)
)
}
}
}
struct ChooseSports: View {
@Binding var model : NameModel
private let sports = ["tennis", "football", "golf", "basketball", "squash", "badminton", "swimming", "skiing"]
var body: some View {
ScrollView{
VStack{
Text("Choose your favourite sports")
.font(.largeTitle)
.foregroundColor(Color.blue)
.padding(.bottom, 40)
.padding(.top, 40)
VStack(spacing: 20){
ForEach(sports, id: \.self){ sport in
CustomButton2(model: $model, name:sport)
}
}
}
}
}
}
struct NamesView_Previews: PreviewProvider {
static var previews: some View {
NamesView()
}
}