IBAction sometimes not being called when touching buttons - Swift - ios
I am trying to develop a word game where players click buttons to select letters.
There seems to be problem where my buttons sometimes do not register touches. It only seems to occur if there is a pause for a few seconds with no user interaction before a button touch. If the first touch works, quick follow up touches also work.
#IBAction func tileButton1(_ sender: UIButton) {
print("Tile 1 Selected")
tileSelected(tileSelected: 1)
}
#IBAction func clearButton(_ sender: Any) {
clearSelectedTiles()
}
#IBAction func SubmitButton(_ sender: Any) {
//print("Submit Button Pressed")
checkIfSubmittedWordIsValid()
}
checkIfSubmittedWordIsValid
func checkIfSubmittedWordIsValid() {
var alreadySelectedWords: [String] = []
switch currentPlayer {
case 1:
alreadySelectedWords = player1words
case 2:
alreadySelectedWords = player2words
case 3:
alreadySelectedWords = player3words
case 4:
alreadySelectedWords = player4words
default:
break
}
if currentWord.characters.count < 3 {
print("Too short")
playSound(fileName: "invalidWord", fileExtension: "aiff", volume: 1.0)
} else if alreadySelectedWords.contains(currentWord) {
print("Already picked this word")
playSound(fileName: "invalidWord", fileExtension: "aiff", volume: 1.0)
} else if wordList.contains(currentWord.lowercased()) {
print("Valid Word")
playSound(fileName: "goodWord", fileExtension: "wav", volume: 0.5)
addWordToPlayerList(word: currentWord)
} else {
print("Not in dictionary")
playSound(fileName: "invalidWord", fileExtension: "aiff", volume: 1.0)
}
clearSelectedTiles()
}
clearSelectedTiles
func clearSelectedTiles() {
tile1.alpha = 1
tile2.alpha = 1
tile3.alpha = 1
tile4.alpha = 1
tile5.alpha = 1
tile6.alpha = 1
tile7.alpha = 1
tile8.alpha = 1
tile9.alpha = 1
tile10.alpha = 1
tile11.alpha = 1
tile12.alpha = 1
tile13.alpha = 1
tile14.alpha = 1
tile15.alpha = 1
tile16.alpha = 1
selectedTiles.removeAll()
validTiles = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
selectedWordLabel.text = ""
currentWord = ""
}
Nothing gets printed when the issue occurs. Following up quickly with a second touch will trigger the IBAction and print to log.
It seems to happen also with all my other buttons (another 15 'tile' buttons and a 'Clear' and 'Submit' button)
What am I doing wrong?
Link to video showing issue First few touches work but then weirdness.
tileSelected
func tileSelected(tileSelected: Int) {
if isTileValid(tile: tileSelected) {
selectedTiles.append(tileSelected)
var surroundingTiles: [Int] = []
switch tileSelected {
case 1:
tile1.alpha = 0.5
surroundingTiles = [2,5,6]
case 2:
tile2.alpha = 0.5
surroundingTiles = [1,3,5,6,7]
case 3:
tile3.alpha = 0.5
surroundingTiles = [2,4,6,7,8]
case 4:
tile4.alpha = 0.5
surroundingTiles = [3,7,8]
case 5:
tile5.alpha = 0.5
surroundingTiles = [1,2,6,9,10]
case 6:
tile6.alpha = 0.5
surroundingTiles = [1,2,3,5,7,9,10,11]
case 7:
tile7.alpha = 0.5
surroundingTiles = [2,3,4,6,8,10,11,12]
case 8:
tile8.alpha = 0.5
surroundingTiles = [3,4,7,11,12]
case 9:
tile9.alpha = 0.5
surroundingTiles = [5,6,10,13,14]
case 10:
tile10.alpha = 0.5
surroundingTiles = [5,6,7,9,11,13,14,15]
case 11:
tile11.alpha = 0.5
surroundingTiles = [6,7,8,10,12,14,15,16]
case 12:
tile12.alpha = 0.5
surroundingTiles = [7,8,11,15,16]
case 13:
tile13.alpha = 0.5
surroundingTiles = [9,10,14]
case 14:
tile14.alpha = 0.5
surroundingTiles = [9,10,11,13,15]
case 15:
tile15.alpha = 0.5
surroundingTiles = [10,11,12,14,16]
case 16:
tile16.alpha = 0.5
surroundingTiles = [11,12,15]
default:
// do nothing
break
}
updateValidTiles(surroundingTiles: surroundingTiles)
//print("Updated Valid Tiles")
//print(validTiles)
//print("Selected Tiles")
//print(selectedTiles)
currentWord = currentWord + boardTiles[tileSelected - 1].tileLetter
selectedWordLabel.text = currentWord
}
}
Try changing touch down to touch up inside, and try changing the state of the button each time it is clicked
I had some labels constrained to the Top Layout Guide.bottom even though I was hiding the status bar with override var prefersStatusBarHidden.
Changing the relevant label constraints to topMargin instead of Top Layout Guide.bottom has solved my problem with touches sometimes being missed.
Related
History in calculator (Swift)
I have code for running the history in the application. My app screenshot: How can I improve it so that the numbers are displayed immediately when pressed (as shown in the video), and not just by pressing =. // Connected to button "=" #IBAction func equalitySignPressed(sender: UIButton) { if stillTyping { secondOperand = currentInput } dotIsPlaced = false addHistory(text: operationSign + displayResultLabel.text!) switch operationSign { case "+": operateWithTwoOperands{$0 + $1} case "-": operateWithTwoOperands{$0 - $1} case "×": operateWithTwoOperands{$0 * $1} case "÷": operateWithTwoOperands{$0 / $1} default: break } } func addHistory(text: String){ //Add text resultLabelText.text = resultLabelText.text! + "" + text }
One option could be to define a separate variable for the label string that is constantly updated using calls to addHistory() after every UIButton press (number or operator), and then the updating of the label itself handled by didSet inside the variable definition: var resultLabelString: String = "" { didSet { self.resultLabelText.text = self.resultLabelText.text! + "" + resultLabelString } } func addHistory(text: String){ self.resultLabelString = text }
Swift: If Let with Multiple Cell Classes
I have a UITableView with 2 prototype cells: CourseCell and BllCell. These cells contain a UISlider and an IBAction is in place on the main ViewController. After the slider has finished moving the following action is triggered: #IBAction func sliderFinishedMoving(_ sender: Any) { if let slider = sender as? gradeSlider { if let superview = slider.superview { if let cell = superview.superview as? CourseCell { let selectedSemester = cell.activeSemester let selectedIndexPath = courseTable.indexPath(for: cell)! let selectedCourse = courseTypes[selectedIndexPath.section].courses[selectedIndexPath.row] if selectedCourse.examType == "" && selectedCourse.examCourse == true { print("please select the exam type before entering grades") slider.value = 0.0 cell.gradeSliderLabel.frame.origin.x = slider.thumbCenterX - (cell.gradeSliderLabel.frame.width/2) cell.gradeSliderLabel.text = "0" return } print(selectedIndexPath.section, selectedIndexPath.row) slider.value = round(slider.value) cell.gradeSliderLabel.frame.origin.x = slider.thumbCenterX - (cell.gradeSliderLabel.frame.width/2) switch(selectedSemester) { case 1: selectedCourse.semester1Grade = Int(slider.value) break case 2: selectedCourse.semester2Grade = Int(slider.value) break case 3: selectedCourse.semester3Grade = Int(slider.value) break case 4: selectedCourse.semester4Grade = Int(slider.value) break case 5: selectedCourse.examGrade = Int(slider.value) break case 6: selectedCourse.oralGrade = Int(slider.value) break default: break } courseTable.reloadData() } if let cell = superview.superview as? BllCell { let selectedSemester = cell.activeSemester let selectedIndexPath = courseTable.indexPath(for: cell)! let selectedCourse = courseTypes[selectedIndexPath.section].courses[selectedIndexPath.row] if selectedCourse.examType == "" && selectedCourse.examCourse == true { print("please select the exam type before entering grades") slider.value = 0.0 cell.gradeSliderLabel.frame.origin.x = slider.thumbCenterX - (cell.gradeSliderLabel.frame.width/2) cell.gradeSliderLabel.text = "0" return } print(selectedIndexPath.section, selectedIndexPath.row) slider.value = round(slider.value) cell.gradeSliderLabel.frame.origin.x = slider.thumbCenterX - (cell.gradeSliderLabel.frame.width/2) switch(selectedSemester) { case 1: selectedCourse.semester1Grade = Int(slider.value) break case 2: selectedCourse.semester2Grade = Int(slider.value) break case 3: selectedCourse.semester3Grade = Int(slider.value) break case 4: selectedCourse.semester4Grade = Int(slider.value) break case 5: selectedCourse.examGrade = Int(slider.value) break case 6: selectedCourse.oralGrade = Int(slider.value) break default: break } courseTable.reloadData() } } } } As you can see, there is duplicate code. Is it possible to fire if let cell ... with both CourseCell and BllCell in order to access the cell.activeSemester variable in both without having to duplicate the function?
You have to make a common parent for both BllCell CourseCell if you are performing the same instruction.
There are numerous issues with this code but the immediate answer to your specific question is to use a protocol: protocol GradeSliderCell: class { var activeSemester: Int { get } var gradeSliderLabel: UILabel! { get } } Both CourseCell and BllCell should conform to this protocol. That cuts the duplicated code in half: #IBAction func sliderFinishedMoving(_ sender: Any) { guard let slider = sender as? gradeSlider else { return } guard let superview = slider.superview else { return } if let cell = superview.superview as? GradeSliderCell { let selectedSemester = cell.activeSemester let selectedIndexPath = courseTable.indexPath(for: cell as! UITableViewCell)! let selectedCourse = courseTypes[selectedIndexPath.section].courses[selectedIndexPath.row] if selectedCourse.examType == "" && selectedCourse.examCourse == true { print("please select the exam type before entering grades") slider.value = 0.0 cell.gradeSliderLabel.frame.origin.x = slider.thumbCenterX - (cell.gradeSliderLabel.frame.width/2) cell.gradeSliderLabel.text = "0" return } print(selectedIndexPath.section, selectedIndexPath.row) slider.value = round(slider.value) cell.gradeSliderLabel.frame.origin.x = slider.thumbCenterX - (cell.gradeSliderLabel.frame.width/2) switch(selectedSemester) { case 1: selectedCourse.semester1Grade = Int(slider.value) case 2: selectedCourse.semester2Grade = Int(slider.value) case 3: selectedCourse.semester3Grade = Int(slider.value) case 4: selectedCourse.semester4Grade = Int(slider.value) case 5: selectedCourse.examGrade = Int(slider.value) case 6: selectedCourse.oralGrade = Int(slider.value) default: break } courseTable.reloadData() } }
Highlighting the correct answer in a quiz app
I'm new to iOS development and have been working through some online courses. In one of these we develop a quiz app and I'd like to improve my skills by improving the app beyond what is covered in the course. The app uses a .json file as the 'database' of questions and answers. This .json file looks as follows... { "id" : "1", "question": "Earth is a:", "answers": [ "Planet", "Meteor", "Star", "Asteroid" ], "difficulty": "1" } ...and just keeps going for over 500 questions. At present, the app presents the user a question with the four possible answers. In the .json file, the first answer is always the correct answer, but the app is coded to shuffle the answers so that the correct answer is not always listed first. The app is also coded so that the four buttons (I use four different coloured images for the buttons) displaying the answers are disabled and also dimmed in appearance after the user makes a selection, except that the button they selected is not dimmed so that their choice is highlighted. What I would like to do is change this so that the button with the correct answer is highlighted instead, as a way of notifying the user what the correct answer was. I'm using the following code to load the questions and answers and to shuffle the answers: 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) 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; } At present I am using this code to check the answer: var currentCorrectAnswerIndex : Int = 0 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() } In terms of highlighting the buttons I was going to use coding such as: 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) } What I can't get my head around is how to code the app so that it highlights the correct answer? At present, the above code effectively highlights the button that was pressed by dimming the alpha of the 'other' buttons. How do I get the app to identify which 'shuffled' button contains the correct answer and highlight that one instead?
When rolling your dice, remember the index of the button containing the right answer. Also, you may put all buttons in an array and manipulate all of them with forall loops. That makes the code much, much cleaner and gets rid of the weird variable names and the switch case.
I'm having performance problems with using UIDynamicAnimations and creating and deleting items
I made an app based on UIDynamicBehavior. It's a game where you have a circle in the middle of the screen and there are balls launching out of the middle to the outer circle (when they hit it they disappear) where the interval between this "launches" get shorter and shorter (this does not cause the problem, you will see later). Well the problem is after about a 100 circles everything gets very laggy and each "launch" just freezes the whole game for a moment until the problem becomes so big the whole thing just freezes. The problem seems to be that I'm somehow not deleting the balls correctly. Even if the intervals don't change and the number of balls that are on the screen at the same time is always the same (3-4) the problem still occurs. This is also not a memory problem since the memory usage stays at around 20MB but the processor goes from 0% at the start and it just keeps rising and rising until its at 100% and the whole thing just stops. So when a ball I created this is all the code there: func lunchMyBall(timer: NSTimer!){ let myBall = createBall() var angle = Int(arc4random_uniform(70)) angle = angle+1-35 if angle < 0 { angle = -angle angle = angle-70 } else { angle = angle+70 } if arc4random()%2 == 1 { angle = -angle } print("angle: \(angle)") if lastBallAngle == nil { myBall.angle = Double(angle) }else{ switch lastBallColorIndex! { case 1: switch myBall.colorIndex { case 1: myBall.angle = lastBallAngle! + Double(angle) case 2: myBall.angle = lastBallAngle! + Double(angle)-90 case 3: myBall.angle = lastBallAngle! + Double(angle)+180 case 4: myBall.angle = lastBallAngle! + Double(angle)+90 default: break } case 2: switch myBall.colorIndex { case 1: myBall.angle = lastBallAngle! + Double(angle)+90 case 2: myBall.angle = lastBallAngle! + Double(angle) case 3: myBall.angle = lastBallAngle! + Double(angle)-90 case 4: myBall.angle = lastBallAngle! + Double(angle)+180 default: break } case 3: switch myBall.colorIndex { case 1: myBall.angle = lastBallAngle! + Double(angle)+180 case 2: myBall.angle = lastBallAngle! + Double(angle)+90 case 3: myBall.angle = lastBallAngle! + Double(angle) case 4: myBall.angle = lastBallAngle! + Double(angle)-90 default: break } case 4: switch myBall.colorIndex { case 1: myBall.angle = lastBallAngle! + Double(angle)-90 case 2: myBall.angle = lastBallAngle! + Double(angle)+180 case 3: myBall.angle = lastBallAngle! + Double(angle)+90 case 4: myBall.angle = lastBallAngle! + Double(angle) default: break } default: break } } if myBall.angle! > 360 { myBall.angle = myBall.angle! - 360 }else if myBall.angle! < 0 { myBall.angle = myBall.angle! + 360 } let tempAngle = CGFloat((myBall.angle!/360)*2*M_PI) lastBallAngle = myBall.angle lastBallColorIndex = myBall.colorIndex dataSource?.lunchMyBall(myBall, angle: tempAngle) let timer = NSTimer.scheduledTimerWithTimeInterval(Double(difficulty), target: self, selector: "lunchMyBall:", userInfo: nil, repeats: false) timer.tolerance = 0.01 } func createBall() -> UIItemView { var frame = CGRect(origin: CGPointZero, size: CGSize(width: 10, height: 10)) frame.origin = (CGPoint(x: (dataSource?.getCircleCenter().x)!-5, y: (dataSource?.getCircleCenter().y)!-5)) let ballView = UIItemView(frame: frame, color: UIColor.random()) return ballView } And this is how I add it to the behavior: func lunchBall(ball: UIView, angle: CGFloat) { let myLunchBehavior: UIPushBehavior? = lunchBehavior let myBall = ball as! UIItemView myBall.angle = 360-((Double(angle)/(2*M_PI))*360) myLunchBehavior!.setAngle(angle, magnitude: 0.01) myLunchBehavior!.addItem(ball) myBall.pushBehavior = myLunchBehavior dynamicAnimator?.referenceView?.addSubview(ball) addChildBehavior(myBall.pushBehavior!) collisionBehavior.addItem(ball) } And this is how I delete it: func collisionBehavior(behavior: UICollisionBehavior, beganContactForItem item: UIDynamicItem, withBoundaryIdentifier identifier: NSCopying?, atPoint p: CGPoint) { print("did") if let myItem = item as? UIItemView { if (isPointInAngle(myItem.angle!, colorIndex: myItem.colorIndex) == true) { dataSource?.scoreChanged(true) print("true") }else{ dataSource?.scoreChanged(false) print("false") } collisionBehavior.removeItem(myItem) removeChildBehavior(myItem.pushBehavior!) myItem.pushBehavior?.removeItem(myItem) myItem.removeFromSuperview() } } This is set up to fire when a collision with the other circle occurs. I'm sorry for the big amount of code but I really can't find out where the problem is happening so it's really hard for me to pin point it. I have been working on this problem for more than 2 days now and still can't figure it out. I tried everything, reusing behaviors (instead of deleting it I made an array so the next ball just takes it from there and sets a new angle), multi-threading (I'm not really experienced with that), I also looked in to Time Profiler but nothing really made sense to me there since I'm a beginner. I would really really appreciate it someone that knows what he's doing would be able to just throw an eye over it to check and spot any problems since I think the problem should probably be pretty obvious for an experienced programmer. Here is the link to my project if that helps. So you can see I have two things that I suppose are important. I have a sub class of a UIDynamicBehavior where I have all my behaviors set up and a basic swift class "BallLauncher" that is supposed to be what causes the balls to launch so there also could be problems there.
UISegmentedControl and .selectedSegmentIndex wrong values?
I have a simple Segment in my code with 3 elements. For testing purposes I also do have a variable that increments based on which of the segments I press (3). The value of that variable is printed in a UITextView. This is the code: import UIKit class ViewController: UIViewController { #IBOutlet weak var segment: UISegmentedControl! #IBOutlet weak var prwtoView: UIView! #IBOutlet weak var prwtoText: UITextField! var i : Int = 0 override func viewWillAppear(animated: Bool) { prwtoText.backgroundColor = UIColor.purpleColor() prwtoText.textColor = UIColor.whiteColor() segment.setTitle("Zero", forSegmentAtIndex: 0) segment.addTarget(self, action: "action", forControlEvents: .ValueChanged) segment.insertSegmentWithTitle("random", atIndex: 2, animated: false) } func action() { let argumentForSegment = segment.selectedSegmentIndex if argumentForSegment == 0 { i = 0 } if argumentForSegment == 1 { i += 2 } else { i += Int(arc4random_uniform(100)) } print(argumentForSegment) prwtoText.text = "\(i)" } } While I know that it starts with value -1 i don't want my app to do anything if not pressed. The thing is that even when I press the first segment (0) and it is supposed to make i = 0 it doesn't do that, although if I print argumentForSegment in my terminal it does show the 0 as value. Concluding, every time I press the zero segment (0), my i value won't become 0. Perhaps I am using the wrong method from UISegmentedControl? edit: Got it fixed by changing the following code: func action() { let argumentForSegment = segment.selectedSegmentIndex if argumentForSegment == 0 { i = 0 } if argumentForSegment == 1 { i += 2 } else { i += Int(arc4random_uniform(100)) } print(argumentForSegment) prwtoText.text = "\(i)" } to: func action() { let argumentForSegment = segment.selectedSegmentIndex if argumentForSegment == 0 { i = 0 } if argumentForSegment == 1 { i += 2 } else if argumentForSegment == 2 // <==== here { i += Int(arc4random_uniform(100)) } print(argumentForSegment) prwtoText.text = "\(i)" } Could someone explain why it used the priority of else although the value was zero when printing argumentForSegment? In other words why when I had an else alone for the value of argumentForSegment == 0 it chose the else instead of the first statement?
Could someone explain why it used the priority of else although the value was zero when printing argumentForSegment? In other words why when I had an else alone for the value of argumentForSegment == 0 it chose the else instead of the first statement? When you have a situation where the code is not behaving as you expect, it is helpful to step through it in the debugger, or add some diagnostic print statements. For example: func action() { let argumentForSegment = segment.selectedSegmentIndex if argumentForSegment == 0 { print("In first block") i = 0 } if argumentForSegment == 1 { print("In second block") i += 2 } else { print("In third block") i += Int(arc4random_uniform(100)) } print(argumentForSegment) prwtoText.text = "\(i)" } If you do this, you will notice that when argumentForSegment is 0, the output will be: In first block In third block So, the problem is not that it is choosing the third block over the first. The problem is that it is doing both. You want it to stop after it has detected that argumentForSegment is 0, so add an else to the second conditional statement so that it only does that when the first conditional statement failed: func action() { let argumentForSegment = segment.selectedSegmentIndex if argumentForSegment == 0 { i = 0 } else if argumentForSegment == 1 // added "else" here { i += 2 } else { i += Int(arc4random_uniform(100)) } print(argumentForSegment) prwtoText.text = "\(i)" }
To improve on Vacawama's answer, you can format this much easier by using a switch statement: func action() { let argumentForSegment = segment.selectedSegmentIndex switch argumentForSegment { case 0: i = 0 case 1: i += 1 case 2: i += Int(arc4random_uniform(100)) default: break } print(argumentForSegment) prwtoText.text = "\(i)" } it's much more clean for this type of thing. (thanks, vacawama)