Before tutoring some math the other day, I wondered if it's possible to create an app that would present varying math problems using different numbers each time. After a few days, the only solution I can come up is not very clean and involves creating a struct
for each individual question. Let's say these are two of the questions:
struct Question1: View {
var questionNumber = 1
var length = Int.random(in: 10...30); var width = Int.random(in: 15...25)
var body: some View {
VStack {
Text("Evaluate the area of a rectangle with a length of \(length) cm and a width of \(width) cm.")
Divider()
VStack(alignment: .leading) {
Text("Area = length x width")
Text("A = \(length) * \(width) = \(length * width)")
} } } }
struct Question2: View {
var questionNumber = 2
var perimeter = Int.random(in: 50...56); var width = Int.random(in: 15...22)
var firstCalculation: Double { Double(perimeter - 2 * width) }
var secondCalculation: Double { firstCalculation / 2 }
var body: some View {
VStack {
Text("A rectangular field has a perimeter of \(perimeter) m. If the width of the field is \(width) m, what is the field's length?")
Divider()
VStack(alignment: .leading) {
Text("Perimeter = 2 * length + 2 * width")
Text("\(perimeter) = 2L + 2 * \(width)")
Text("2L = \(firstCalculation, specifier: "%.0f")")
Text("L = \(secondCalculation, specifier: "%.1f")")
} } } }
Combining these with the following ContentView
works, but making 100 different structs
seems unwieldy, especially given that I couldn't figure out how to call each struct
programmatically; I just used an Int
array and a switch
statement, which is hardly elegant.
struct ContentView: View {
var allQuestions = [1, 2]
@State var index: Int = 0; var selectedQuestion: Int { index + 1 }
var body: some View {
VStack {
Picker("Select problem", selection: $index) {
ForEach(allQuestions, id: \.self) { questionNumber in
Text("Question #\(questionNumber)") } }
QuestionDisplay(questionNumber: selectedQuestion)
} } }
struct QuestionDisplay: View {
var questionNumber: Int
var body: some View {
switch questionNumber { case 1: return Question1(); case 2: return Question2(); default: return Question1()
} } }
Ideally, the goal would be to use a combination of something like:
struct Question: Identifiable {
var questionNumber: Int; var id: Int { questionNumber }
var givens: [Any]; var calculations: [(Double...) -> Double]
var question: String
var solutionLines: [String]
}
and
struct ContentView2: View {
var allQuestions: [Question]
@State var selectedQuestion: Int = 1; var arrayIndex: Int { selectedQuestion - 1 }
var given1: Int { allQuestions[arrayIndex].givens[0] }
var given2: Int { allQuestions[arrayIndex].givens[1] }
var calculation1: <something something > { allQuestions[arrayIndex].calculations[0] }
var calculation2: <something something > { allQuestions[arrayIndex].calculations[1] }
var body: some View {
VStack {
Picker("Select problem", selection: $selectedQuestion) {
ForEach(allQuestions) { question in
Text("Question #\(question.questionNumber)") } }
VStack {
Text(allQuestions[arrayIndex].question); Divider()
VStack(alignment: .leading) {
ForEach(allQuestions[arrayIndex].solutionLines, id: \.self) { line in
Text(line)
} } } } } }
where the mathematical quantities like length, width, and area would just have to be referred to as givens[0], givens[1], givens[2], etc.
Then we could create individual questions of type Question and toss them into an array of Question objects (i.e., var question1: Question
. However, even disregarding the craziness that entering the various calculations would require for each question and the fact that I can't include the specifiers in the Strings and would have to use a function–which I have written, actually–to round the values instead, you can't (afaik) deal with the string interpolation in a parameter.
Everytime I try what I think is a different angle, I come upon the same issue: a parameter value contains string interpolation or depends upon another parameter's value presented at the same time (e.g., Question(givens: [Int.random(..), Int.random(..)], .., question: "A rectangle's parameter is (givens[0]).."..)).
So would making individual struct
s (only some other object) be the only way to pull this off?