0

I'm new to Swift and iOS development and am doing a number of online courses. In one course I completed an iPhone quiz app. All the questions and potential answers are loaded from a .json file and the code we worked through randomly selects a question for the user to answer. This works fine except that over time questions will repeat, so I've been looking at ways to prevent this.

After a few failed attempts to get the app to store the random selections and remove them from the overall pool of remaining questions, I realised that the more efficient approach would be to just shuffle the questions initially and then ask them one by one.

So, I did some digging around and came across what's called a Fisher-Yates shuffle and have since been trying to find a way to achieve this. I then found a reference to using GKRandomSource from Swift's Gameplaykit to shuffle things, so I've been trying to use this to do the trick and can't seem to get it to work.

Below is what I've tried so far in terms of code etc.

All questions and potential answer choices are stored in a .json file as such:

{
        "id" : "1",
        "question": "Earth is a:",
             "answers": [
            "Planet",
            "Meteor",
            "Star",
            "Asteroid"
          ],
          "difficulty": "1"
      }

I use the following code to load the .json file:

func loadAllQuestionsAndAnswers()
{
    let path = NSBundle.mainBundle().pathForResource("content", ofType: "json")
    let jsonData : NSData = NSData(contentsOfFile: path!)!
    allEntries = (try! NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.MutableContainers)) as! NSArray
    //println(allEntries)

}

And below is the code I'm using to try and achieve the shuffle of all questions:

var shuffledNumber : Int! = 0

    loadAllQuestionsAndAnswers()

    if #available(iOS 9.0, *) {
        let shuffledNumber = GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(allEntries as NSArray as [AnyObject])
    } else {
        // Fallback on earlier versions
    }
    LoadQuestion(shuffledNumber)

I use a label to display the question and four images to display the potential answers, using the following code:

func LoadQuestion(index : Int)
{
    let entry : NSDictionary = allEntries.objectAtIndex(index) as! NSDictionary
    let question : NSString = entry.objectForKey("question") as! NSString
    let arr : NSMutableArray = entry.objectForKey("answers") as! NSMutableArray

    //println(question)
    //println(arr)

    labelQuestion.text = question as String

    let indices : [Int] = [0,1,2,3]
    //let newSequence = shuffle(indices)
    let newSequence = indices.shuffle()
    var i : Int = 0
    for(i = 0; i < newSequence.count; i++)
    {
        let index = newSequence[i]
        if(index == 0)
        {
            // we need to store the correct answer index
            currentCorrectAnswerIndex =  i

        }

        let answer = arr.objectAtIndex(index) as! NSString
        switch(i)
        {
        case 0:
            buttonA.setTitle(answer as String, forState: UIControlState.Normal)
            break;

        case 1:
            buttonB.setTitle(answer as String, forState: UIControlState.Normal)
            break;

        case 2:
            buttonC.setTitle(answer as String, forState: UIControlState.Normal)
            break;

        case 3:
            buttonD.setTitle(answer as String, forState: UIControlState.Normal)
            break;

        default:
            break;
        }



    }
    buttonNext.hidden = true
    // we will need to reset the buttons to reenable them
    ResetAnswerButtons()

}

Finally, I use the following code to present the user with a 'Next' button after they've answered a question:

@IBAction func PressedButtonNext(sender: UIButton) {
    print("button Next pressed")
        if #available(iOS 9.0, *) {
            _ = GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(allEntries as [AnyObject])
        } else {
            // Fallback on earlier versions
        }
        LoadQuestion(shuffledNumber)

Now, while I think this should all work, it's not. While the app loads fine, it always presents the user with the very first question from the .json file and keeps repeating the same question every time you tap on the Next button.

How do I get this to shuffle all the questions and then just ask them in order one by one? Let me know if I need to include more of the code.

Monomeeth
  • 753
  • 3
  • 13
  • 29

1 Answers1

1

arrayByShufflingObjectsInArray gives you a new array that is shuffled. Therefore you should be calling it once, and moving an index along it when PressedButtonNext is called. Given you have declared

    var shuffledNumber : Int! = 0

And then effectively redeclared

    let shuffledNumber = ...

It's not surprising it's not working. I suspect you are not running under IOS 9, so you're not getting a compilation error (although I have not used #available before).

You should declare the following instance variables:

    var shuffledQuestions: [AnyObject]!
    var nextQuestion = -1

And then in loadAllQuestionsAndAnswers, you should include

    shuffledQuestions = GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(...

And in PressedButtonNext

    nextQuestion++
    LoadQuestion(nextQuestion)

And then change your LoadQuestion to get entry from the shuffled questions:

    let entry : NSDictionary = shuffledQuestions[index] as! NSDictionary

Have a look at this example here

Michael
  • 8,891
  • 3
  • 29
  • 42
  • Thanks. I'll take a look at the linked page and have a play with your suggested code. By the way, I was running the app using the iPhone 6s iOS 9.2 simulator without any compilation errors. – Monomeeth Feb 01 '16 at 05:30
  • Yes, but you are checking "9.0, *", which suggests to me 9.0.1, 9.0.2, etc. I expect you want "9, *" – Michael Feb 01 '16 at 05:33
  • Oh, I see. I might just do that first and see what happens in the simulator (this is al new to me, so I'm curious). – Monomeeth Feb 01 '16 at 05:34
  • Finally found the documentation (https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Attributes.html) which seems to support your syntax. It's just that I tried it in the IBM sandbox, and commenting out the #available, I got compilation errors. – Michael Feb 01 '16 at 05:38
  • I think I'm getting closer, but would you happen to know why I'm getting a Type '[AnyObject]!' has no subscript members error where I've placed the LoadQuestion(shuffledQuestions[nextQuestion]) line in PressedButtonNext ? – Monomeeth Feb 01 '16 at 06:45
  • From the name, I had assumed your LoadQuestion method took a 'question' as a parameter, whereas the message you're getting suggests it's expecting an array. Perhaps show the contents (or at least declaration) of LoadQuestion – Michael Feb 01 '16 at 06:49
  • D'oh! Thought it was in my original question. I've just edited it to include it. – Monomeeth Feb 01 '16 at 07:12