JamesG JamesG - 3 months ago 25
JSON Question

Swift: Checking JSON values stored in values of NSMutableArray

I am having trouble checking whether or not a question in the JSON file has already been seen by the user.

I have an NSArray (allEntries) with Stored JSON in the values. I create this like so:

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


If I print out one of the values allEntries[0], I get:

{
answers = (
Chimney,
"Front door",
Window,
"Dog door"
);
difficulty = 1;
id = 2;
question = "How does Santa get into a house on Christmas eve?";
}


Now, later on in my code I need to call just one of the questions at random, so I generate an index and pass it to this function:

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

....
}


When the question is loaded. I add that ID value to another array called alreadyAsked

At first I get this error:


Could not cast value of type '__NSCFString' (0x865ee0) to 'NSNumber' (0x133781c).


on this line:

let questionID : Int = entry.objectForKey("id") as! Int


so I changed the Int to NSString:

let questionID : NSString = entry.objectForKey("id") as! NSString


and then tried to recast like so:

let questionID = Int(questionIDRaw as! Int)


but then I get this issue:


Definition conflicts with previous value


What I need to happen:



Disclaimer I am a noob. I have followed the tutorial from Udemy so far and managed to get the app working. The additional check is something I am trying to do.

So, I want to be able to check to make sure that when I pull a random question, that the id (in the json) for the question is not in the alreadyAsked array. How do I do this?

Appreciate all help :)

EDIT:

Full Controller:

import UIKit
import AVFoundation // audio


class ViewController: UIViewController {

@IBOutlet weak var buttonA: UIButton!
@IBOutlet weak var buttonB: UIButton!
@IBOutlet weak var buttonC: UIButton!
@IBOutlet weak var buttonD: UIButton!


@IBOutlet weak var labelQuestion: UILabel!

@IBOutlet weak var labelScore: UILabel!

@IBOutlet weak var labelFeedback: UILabel!

@IBOutlet weak var buttonNext: UIButton!

@IBOutlet weak var BackgroundImageView: UIImageView!

var score :Int! = 0
var allEntries : NSArray!


var currentCorrectAnswerIndex : Int = 0

var audioPlayer = AVAudioPlayer()

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.

LoadAllQuestionsAndAnswers()

let randomNumber = Int(arc4random_uniform(UInt32(allEntries.count)))
LoadQuestion(randomNumber)
LoadScore()

AdjustInterface()
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}


func LoadScore()
{
let defaults = NSUserDefaults.standardUserDefaults()
score = defaults.integerForKey("score")
labelScore.text = "score: \(score)"
}


func SaveScore()
{
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setInteger(score, forKey: "score")
}


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)

}


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)
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()

}

func shuffle<C: MutableCollectionType where C.Index == Int>(var list: C) -> C {
let total = list.count
for i in 0..<(total - 1) {
let j = Int(arc4random_uniform(UInt32(total - i))) + i
swap(&list[i], &list[j])
}
return list
}

func ResetAnswerButtons()
{
buttonA.alpha = 1.0
buttonB.alpha = 1.0
buttonC.alpha = 1.0
buttonD.alpha = 1.0
buttonA.enabled = true
buttonB.enabled = true
buttonC.enabled = true
buttonD.enabled = true
}


@IBAction func PressedButtonA(sender: UIButton) {
print("button A pressed")
buttonB.alpha = 0.3
buttonC.alpha = 0.3
buttonD.alpha = 0.3

buttonA.enabled = false
buttonB.enabled = false
buttonC.enabled = false
buttonD.enabled = false
CheckAnswer(0)
}

@IBAction func PressedButtonB(sender: UIButton) {
print("button B pressed")
buttonA.alpha = 0.3
buttonC.alpha = 0.3
buttonD.alpha = 0.3

buttonA.enabled = false
buttonB.enabled = false
buttonC.enabled = false
buttonD.enabled = false
CheckAnswer(1)
}

@IBAction func PressedButtonC(sender: UIButton) {
print("button C pressed")
buttonA.alpha = 0.3
buttonB.alpha = 0.3
buttonD.alpha = 0.3

buttonA.enabled = false
buttonB.enabled = false
buttonC.enabled = false
buttonD.enabled = false
CheckAnswer(2)
}

@IBAction func PressedButtonD(sender: UIButton) {
print("button D pressed")
buttonA.alpha = 0.3
buttonB.alpha = 0.3
buttonC.alpha = 0.3

buttonA.enabled = false
buttonB.enabled = false
buttonC.enabled = false
buttonD.enabled = false
CheckAnswer(3)
}

@IBAction func PressedButtonNext(sender: UIButton) {
print("button Next pressed")
let randomNumber = Int(arc4random_uniform(UInt32(allEntries.count)))
LoadQuestion(randomNumber)
// we need to play a sound effect for the next question coming
PlaySoundButton()

}

func CheckAnswer( answerNumber : Int)
{
if(answerNumber == currentCorrectAnswerIndex)
{
// we have the correct answer
labelFeedback.text = "Correct! +1"
labelFeedback.textColor = UIColor.greenColor()
score = score + 1
labelScore.text = "score: \(score)"
SaveScore()
// later we want to play a "correct" sound effect
PlaySoundCorrect()

}
else
{
// we have the wrong answer
labelFeedback.text = "Wrong answer"
labelFeedback.textColor = UIColor.redColor()
// we want to play a "incorrect" sound effect
PlaySoundWrong()
}

buttonNext.enabled = true
buttonNext.hidden = false
}

func PlaySoundCorrect()
{
let alertSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("correct", ofType: "mp3")!)

let error:NSError?
do {
audioPlayer = try AVAudioPlayer(contentsOfURL: alertSound)
} catch let error1 as NSError {
error = error1
print(error)
}
audioPlayer.prepareToPlay()
audioPlayer.play()


}

func PlaySoundWrong()
{
let alertSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("wrong", ofType: "wav")!)

var error:NSError?
do {
audioPlayer = try AVAudioPlayer(contentsOfURL: alertSound)
} catch let error1 as NSError {
error = error1
print(error)
}
audioPlayer.prepareToPlay()
audioPlayer.play()
}


func PlaySoundButton()
{
let alertSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("button", ofType: "wav")!)

let error:NSError?
do {
audioPlayer = try AVAudioPlayer(contentsOfURL: alertSound)
} catch let error1 as NSError {
error = error1
print(error)
}
audioPlayer.prepareToPlay()
audioPlayer.play()
}


func AdjustInterface()
{
let screenSize: CGRect = UIScreen.mainScreen().bounds
let screenWidth = screenSize.width
let screenHeight = screenSize.height

buttonA.center = CGPointMake(screenWidth / 2, buttonA.center.y)
buttonB.center = CGPointMake(screenWidth / 2, buttonB.center.y)
buttonC.center = CGPointMake(screenWidth / 2, buttonC.center.y)
buttonD.center = CGPointMake(screenWidth / 2, buttonD.center.y)
buttonNext.center = CGPointMake(screenWidth / 2, buttonNext.center.y)
labelQuestion.center = CGPointMake(screenWidth / 2, labelQuestion.center.y)

BackgroundImageView.frame = CGRectMake(0, 0, screenWidth, screenHeight)


}

}


This viewcontroller is the complete one before I started to try and check if the question is already viewed. So, to clarify, in the above viewcontroller how do i:


  • Get the ID from the JSON entry in the question

  • store that in an array called alreadyAsked

  • check if the question that is loaded has already been seen. If so, skip and choose another

  • if all questions have been seen, display in console none left to view


Answer
  • Get the ID from the JSON entry in the question

Any reason you need to have the ID thing in Int? If no, keep your questionID as a String.

let questionID = entry["id"] as! String

(If yes, you can do something like this, but I omit this case.)

        let questionID = Int(entry["id"] as! String)!

(Assuming your "content.json" never contains unexpected values...)

  • store that in an array called alreadyAsked

Declare an instance property as:

    var alreadyAsked: Set<String> = []

And append the questionID as :

        alreadyAsked.insert(questionID)

(I hope you know where you need to put this line.)

  • check if the question that is loaded has already been seen. If so, skip and choose another

You can check it here and there in your code, but one possible place is inside the LoadQuestion(_:) method.

You may need to return a value which indicates that the question is already asked:

    func loadQuestion(index : Int) -> Bool

(You'd better follow a simple naming guideline: Use upper camel case only for types. Which improves the readability of your code when shared. I mean "shared" includes posting a question in a Q&A site.)

And after you get that questionID, check it:

        if alreadyAsked.contains(questionID) {
            return false //already asked, so not again
        }

You need to put return true at the end of loadQuestion(_:).

You may need to repeat until loadQuestion(_:) returns true.

        var randomNumber: Int
        repeat {
            randomNumber = Int(arc4random_uniform(UInt32(allEntries.count)))
        } while !loadQuestion(randomNumber)

(This is not efficient, but practically ("seemingly" would fit better?) it works. And keeping your word "If so, skip and choose another")

  • if all questions have been seen, display in console none left to view

Check it before entering the loop above:

        if allEntries.count > alreadyAsked.count {
            var randomNumber: Int
            repeat {
                randomNumber = Int(arc4random_uniform(UInt32(allEntries.count)))
            } while !loadQuestion(randomNumber)
            loadScore()

            adjustInterface()
        } else {
            //display in console none left to view
            //...
        }

Your code has many parts which are unsafe or in a bad manner, and some of them may be critical. You may post another question if you cannot resolve it yourself.