I am trying to only allow the user to select only one of the topics rather than at least one topic. I have written the code for this, however, it does not seem to do what I hope. I believe the issue could be with the line shown below, however, as a first time swift user I am unaware of what the exact issue could be. allTopics.filter { $0 }.count == 0 && NumberSelected != nil}
(I have removed several sections of code from here for other text etc that will not be relevant to this question.)
import SwiftUI
struct ContentView: View {
//Creating Variables for Revision Topics
@State private var setMemory = false
@State private var setSocialInfluence = false
@State private var setApproaches = false
@State private var setPsychopathology = false
@State private var setBiopsychology = false
@State private var setAttachment = false
@State private var setIssuesandDebates = false
@State private var setSchizophrenia = false
@State private var setResearchMethods = false
@State private var oneClicked = false
//Creating Buttons for Number of Questions
let buttons = ["10", "20", "30", "40", "50"]
@State public var NumberSelected: Int?
//Creating Variables for 'Continue' Button
let button = ["Continue"]
@State public var buttonContinue: Int?
//Making Sure User Selects Topic(s) and Number of Questions
private var allTopics: [Bool] {
[setMemory, setSocialInfluence, setApproaches, setPsychopathology, setBiopsychology, setAttachment, setIssuesandDebates, setSchizophrenia, setResearchMethods]}
private var TopicSelected: Bool {
allTopics.contains { $0 }}
private var isFormValid: Bool {
allTopics.filter { $0 }.count == 0 && NumberSelected != nil}
var body: some View {
Group{
VStack(alignment: .leading, spacing: 5) {
Toggle("Memory",isOn: $setMemory)
.toggleStyle(.button)
.tint(Color(red: 0.902, green: 0.755, blue: 0.161))
Toggle("Approaches",isOn: $setApproaches)
.toggleStyle(.button)
.tint(Color(red: 0.945, green: 0.442, blue: 0.022))
Toggle("Biopsychology",isOn: $setBiopsychology)
.toggleStyle(.button)
.tint(Color(red: 0.817, green: 0.065, blue: 0.287))
Toggle("Issues & Debates",isOn: $setIssuesandDebates)
.toggleStyle(.button)
.tint(Color(red: 0.399, green: 0.06, blue: 0.947))
Toggle("Research Methods Year 1 & 2",isOn: $setResearchMethods)
.toggleStyle(.button)
.tint(Color(red: 0.105, green: 0.561, blue: 0.896))}
.padding(.leading, -135.0)
.padding(.top, -10)
VStack(alignment: .leading, spacing: 5) {
Toggle("Social Influence",isOn: $setSocialInfluence)
.toggleStyle(.button)
.tint(Color(red: 0.902, green: 0.755, blue: 0.17))
Toggle("Psychopathology",isOn: $setPsychopathology)
.toggleStyle(.button)
.tint(Color(red: 0.945, green: 0.442, blue: 0.022))
Toggle("Attachment",isOn: $setAttachment)
.toggleStyle(.button)
.tint(Color(red: 0.817, green: 0.065, blue: 0.287))
Toggle("Schizophrenia",isOn: $setSchizophrenia)
.toggleStyle(.button)
.tint(Color(red: 0.394, green: 0.061, blue: 0.943))}
.padding(.top, -192)
.padding(.leading, 180)
}
//Number of Questions - Selected Buttons
HStack(spacing: 15) {
ForEach(0..<buttons.count, id: \.self) {button in
Button(action: {
self.NumberSelected = button
}) {
Text("\(self.buttons[button])")
.foregroundColor(Color("Black-White"))
.font(.title3)
.padding()
.background(self.NumberSelected == button ? Color("Custom Gray"): Color("White-Black"))
.clipShape(Capsule())}}
}
}
}
//Continue Button
HStack(spacing: 15) {
ForEach(0..<button.count, id: \.self) {button in
Button(action: {
self.buttonContinue = button
}) {
//Links Continue Button To Next Page
NavigationLink(destination: SecondView()) {
Text("Continue")
}
//'Continue' Button is Disabled if User Has Not Selected Values
.clipShape(Capsule())}}.disabled(!isFormValid)
}
}
}
}
CodePudding user response:
You really need to restructure your code. While your approach with @State var
is working for a few choices it becomes very cumbersome to deal with as your code advances.
My solution has implications for your code other than just the exclusive selection of toggles. This issues you will have to address on your own, or ask a new question.
Create an enum holding your choices
enum SelectedOption: CaseIterable{ case memory, approaches, biopsychology var prettyDescription: String{ // used for getting a description in the view switch self{ case .memory: return "Memory" case .approaches: return "Approaches" case .biopsychology: return "Biopsychology" } } }
I´ve done only 3 but this can be expanded without to much effort.
Delete the now deprected
@State var
s from your view and replace them with a single one:@State private var selectedOption: SelectedOption?
Change the VStack containing your Toggles:
VStack{ ForEach(SelectedOption.allCases, id: \.self){ enumCase in Toggle(enumCase.prettyDescription, isOn: Binding(get: { selectedOption == enumCase }, set: { _,_ in selectedOption = enumCase })) .toggleStyle(.button) .tint(Color(red: 0.817, green: 0.065, blue: 0.287)) } }
You are now itterating over all cases SelectedOption can contain and using a binding to determine whether the toggle should be selected or not depending on selectedOption
CodePudding user response:
Here is a solution, the short answer is to put all the related data together in a struct
, then have an Array
of objects that you can iterate over to create any View
and easily pass the data along.
import SwiftUI
//Contains all the items for each topic
struct RevisionTopics: Identifiable, Hashable{
let id: UUID = UUID()
var name: String
var color: Color
//Add variables as needed so eveything is contained.
//Questions, answers, etc
}
//Keeps work out of the `View`
class TopicsViewModel: ObservableObject{
///Holds the single selected topic
@Published var selectedTopic: RevisionTopics? = nil
///Source of truth for all the topics
@Published var topics: [RevisionTopics] = [.init(name: "Memory", color: Color(red: 0.902, green: 0.755, blue: 0.161)), .init(name: "Social Influence", color: Color(red: 0.902, green: 0.755, blue: 0.17)), .init(name: "Approaches", color: Color(red: 0.945, green: 0.442, blue: 0.022)), .init(name: "Psychopathology", color: Color(red: 0.945, green: 0.442, blue: 0.022)), .init(name: "Biopsychology", color: Color(red: 0.817, green: 0.065, blue: 0.287)), .init(name: "Attachment", color: Color(red: 0.817, green: 0.065, blue: 0.287)), .init(name: "Issues and Debates", color: Color(red: 0.399, green: 0.06, blue: 0.947)), .init(name: "Schizophrenia", color: Color(red: 0.394, green: 0.061, blue: 0.943)), .init(name: "Research Methods", color: Color(red: 0.105, green: 0.561, blue: 0.896))]
@Published var buttons: [Int] = [10,20,30,40,50]
@Published var numberOfQuestions: Int = 0
}
struct ContentView: View {
@StateObject var topicsVM: TopicsViewModel = .init()
var body: some View {
NavigationView {
ScrollView{
//Topics
//Option 1 - use picker
Picker("Topics", selection: $topicsVM.selectedTopic){
Text("Select a topic").tag(nil as RevisionTopics?)
ForEach(topicsVM.topics){ topic in
Text(topic.name).tag(topic as RevisionTopics?).foregroundColor(topic.color)
}
}.pickerStyle(.wheel)
//Option 2 - keep toggles with custom binding
ForEach(topicsVM.topics){ topic in
Toggle(topic.name, isOn:
Binding(get: {
topicsVM.selectedTopic == topic
}, set: { new in
if new{
topicsVM.selectedTopic = topic
}else{
topicsVM.selectedTopic = nil
}
})).tint(topic.color)
}
HStack(spacing: 15) {
ForEach(topicsVM.buttons, id: \.self) {button in
Button(action: {
topicsVM.numberOfQuestions = button
}) {
Text("\(button)")
.foregroundColor(Color.blue)
.font(.title3)
.padding()
.background(topicsVM.numberOfQuestions == button ? Color.red:Color.gray)
.clipShape(Capsule())}}
}
if let selected = topicsVM.selectedTopic, topicsVM.numberOfQuestions != 0{
NavigationLink("Continue", destination: {
Text(selected.name)
//SecondView(topic: selected)
})
}else{
Text("**To Continue**")
if topicsVM.selectedTopic == nil{
Text("Select a topic")
}
if topicsVM.numberOfQuestions == 0{
Text("Select the number of questions")
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}