SWIFTUI - How to navigate to a random view?

568 Views Asked by At

I have built a primitive quiz app. The ContentView has a navigationlink to question one. And in the view of question one (which is a view called questionOne) I have the question and another navigationlink to questionTwo. It goes on and on until the last question, and in the last question (there are 194 for questions), there is a navigation link to the result view. Because everything is hardcoded like that, the order of the questions are always the same.

What I want to know is, how can I randomize the order of the 194 questions, and after every question is answered, go to the result view at the end.

My limited knowledge suggests that I should create an array of the views and use the array in the navigation link but I do not know how to implement it. And even if I can implement it, I don't know how to not show the same question twice in one sitting and get to the result page at the end.

Here is an examples of my code:

This is the main page:

struct ContentView: View {

    var body: some View {
        NavigationView {
          VStack {
            Text("Capitals of the World Trivia").font(.title)
            .fontWeight(.bold)

            NavigationLink(destination: questionOne()) {
                 logoView()
            }.buttonStyle(PlainButtonStyle()).padding()

            Text("Tap the globe to continue")     
          }
       }
   }
}

This is a question view:

var score = 0
// I have a global variable for score, 

 struct questionOne: View {
     @State var counter = 0
     @State var isSelected = false

     func onClick() {
          counter += 1
          if counter == 1 { score += 1}
          else if counter > 1 { score += 0}
      }

      var body: some View {

        GeometryReader { GeometryProxy in
          VStack (alignment: .center) {

            Q1Prague().cornerRadius(10)
            Spacer()
            Text(verbatim: "What's the capital of Czech Republic?")
                .font(.title)
                .frame(width: 350, height: 80)
            Button(action: {self.isSelected.toggle()}) {
                Text("Novigrad")
            }.buttonStyle(SelectedButtonStyleFalse(isSelected: self.$isSelected))
            Spacer()
            Button(action: {self.onClick();self.isSelected.toggle()}) {
                Text("Prague")
            }.buttonStyle(SelectedButtonStyle(isSelected: self.$isSelected))
            Spacer()
            Button(action: {self.isSelected.toggle()}) {
                Text("Ostrava")
            } .buttonStyle(SelectedButtonStyleFalse(isSelected: self.$isSelected))
            Spacer()
            VStack {
                HStack { 
                    NavigationLink(destination: questionTwo()) {
                       Text("Next Question")
                   }
                }
               AdView().frame(width: 150, height: 60)
            }
          }
        }.padding(.top, 80).edgesIgnoringSafeArea(.top)
    }
}

Here is my last question:

 struct question194: View {
     @State var counter = 0
     @State var isSelected = false
     func onClick() {
          counter += 1
          if counter == 1 { score += 1}
          else if counter > 1 { score += 0}
     }

     var body: some View {
           GeometryReader { GeometryProxy in
             VStack (alignment: .center) {
                Q194Athens().cornerRadius(10)
                Spacer()
                Text(verbatim: "What's the capital of Greece?")
                    .font(.title)
                    .frame(width: 400, height: 80)
                Button(action: {self.onClick();self.isSelected.toggle()}) {
                    Text("Athens")
                }.buttonStyle(SelectedButtonStyle(isSelected: self.$isSelected))
                Spacer()
                Button(action: {self.isSelected.toggle()}) {
                                   Text("Sparta")
                               }.buttonStyle(SelectedButtonStyleFalse(isSelected: self.$isSelected))
                Spacer()
                Button(action: {self.isSelected.toggle()}) {
                    Text("Ostrava")
                } .buttonStyle(SelectedButtonStyleFalse(isSelected: self.$isSelected))
                Spacer()
                VStack {
                    HStack { 
                        NavigationLink(destination: ResultPage()) {
                          Text("Next Question")
                        }
                    }
                   AdView().frame(width: 150, height: 60)
                }
               }
            }.padding(.top, 80).edgesIgnoringSafeArea(.top)
        }
}
2

There are 2 best solutions below

2
On

I'm going to give you a couple of how-to-do solutions.
Implementation details will be left for the reader.

You don't want to create a stack of 194 SwiftUI Views (or UIViewControllers). Here's why:
1. Create a test app with just 3 or 4 questions displayed.
2. Run it in the Simulator and go to your last question.
3. Go to Debug | View Debugging | Capture View Hierarchy.
4. Imagine that for 194 question Views.

First solution:

  1. Create an array of objects where each object contains:
    a/ Strings, etc. to hold each question and answer and any other question info.
    b/ A questionHasBeenShown Bool flag to indicate if the question has been shown.

  2. Create a showQuestionsCounter to keep track of how many questions you have shown.

  3. Filter the array based upon questionHasBeenShown == false.
    (You can try removing viewed questions from the original array instead as they are shown. Then you don't need the Bool.)

  4. Use a random number to select the next question.
    Increment the counter.

  5. Use the next question you just selected to update all the controls in the View.

You just use one view for this and do not create a new view for each question.

Second solution:

If you insist on having a new View for each question ...
Create a new view for the next question and use the same logic as in the first solution to populate the new view.

Have fun,
David

0
On

I have not tested or coded any of the following ...
You can try something like:

struct QuestionViewItem {  
    var wasShown: Bool  
    let questionView: View  
}

var questionArray[QuestionViewItem] = [  
    { false, QuestionView1() },  
    { false, QuestionView2() },  
    { false, QuestionView3() },  
    ...  
]  

If this takes forever to be created because it's actually creating all 194 views when initialized then you can try using a protocol ...

protocol QuestionViewProtocol {  
    func getQuestion() -> View  
}  

struct QuestionProtocolView1: QuestionViewProtocol {  
    func getQuestion() -> View {  
        return QuestionView1()  
    }  
}  
struct QuestionProtocolView2: QuestionViewProtocol {  
    func getQuestion() -> View {  
        return QuestionView2()  
    }  
}  

then:

struct QuestionViewProtocolItem {  
    var wasShown: Bool  
    let questionViewProtocol: QuestionViewProtocol  
}  

var questionArray[QuestionViewProtocolItem] = [  
    { false, QuestionProtocolView1() },  
    { false, QuestionProtocolView2() },  
    { false, QuestionProtocolView3() },  
    ...  
]  

And create each view:

questionArray[n].questionViewProtocol.getQuestion()

OR:

Maybe fold the protocol structs and the items into a single line using a closure inside (or instead of) the QuestionViewItem.
Or use a tuple with the second item the closure.
That would be more Swift and a lot cooler.