Swift array not maintaining items properly - ios

Bear with me as I try to describe this. I'm creating a trivia game in Xcode which reads questions, answer choices, and the correct answer expected from a file. The file contains 20 questions worth of data separated by an asterisk (*) for each line. The first line is the question, the next four lines are the choices, and the last line is the correct answer (this format is repeated for each question).
In the code I create a string (questionFileContents) which contains all the text from the text file containing the questions.
In my createArrays() method I create a new array which contains each piece of the file as a separate string (determined by where the * is). I create a new array which contains 6 pieces of info (question, choices, and the correct answer) - this array gets loaded into the arrayOfArrays once it is full with the 6 pieces of info and then it moves on to adding a new array with another 6 pieces of info.
Hopefully, that makes sense.
The problem that I am getting is that when I use print(arrayOfArrays.count) it states that I only have 17 items within that array even though I should be getting 20 (for each of the 20 different questions). When I add a bunch of empty text to the text file (equivalent to the number of questions the arrayOfArrays was disregarding) it then disregards that and includes the questions which it had been disregarding before. So... what is causing the arrayOfArrays to not contain the 20 items it should be containing? Is this a compiler error, if not, where is my logic wrong?
I've included my code as well as the text file from which I am reading the question contents.
Thanks in advance!
import UIKit
class QuestionAnswerController: UIViewController {
#IBOutlet weak var textViewForQuestions: UITextView! // the textview which displays the current question text
#IBOutlet weak var questionNumberView: UITextView!
#IBOutlet weak var button1Text: UITextView! // these are the different textviews which correspond to the buttons and answers
#IBOutlet weak var button2Text: UITextView!
#IBOutlet weak var button3Text: UITextView!
#IBOutlet weak var button4Text: UITextView!
#IBOutlet weak var scoreView: UITextView! // textview which indicates the user's score
var questionFileContents : String = "" // blank string which will contain the different contents (questions, choices, answers)
var arrayOfArrays : Array = [[String]]() // array which contains the different arrays with their question components
var currentTrackerOfArrays : Int = 0 // keeps track of which item from the string is being added to the addingToArray array
var currentAnswer : String = "" // keeps track of what the correct answer is for this question
var userScore : Int = 0 // keeps track of the user's current score
var userSelectedAnswer : String = "" // keeps track of the user's current answer provided when they hit the answer button
var currentQuestion : Int = 1
#IBAction func answerButtonPressed(_ sender: UIButton) { // do something when the user presses a button
if sender.tag == 0 { // if the button pressed is (insert num here), do this
// sets the user's selected answer to whatever they chose
userSelectedAnswer = """
A
"""
checkForCorrectAnswer() // checks to see if the answer was correct
}
else if sender.tag == 1 {
userSelectedAnswer = """
B
"""
checkForCorrectAnswer()
}
else if sender.tag == 2 {
userSelectedAnswer = """
C
"""
checkForCorrectAnswer()
}
else if sender.tag == 3 {
userSelectedAnswer = """
D
"""
checkForCorrectAnswer()
}
newQuestionSet() // updates the list of choices as well as the question which is presented to the user
}
override func viewDidLoad() { // upon the view loading, do...
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
createArrays()
updateUI()
newQuestionSet()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func createQuestionsListString() { // takes all content from the questions file and makes a string with the content
if let filepath = Bundle.main.path(forResource: "TriviaQuestions-Formatted", ofType: "txt") { // the main filepath for the file
do {
let contents = try String(contentsOfFile: filepath) // attempts to make string with file
questionFileContents = contents // sets the questionFileContents variable to the contents of the file
} catch let error as NSError { // in the event of an error, do...
// contents could not be loaded
print("Contents could not be loaded.")
print(error.description)
}
}
}
func newQuestionSet() { // sets all the different elements of the user interface for the next question
if arrayOfArrays.count > 0 { // if there is still questions left in the array, do...
let randomQuestionInt = Int(arc4random_uniform(UInt32(arrayOfArrays.count) - 1)) // chooses a random question number
textViewForQuestions.text = arrayOfArrays[randomQuestionInt][0] // sets the text of the question
button1Text.text = arrayOfArrays[randomQuestionInt][1] // these set the different choices' text to be the choices from the question array
button2Text.text = arrayOfArrays[randomQuestionInt][2]
button3Text.text = arrayOfArrays[randomQuestionInt][3]
button4Text.text = arrayOfArrays[randomQuestionInt][4]
currentAnswer = arrayOfArrays[randomQuestionInt][5] // sets the current correct answer
arrayOfArrays.remove(at: randomQuestionInt) // prevents repeated questions
// print(arrayOfArrays.count)
}
else { // in the event that there are no more questions remaining, do...
textViewForQuestions.text = "Finished."
currentAnswer = ""
}
}
func updateUI() { // updates the user interface with the current score
scoreView.text = "Score: \(userScore)"
questionNumberView.text = "Question number: \(currentQuestion)"
}
func checkForCorrectAnswer() {
if userSelectedAnswer == String(currentAnswer) { // if the user selected answer is the same as the correct answer for the question, do...
userScore += 1 // update the score
currentQuestion += 1
updateUI() // update the UI with the new score
}
else {
currentQuestion += 1
updateUI()
}
}
func createArrays() { // creates the arrays for the questions and the array of arrays
createQuestionsListString() // calls the method to make the string from the questions file
let questionPiecesArray = questionFileContents.split(separator: "*") // breaks apart the string based on where an asterix is located (and puts the pieces in an array)
var addingToArray : Array = [String]() // the array which contains the different components of the question (question, choices, correct answer) which will be added to the array of arrays
for _ in questionPiecesArray { // for however many pieces are in the questionPiecesArray, do...
if addingToArray.count >= 6 { // if the array storing the current question gets filled with 6 or more objects, do...
arrayOfArrays.append(addingToArray) // adds the question array to the array containing all the question arrays
addingToArray.removeAll() // empties the question array to make room for new question components
}
else if addingToArray.count <= 6 { // if the array isn't full, do...
addingToArray.append(String(questionPiecesArray[currentTrackerOfArrays])) // addsar the current question component (from questionPiecesArray) to the question array
currentTrackerOfArrays += 1 // moves onto the next piece of information
}
}
print(arrayOfArrays.count)
print(questionPiecesArray.count)
print(arrayOfArrays)
// current problem, the array of arrays is maxing out at holding 17 arrays and won't hold anymore ...
// this problem makes no sense because the print(questionPiecesArray.count) method is showing ...
// that there are 120+ objects in the array so the for loop should add that many objects to the various arrays
// through testing it seems that the arrayOfArrays always has 4 less arrays than it should
// I'll just repeat the last 4 questions again so that they get included in the mix (?)...
// Perhaps it would be better to put in meaningless text for the last 4 blank question templates so that if a glitch occurs it will be more obvious
// Yeah, I'll do that
// Test: it seems to be working well with the empty text
}
}
This is what is contained in the txt file it is reading from:
How do crickets hear?*
Through their wings*
Through their belly*
Through their knees*
Through their tongue*
C*
Which American city invented plastic vomit?*
Chicago*
Detroit*
Columbus*
Baltimore*
A*
In ‘Ben Hur’, which modern thing can be seen during the chariot scene?*
A waitress*
A car*
A postbox*
A street lamp*
B*
What was Karl Marx’s favorite color?*
Brown*
Blue*
Red*
Purple*
C*
What’s the best way to stop crying while peeling onions?*
Lick almonds*
Suck lemons*
Eat cheese*
Chew gum*
D*
How old was the youngest Pope?*
11*
17*
22*
29*
A*
Which animal sleeps for only five minutes a day?*
A chameleon*
A koala*
A giraffe*
A beaver*
C*
How many words in the English language end in “dous"?*
Two*
Four*
Six*
Eight*
B*
One human hair can support how many kilograms?*
Three*
Five*
Seven*
Nine*
A*
The bikini was originally called the what?*
Poke*
Range*
Half*
Atom*
D*
Which European city is home to the Fairy Investigation Society?*
Poznan*
Dublin*
Bratislava*
Tallinn*
B*
What’s a frog’s favourite colour?*
Blue*
Orange*
Yellow*
Brown*
A*
Which one of these planets rotates clockwise?*
Uranus*
Mercury*
Pluto*
Venus*
D*
What perspires half a pint of fluid a day?*
Your scalp*
Your armpits*
Your feet*
Your buttocks*
C*
St Stephen is the patron saint of who?*
Plumbers*
Bricklayers*
Roofers*
Carpenters*
B*
Which country leads the world in cork production?*
Greece*
Australia*
Spain*
Mexico*
C*
On average, what do you do 15 times a day?*
Laugh*
Burp*
Break wind*
Lick your lips*
A*
What colour was Coca-Cola originally?*
Red*
Purple*
Beige*
Green*
D*
Bubble gum contains what?*
Plastic*
Calcium*
Rubber*
Pepper*
C*
The inventor of the paint roller was of which nationality?*
Hungarian*
Canadian*
Norwegian*
Argentinian*
B*
Please note: there were no problems with reading from the text file.

The problem is in your else if statement. Use this for loop to get the right result (I've tested it):
for item in questionPiecesArray {
// always add the current item to the array
addingToArray.append(String(item))
// if it was last for the current question, reset the addingToArray
if addingToArray.count >= 6 {
arrayOfArrays.append(addingToArray)
addingToArray.removeAll()
}
}
Also, using this you won't need currentTrackerOfArrays anymore.

Related

Swift: how to always get different value from shuffled array [duplicate]

This question already has answers here:
Get a random unique element from an Array until all elements have been picked in Swift
(5 answers)
Closed 3 years ago.
I created an array of words basically inside of a button, and each time I press my button I get a random item from the array. Now sometimes I get identical items. What if I don't want my items to repeat themselves and I always want to get new items? (Obviously I even want them to repeat their cycle after they all have ben showed once).
#IBOutlet weak var shoppingLabel : UILabel!
#IBAction func shoppingListButton(_ sender: Any) {
var shoppingList = ["Oranges","Apples","Broccoli"].shuffled()
print(shoppingList)
resultLabel.text = shoppingList.first ?? ""
}
this is not a duplicate as the similar question has an array outside of the button and is a var array, mine is a let. With my array I'm unable to remove items from it because it can't be changed, and no, I can't make it a var array...
To cycle through a random array:
Create the array
Shuffle it once
Pick values by cycling through the array from the beginning to the end
To achieve 1) and 2) simply define the array as a constant and shuffle it outside the method in which you want to use it.
To achieve 3) create an additional variable to keep track of which index of the array you are currently at, and increment it after picking a value.
To make sure you don't go beyond the bounds of the array and to achieve the "cycling" through of the array, reset the index to 0 when the index becomes greater than the last index of the array. A simple way to do this, is to use the remainder operator % in Swift.
E.g.
let shoppingList = ["Oranges", "Apples", "Broccoli"].shuffled()
var currentIndex = 0
#IBAction func shoppingListButton(_ sender: Any) {
// pick an item
let nextItem = shoppingList[currentIndex]
// update the label
resultLabel.text = nextItem
// increment the index to cycle through items
currentIndex = (currentIndex + 1) % shoppingList.count
}
To pick random non-repeating values from an array:
Create the array
Pick a random value from the array
If the picked value equals the last value, pick a new one
To achieve 2) use the randomElement() function to pick a random element. This is less computationally expensive than shuffling the entire array and picking the first element each time.
To achieve 3) use a while loop or similar to keep picking random elements until a new one is generated.
E.g.
let shoppingList = ["Oranges", "Apples", "Broccoli"]
#IBAction func shoppingListButton(_ sender: Any) {
// pick a random element that is not equal to the last one
var nextItem: String?
repeat {
nextItem = shoppingList.randomElement()
} while nextItem == resultLabel.text
// update the label
resultLabel.text = nextItem
}

Passing a Swift variable from one IBAction to another [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
In my application I have a "Play" button which creates two random numbers and creates a string for those numbers, which is then used to fill in the two UIImage holders for cards (cards 0-10 which are set in a Array). Then the two random numbers are added together.
There is an input field where the user can input their guess and then hit "Submit". If their answer is correct it will flash "Correct". If the answer is wrong, it will flash Try Again. My question is how can I get the total variable passed to the Submit so I can successfully create my if else statements?
So here is the end code to the Play button Action which works perfectly
let total = firstRandomNumber + secondRandomNumber
Here is the code for the Submit button which is giving me an error
#IBAction func submit(sender: UIButton) {
correct.hidden = (true)
tryagain.hidden = (true)
let guess = Double(text.text!)
if guess = total {
correct.hidden = (false)
}
The error I'm getting is "undefined variable total"
So the variables are not going to be PASSED to Submit() because that's the function the button calls. The button can careless about your two variables. Instead you'll want to have two Global variables (in this case the two random numbers) and request them in Submit. These are variables that was created outside any functions and are set when they need to be set. So lets see how you'd do that with your code:
// Two random numbers (global)
var a: Int?
var b: Int?
func generateRandomNumber() {
a = // assign a to the number generator value
b = // same as above
}
func submit() { // your button call
if let userGuess = text.text { // do if let instead of ! it's safer
let total = a + b
if userGuess == total { // our check remember == is different from =
correctView.hidden = false
} else { // if it's wrong show this view
tryAgainView.hidden = false
}
}
}
Obviously there's a bunch missing but you can get the main idea. Let me know if these anything I can answer about this method.

How to temporarily remove swift array members?

I'm a bit new to Swift (so sorry if my question is a bit nooby) but...
So far my program takes strings in an array and displays them on the screen (Xcode)
I would like to implement a mechanism which makes it so that the user cannot get the same string twice in a row (when he/she presses the button).
My idea was to see if the random generated string is equal to the string already displayed on the label and (if thats true), delete the generated string from the array, run the function to display a random string from the array and then add that same string back after displaying a random fact.
Array of facts (in FactModel.swift):
struct FactModel {
var facts = [
"Ants stretch when they wake up in the morning.",
"Ostriches can run faster than horses.",
"Olympic gold medals are actually made mostly of silver.",
"etc..."
]
Function that gets returns a fact and the array index of that fact (In FactModel.swift):
func getRandomFact() -> (String,Int) {
let randomNumber = GKRandomSource.sharedRandom().nextIntWithUpperBound(facts.count)
let checkFact = facts[randomNumber]
return (checkFact,randomNumber)
}
My ViewController code so far:
var mutableFactModel = FactModel()
#IBOutlet weak var FunFactButton: UIButton!
#IBOutlet weak var FunFact: UILabel!
#IBAction func ShowFunFact(sender: AnyObject) {
let localRandomFact = mutableFactModel.getRandomFact().0
if localRandomFact == FunFact.text {
mutableFactModel.facts.removeAtIndex(mutableFactModel.getRandomFact().1)
FunFact.text = mutableFactModel.getRandomFact().0
mutableFactModel.facts.append(mutableFactModel.getRandomFact().0)
} else {
FunFact.text = mutableFactModel.getRandomFact().0
}
}
It doesn't quite work the way I want it to, any ideas on making it work or a whole new way of going about it (not getting the same string twice in a row)?
Shuffle your facts array. Each time the user press the button, take the first element then remove it. Shuffling an array is akin to sort it in random order:
var facts = [
"Ants stretch when they wake up in the morning.",
"Ostriches can run faster than horses.",
"Olympic gold medals are actually made mostly of silver.",
"etc..."
].sort { f in arc4random_uniform(1000) % 2 == 0 }

Using UIButton Text as Text Input - Swift

Hello I have a profilelbl variable as below which is a uibutton. I want the text of the button to be an input in my database (parse). But I couldn't figured it out. I tried lots of things but still getting error:
#IBOutlet weak var profileLbl: UIButton!
var notification = PFObject(className: "notifications")
notification["actionReceiverName"] = profilelbl.text /*not working*/
/* also tried
notification["actionReceiverName"] = sender.profilelbl.text
notification["actionReceiverName"] = profilelbl.title */
you can do it easy like that
if let button = profilelbl as? UIButton {
if let title = button.titleForState(.Normal) {
println(title)
notification["actionReceiverName"] = title
}
}
Using UI objects to save/load data is a very bad idea. Using user-visible strings programmatically is an even worse idea. #ÖzgürErsil answered the question you asked, but the better answer to your question is "Don't do that. Ever."
Here are 2 examples where your approach will fail:
If 6 months later you want to change your UI and rename your button,
you won't remember that the button title is used in code and your
code will break. To that you would have to alter your database to
use a different string value.
If you decide to localize your app for foreign
languages, the button titles will come up in the local language, and
your code will break. There is no clean way to fix this problem,
since each local language would use a different version of the
button title.
It would be better to put unique tag numbers on your buttons, then look up text strings using the tags and pass those strings to your database.
Say you have button tags starting at 100.
You'd use code like this:
let buttonStrings = ["button1", "button2", "button3"]
let baseButtonTag = 100;
#IBAction func handleButton(sender: UIButton)
{
let tag = sender.tag
if tag >= baseButtonTag && tag < baseButtonTag + buttonStrings.count
{
let index = sender.tag - baseButtonTag
let buttonString = buttonStrings[index];
//Now use buttonString with your database as desired.
}
}

How to implement functions count and dropLast in swift, IOS?

I am making calculator in Swift. Stuck in backspace button. If user press wrong digit then backspace button would help to delete digit off the display.
Though I wrote dropLast function and works. It return appropriate result. How to use count method, don't understand the return type of count method.
#IBOutlet weak var display: UILabel!
#IBAction func backspace() {
//how to use count method to check collection of elements
//dropLast drop the last digit and display result
let dropedDigit = dropLast(display.text!)
display.text = dropedDigit
}
How about something like this:
private func dropLast(text: String) -> String {
let endIndex = advance(text.endIndex, -1)
return text.substringToIndex(endIndex)
}
It calculates the index where you want to make the cut (endIndex of text - 1) and then returns the substring to this index. This function should drop the last character.
I am not using count method here, but for you reference Swift 1.2 introduces count(<#x: T#>) method that calculates length of sets including Strings.
I know this thread is outdated, but I just went through the process of making this work, myself, in Swift 2.2, and figured I could help answer it.
#IBAction func delButton(sender: AnyObject) {
if display.text != nil {
var tempString = Array(display.text!.characters)
tempString.removeLast(1)
display.text = ""
for num in 0..<tempString.count {
display.text = display.text! + String(tempString[num])
}
}
}
Basically, we're checking to see that the display label has stuff in it, so we don't throw an error, and if so, making a variable in the scope of the function to hold the label's characters individually in a string. After that, we remove the last character from the array, clear out the label to ensure we aren't adding what's already there to our new values, then iterating through the updated array of characters and adding it to the label.
It's important to note that we are casting the values contained in the array as String, because they've been put into the array as character values, which operate differently than the string value the label is expecting.
Like I said, I know the thread is a little out of date, but I've been going through courses in Swift, and have discovered that while there is a plethora of information out there for Objective-C, there is perilously little information out there for how to do a lot of those things in Swift. Since the language is being updated repeatedly, I've noticed a growing divide between the two languages.

Resources