'Struggling to set values of subclass (down casting?)
I'm new to swift & swift UI so forgive me if there is an obvious answer to this question.
I'm trying to dynamically change the information within a view based on certain questions. Different types of questions require different responses. How do I properly down cast the Question object in QuestionResponseView as the proper subclass so that I can set the user's responses to the questions?
(Note: I still need to finish some design elements. I'm trying to solve this functionality issue before moving forward)
Thank you!
The Question object and it's subclasses:
struct Questions {
static let CheckinQuestions : [Question] = [
ValueResponseQuestion(questionText: "How many hours of sleep did you get last night?", initialValue: 6, maxValue: 12),
TextResponseQuestion(questionText: "What is taking up most of your headspace today?", responseText: "Family, Work, Children, Argument with friend"),
TextResponseQuestion(questionText: "What do you feel you need in this moment? What is missing?", responseText: "Friendship, Money, Peace, Exercise"),
ValueResponseQuestion(questionText: "What is your stress level currently at?", initialValue: 50, maxValue: 100)
]
static var GroundingQuestions : [Question] = [
//TODO: (later)
]
}
class Question : ObservableObject {
var questionText : String
init(questionText: String) {
self.questionText = questionText
}
}
class ValueResponseQuestion : Question {
var responseValue : Double
var minValue : Double = 0
var maxVal : Double
init(questionText: String, initialValue: Double, maxValue: Double) {
responseValue = initialValue
maxVal = maxValue
super.init(questionText: questionText)
}
}
class TextResponseQuestion : Question {
var responseText : String
init(questionText: String, responseText: String){
self.responseText = responseText
super.init(questionText: questionText)
}
}
The view where the user answers the questions from the array in the previous file, and the answer's are supposed to be recorded when the button is pressed.
struct QuestionResponseView: View {
/** Variable that tracks the current question from the array of questions */
@State var questionNum : Int = 0
/** The current question (starting at the beginning of the array) */
@State var question : Question = Questions.CheckinQuestions[0]
//TODO: Do I need this, or can I simply save this value in the ValueResponseQuestion class?
/** The variable used to track value responses from slider */
@State private var responseVal: Double = 6.0
//TODO: Do I need this, or can I simply save this value in the TextResponseQuestion class?
/** The variable used to track text responses from the text box */
@State private var responseText: String = "Placeholder"
/** The progress bar at the top of the page that tracks the current question */
private var progressView : RoundedProgressBar
init() {
UITextView.appearance().backgroundColor = .clear
progressView = RoundedProgressBar(progressVal: 1)
}
var body: some View {
VStack{
Text("Let's start...").font(.title)
Spacer()
progressView
Spacer()
VStack{
Text("\(question.questionText)").padding()
if question is ValueResponseQuestion {
//TODO: Set value response
//How can I get/set this from the question object?
Slider(value: $responseVal, in: 0...12).padding()
} else if question is TextResponseQuestion {
//How can I get/set this from the question object?
TextEditor(text: $responseText).background(.thickMaterial).frame(width: 285, height: 250).cornerRadius(20).padding()
}
}.frame(width: 340, height: 490, alignment: .center).overlay(RoundedRectangle(cornerRadius: 40).stroke(.gray, lineWidth: 3))
Spacer()
Button{
//Record responses and change the question
//How do I downcast question to TextResponseQuestion so that I can access and set this value?
question.questionResponseText = responseText
print(question)
responseText = "Placeholder"
questionNum += 1
if questionNum < Questions.CheckinQuestions.endIndex {
question = Questions.CheckinQuestions[questionNum]
}
else {
}
progressView.increaseProgress()
} label: {
Text("Next Question").foregroundColor(.white).padding()
}.frame(width: 150, height: 30).background(Color(red: 0.376, green: 0.4, blue: 0.816)).cornerRadius(3)
}
}
}
struct QuestionResponseView_Previews: PreviewProvider {
static var previews: some View {
QuestionResponseView()
}
}
Solution 1:[1]
One solution to your question about casting is the following:
if let question = question as? TextResponseQuestion {
question.responseText = responseText
}
That being said, I'd reconsider your architecture if I were in your position, for a couple of reasons:
You're using classes for your model -- SwiftUI
@Statevariables will work better with value types vs reference types.structs will be more likely to be what you're looking for.You're trying to store the answers along with the questions -- I'd consider splitting up the answers into a separate store
Assuming that you did #2, you could use enums with associated values instead of different subclasses for the different types of questions. In fact, you could do that even without #2, but I think separating the questions and answers may be cleaner.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | jnpdx |

