This is my code, The correct answer randomizes its position in the number of buttons. however, the current code seems to duplicate the 4th answer
It does this for every question and their respective set of answers.
The buttons tags are setup simply, the first one is tagged "1" and the 2nd button is tagged "2" and so on.
If any more info is needed feel free to ask.
I've left the problem code so that others who have similar issues will have a base to see what I did wrong.
Problem Code:
//
// AnimalViewController.swift
// It's Trival
//
// Created by Chris Levely on 12/18/16.
// Copyright © 2016 ZenithNomad. All rights reserved.
//
import UIKit
class AnimalViewController: UIViewController {
let questions = ["What is the fastest fish in the sea?", "Which animal has the most legs?"]
let answers = [["Sailfish", "Tuna", "Swordfish", "Marlin"], ["Millipede", "Spider", "Ant", "Octopus"]]
var currentQuestion = 0
var rightAnswerPlacement : UInt32 = 0
#IBOutlet weak var Question: UILabel!
#IBAction func AnswerQuestion(_ sender: AnyObject)
{
if (sender.tag == Int(rightAnswerPlacement))
{
print("Right")
}
else
{
print("Wrong")
}
if (currentQuestion != questions.count)
{
newQuestion()
}
}
func newQuestion()
{
Question.text = questions[currentQuestion]
rightAnswerPlacement = arc4random_uniform(4)+1
var button : UIButton = UIButton()
var x = 1
for i in 1...4
{
button = view.viewWithTag(i) as! UIButton
if (i == Int(rightAnswerPlacement))
{
button.setTitle(answers[currentQuestion][0], for: .normal)
}
else
{
button.setTitle(answers[currentQuestion][x], for: .normal)
x = 3
}
}
currentQuestion += 1
}
override func viewDidAppear(_ animated: Bool) {
newQuestion()
}
}
New Fixed Code:
//
// AnimalViewController.swift
// It's Trival
//
// Created by Chris Levely on 12/18/16.
// Copyright © 2016 ZenithNomad. All rights reserved.
//
import UIKit
class AnimalViewController: UIViewController {
let questions = ["What is the fastest fish in the sea?", "Which animal has the most legs?"]
let answers = [["Sailfish", "Tuna", "Swordfish", "Marlin"], ["Millipede", "Spider", "Ant", "Octopus"]]
var currentQuestion = 0
var rightAnswerPlacement : UInt32 = 0
#IBOutlet weak var Question: UILabel!
#IBAction func AnswerQuestion(_ sender: AnyObject)
{
if (sender.tag == Int(rightAnswerPlacement))
{
print("Right")
}
else
{
print("Wrong")
}
if (currentQuestion != questions.count)
{
newQuestion()
}
}
func newQuestion()
{
Question.text = questions[currentQuestion]
rightAnswerPlacement = arc4random_uniform(4)+1
var button : UIButton = UIButton()
var x = 1
for i in 1...4
{
button = view.viewWithTag(i) as! UIButton
if (i == Int(rightAnswerPlacement))
{
button.setTitle(answers[currentQuestion][0], for: .normal)
}
else
{
button.setTitle(answers[currentQuestion][x], for: .normal)
x += 1
}
}
currentQuestion += 1
}
override func viewDidAppear(_ animated: Bool) {
newQuestion()
}
}
It is because you hardcode x = 3, you should randomize x as well.
Here is my approach, I test it using UILabel, but it is the same concept
func newQuestion()
{
questionLabel.text = questions[currentQuestion]
var label : UILabel = UILabel()
var xArray = [0, 1, 2, 3]
var x = Int(arc4random_uniform(UInt32(xArray.count)))
for i in 1...4
{
label = view.viewWithTag(i) as! UILabel
label.text = answers[currentQuestion][xArray[x]]
if answers[currentQuestion][xArray[x]] == answers[currentQuestion][0]
{
print(answers[currentQuestion][xArray[x]])
//this is the answer, do something when user click this button
}
//remove the index x because you don't want to get a same number next time
xArray.remove(at: x)
x = Int(arc4random_uniform(UInt32(xArray.count)))
}
currentQuestion += 1
}
Imagine that before you set the title of the correct answer to a random button you first set the title of all the buttons in the same order of the array of possible answers, no problem there, now you set the string of the right answer to a random button, if this random position is not the same as the position of the right answer in the array (0), you end up with a duplicate string, that is what is actually happening.
What you need to do is to switch the answer at position 0 (right answer) with another answer at a random position in the array or even better shuffle the array of answers, how? I think there isn't a built-in function, but you could create a new empty array and remove a random answer from the original array and add it to the new one until all the answers switch from the original array to the new one in a random order.
Hope it helps.
My solution was a little more simple than randomizing the x value. In my sample code above I have it written as x =1 to start and then in the newQuestion function as x = 3,
The solution that I have gone with, I now have it as x+=1 so it now goes through the array for each loop instead of sticking with the 4th answer in the array. may not be the most elegant of solutions but it is all that is required for this simple app
Related
When we click on the wrong option in the quiz application, the background color is red and the background color of the correct option is green. how can I do that? My model :
struct QuizModel {
let q : String
let a : [String]
let correctAnswer : Int
}
my question :
QuizModel(q: "Aşağıdakilerden hangisi ilk yardımın amaçlarından biri değildir?", a: [
"A) İlaçla tedavi etmek","B) Durumun kötüleşmesini önlemek","C) Hayati tehlikeyi ortadan kaldırmak","D) Yaşamsal fonksiyonların sürdürülmesini sağlamak"], correctAnswer: 0)
answer func :
func checkAnswer(userAnswer : Int)-> Bool {
if userAnswer == quiz[questionNumber ].correctAnswer {
return true
}else{
return false
}
}
user tapped option button :
#objc func TappedButton(_ sender : UIButton){
let userAnswer = sender.tag
let userRightGot = quizBrains.checkAnswer(userAnswer: userAnswer)
if userRightGot {
sender.backgroundColor = .green
score += 1
scoreLabel.text = "\(score)"
}else{
sender.backgroundColor = .red
failScore += 1
failScoreLabel.text = "\(failScore)"
}
The method affects only the currently tapped button, you have to update the state of the other buttons, too.
Something like this, button0-button3 are the references to the buttons, you have to add IBOutlets if there are none.
var buttons = [button0, button1, button2, button3]
// please name methods with starting lowercase letter
#objc func tappedButton(_ sender : UIButton) {
let userAnswer = sender.tag
let correctAnswer = quiz[questionNumber].correctAnswer
for button in buttons {
button.backgroundColor = button.tag == correctAnswer ? .green : .red
}
if userAnswer == correctAnswer {
score += 1
scoreLabel.text = "\(score)"
} else {
failScore += 1
failScoreLabel.text = "\(failScore)"
}
}
This question already has answers here:
Swift Array - Check if an index exists
(12 answers)
Closed 3 years ago.
I'm having issues trying to figure out why am I getting Thread 1 Fatal error: Index out of range on my app. My app displays 8 images show to start image slideshow. For some reason I'm getting this error I can't figure it out. Is there a way to remove this error? Can anyone help me?
Here is my code balow and also a screenshot link here :
var images = [UIImage]()
var counter = 2
var time = Timer()
#IBOutlet weak var menuButton: UIBarButtonItem!
#IBOutlet weak var ImageView: UIImageView!
#IBOutlet weak var Slider1: UISlider!
#IBAction func Slider(_ sender: UISlider) {
_ = 0
let value = Int(sender.value)
ImageView.image = images[value]
}
#IBAction func NextButton(_ sender: Any) {
Slider1.value += 1
ImageView.image = images[Int(Slider1.value)]
self.ImageView.animationImages = self.images
self.ImageView.animationDuration = 15.0
self.ImageView.animationRepeatCount = 0
self.ImageView.startAnimating()
UIView.transition(with: self.ImageView, duration: 5.0, options: .transitionCrossDissolve, animations: {self.ImageView.image = self.ImageView.image}, completion: nil)
}
#IBAction func PrevButton(_ sender: Any) {
Slider1.value -= 1
ImageView.image = images[Int(Slider1.value)]
self.ImageView.animationImages = self.images
self.ImageView.animationDuration = 15.0
self.ImageView.animationRepeatCount = 0
self.ImageView.startAnimating()
UIView.transition(with: self.ImageView, duration: 5.0, options: .transitionCrossDissolve, animations: {self.ImageView.image = self.ImageView.image}, completion: nil)
}
//Set Status Bar to light content (white)
override var preferredStatusBarStyle : UIStatusBarStyle {
return .lightContent
}
override func viewDidLoad() {
//Set Navigation Bar color Example Home, Back button
self.navigationItem.backBarButtonItem?.tintColor = UIColor.white;
time = Timer.scheduledTimer(withTimeInterval: 8, repeats: true) { _ in
self.NextButton(self);
}
super.viewDidLoad()
setup()
images = [#imageLiteral(resourceName: "MainImage1.jpg"), #imageLiteral(resourceName: "MainImage2.jpg"), #imageLiteral(resourceName: "MainPage3.jpg"), #imageLiteral(resourceName: "MainImage4.jpg"), #imageLiteral(resourceName: "MainImage5.jpg"), #imageLiteral(resourceName: "MainImage6.jpg"), #imageLiteral(resourceName: "MainImage7.jpg"), #imageLiteral(resourceName: "MainImage8.jpg")]
sideMenus()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func setup(){
self.navigationController?.navigationBar.tintColor = UIColor.white
}
override var prefersStatusBarHidden: Bool{
return false
}
var navigationBarAppearace = UINavigationBar.appearance()
override func viewDidAppear(_ animated: Bool){
}
func sideMenus() {
if revealViewController() != nil {
menuButton.target = revealViewController()
menuButton.action = #selector(SWRevealViewController.revealToggle(_:))
revealViewController().rearViewRevealWidth = 275
revealViewController().rightViewRevealWidth = 160
view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
}
}
The error is telling you that you're accessing an element outside the range of the images array. In viewDidLoad you set the images array with 8 elements. These elements have the range of 0-7. If you try to access an element outside that range, you get the out of range error that you're seeing.
Looking at your code, the problem is that in NextButton and PrevButton, you adjust the slider value and index, but you don't check to make sure you haven't gone past the array bounds. If you're on the first image and click the Prev button, you're out of range. If you're on the last image and click the Next button, you're out of range.
What you have to do is make sure you don't have a negative index in the array or have an index that's greater than or equal to the number of items in the array. From a user interface standpoint, the best solution is to disable the Prev button when you're on the first image and disable the Next button when you're on the last image.
Since you asked for an example, here's some code to put in nextButton after incrementing the slider value:
if slider1.value >= images.count {
return
}
And the code for prevButton after decrementing the slider value:
if slider1.value < 0 {
return
}
Now the function will exit if the array index is out of range.
One last thing to check is that the images array is being filled in viewDidLoad. Set a breakpoint in viewDidLoad and step through the code. If there's a problem loading the MainImage.jpg files, the array is going to be empty, and you'll get out of range errors when you access the array.
In the last viewcontroller of my app I wrote a code which creates a number of labels which are filled with random characters from a set
Under the labels textfields appear where the user should match the characters from the labels. When he hits the button, his answer is checked. If the answer is right the score is updated.
After that the score label should update, and new text for the labels with characters should be generated and shown, but that's where I get stuck... My instinct would be to add a loop to the code in the viewDidLoad, but as the code to check the answer is in the buttonAction function outside of the viewDidLoad, I don't know how to do this...
Here is some code from my app which might clarify things. I deleted quite a lot of straightforward code and put a comment line in just for this example.
override func viewDidLoad()
{
super.viewDidLoad()
// variables are declared, the number of labels is calculated based on the frame size
// the text for the labels is calculated based on a function in a separate class I wrote
var a = 0
while a < numberOfLabels {
// the UILabels are made and filled
// the corresponding UITextFields are made
a += 1
labelX += labelWidth
}
// then here the button is coded, the last lines are:
myButton.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
view.addSubview(myButton)
}
#objc func buttonAction(sender: UIButton!) {
var a = 0
var rightAnswer = false
var userInput: [String] = Array()
while a < numberOfLabels.shared.category! {
if let theLabel = self.view.viewWithTag(a) as? UITextField {
let tekstInput = theLabel.text
userInput.insert(tekstInput!, at:a-1)
}
a = a + 1
}
let controle = Controle()
rightAnswer = controle.checkAnswer(userAnswer: userInput)
if rightAnswer {
if var score = PassScore.shared.category {
score += 1
PassScore.shared.category = score
}
else {
var score = 1
PassScore.shared.category = score
}
}
return
}
Have your initialization code in a function
func setupLabels() {
var a = 0
while a < numberOfLabels {
// the UILabels are made and filled
// the corresponding UITextFields are made
a += 1
labelX += labelWidth
}
}
And call it whenever you need it:
override func viewDidLoad()
{
super.viewDidLoad()
setupLabels()
}
#objc func buttonAction(sender: UIButton!) {
//...
setupLabels()
}
I have 5 buttons in an array I want to make random three of these invisible when pressed on button. so the last two buttons will be visible
I tried like this:
#IBAction func eliminateChoiceClicked(_ sender: Any) {
let buttons:[UIButton] = [buttonA,buttonB,buttonC,buttonD,buttonE]
let randomNumber = Int(arc4random_uniform(UInt32(3)))
buttons[randomNumber].isHidden = !buttons[randomNumber].isHidden
}
but it takes first elements [0,1,2] and only 1 button is invisible at each press
You are currently only selecting one button. Change that to a loop to select 3. Remove the selected button from the array and make it invisible. Finally, make the remaining buttons visible:
#IBAction func eliminateChoiceClicked(_ sender: Any) {
var buttons:[UIButton] = [buttonA,buttonB,buttonC,buttonD,buttonE]
// Select 3 buttons randomly and hide them
for _ in 1...3 {
let randomNumber = Int(arc4random_uniform(UInt32(buttons.count)))
let button = buttons.remove(at: randomNumber)
button.isHidden = true
}
// Make the remaining buttons visible
for button in buttons {
button.isHidden = false
}
}
I've just written a neat extension to get a random elements from an array:
extension Array {
func randomItems(count: Int) -> [Element] {
guard count <= self.count else { fatalError() }
var notUsedElements: [Element] = self
var randomElements: [Element] = []
while randomElements.count != count {
randomElements.append(notUsedElements.remove(at: Int(arc4random_uniform(UInt32(notUsedElements.count)))))
}
return randomElements
}
}
Using this you can achieve what you want this way:
#IBAction func eliminateChoiceClicked(_ sender: Any) {
let buttons:[UIButton] = [buttonA, buttonB, buttonC, buttonD, buttonE]
// make sure all are visible at the beginning
buttons.forEach { (button) in
button.isHidden = false
}
// hide random 3:
buttons.randomItems(count: 3).forEach { (button) in
button.isHidden = true
}
}
I have two UILabels with two UITapGestureRecognizers in a UITableViewCell.
cell.Username.tag = indexPath.row
cell.SharedUser.tag = indexPath.row
let tapGestureRecognizer2 = UITapGestureRecognizer(target:self, action:"GoToProfil:")
let tapGestureRecognizer3 = UITapGestureRecognizer(target:self, action:"GoToProfil:")
cell.Username.userInteractionEnabled = true
cell.Username.addGestureRecognizer(tapGestureRecognizer2)
cell.SharedUser.userInteractionEnabled = true
cell.SharedUser.addGestureRecognizer(tapGestureRecognizer3)
func GoToProfil (sender: AnyObject!) {
self.performSegueWithIdentifier("GoToProfilSegue", sender: sender)
}
I'm using a Segue to push another UIViewController, and I'm overriding the PrepareSegue function to send the needed information corresponding to the Sender tag.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
let ProfilView = segue.destinationViewController as! Profil
ProfilView.hidesBottomBarWhenPushed = true
ProfilView.title = posts[sender.view!.tag].User?.objectForKey("Name") as? String
ProfilView.User = posts[sender.view!.tag].User
}
My problem is that I want to know which UILabel was pressed, knowing that I'm already using tag.
Your GoToProfile: function should be written properly. The parameter isn't the "sender", it's the gesture recognizer.
func GoToProfil (gestureRecognizer: UITapGestureRecognizer) {
}
From there, you can determine the label by using the view property of the gesture recognizer.
But you seem to have two conflicting requirements. You want to know which of the two labels was tapped and you want to know which row the label is in.
Normally you would use the label's tag to know which of the two labels was tapped. But you are using their tags to track the row.
The solution I recommend is to use the tag to differentiate the two labels. Then you can calculate the row based on the frame of the label.
See the following answer for sample code that translates the frame of a cell's subview to the cell's indexPath.
Making the following assumptions:
You are trying to uniquely identify the label using UIView.tag
You want different behaviour for Username & SharedUser
I recommend the following, first define your tags below your #imports
#define kUsername 1
#define kSharedUser 2
Then assign them to your views
cell.Username.tag = kUsername
cell.SharedUser.tag = kSharedUser
Then in your prepareSegue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
int tag = [sender.view!.tag]
if (tag == kUsername) {
//Username logic
} else if(tag == kSharedUser) {
//Shared User Logic
}
}
This way you can easily and simply determine tap, Note this might have different results if you have more then 1 Username & SharedUser labels. Then you will either need more #defines or change how you generate your tags.
You can add a property to UILabel to track the label's type. (I used an enum since there's just 2 cases, but it could be a string, etc.)
enum LabelDest : String
{
case Username = "Username"
case SharedUser = "SharedUser"
}
extension UILabel
{
struct Static {
static var key = "labelDest"
}
var labelDest:LabelDest? {
set { objc_setAssociatedObject( self, &Static.key, newValue?.rawValue, .OBJC_ASSOCIATION_COPY_NONATOMIC )
}
get {
guard let val = objc_getAssociatedObject( self, &Static.key ) as? String else { return nil }
return LabelDest( rawValue:val )
}
}
}
Now you can just do this:
let label = UILabel()
label.labelDest = .Username
Later:
switch label.labelDest
{
case .Some(.Username):
// handle user name
break
...
If you want to use the .tag field on your labels you can use a different technique to find the table row associated with a label: (again using class extensions)
extension UIView
{
var enclosingTableViewCell:UITableViewCell? {
return superview?.enclosingTableViewCell
}
var enclosingTableView:UITableView? {
return superview?.enclosingTableView
}
}
extension UITableViewCell
{
var enclosingTableViewCell:UITableViewCell? {
return self
}
}
extension UITableView
{
var enclosingTableView:UITableView? {
return self
}
}
extension UIView {
var tableRow:Int? {
guard let cell = self.enclosingTableViewCell else { return nil }
return self.enclosingTableView?.indexPathForCell( cell )?.row
}
}
Now, from your gesture recognizer action:
func goToProfil( sender:UIGestureRecognizer! )
{
guard let tappedRow = sender.view?.tableRow else { return }
// handle tap here...
}
You can access the sender data, and read the tag of the object that send you, like in this sample code.
To uniquely identify each row and each label, you can use something like this:
cell.Username.tag = (indexPath.row*2)
cell.SharedUser.tag = (indexPath.row*2)+1
With this, if you have a even tag, its the Username, odd will be the SharedUser. Dividing by the floor of 2 you can have the row back.
#IBOutlet weak var test1: UILabel!
#IBOutlet weak var test2: UILabel!
override func viewWillAppear(animated: Bool) {
test1.tag = 1
test2.tag = 2
test1.userInteractionEnabled = true
test2.userInteractionEnabled = true
self.test1.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "handleSingleTap:"))
self.test2.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "handleSingleTap:"))
}
func handleSingleTap(sender: UITapGestureRecognizer) {
print(sender.view?.tag)
}