Conflicting calls to viewDidLoad() and another block of code - swift

Screen in question I am trying to call the viewDidLoad() func here with the question() func a set number of times and only then call the code for the new view controller to be pushed. But, the code for both is getting executed at the same exact time such that as soon as the first round finishes, the view controller is pushed. I have tried using loops in different variations, but those attempts all led to similar results. Ideas? And thanks in advance!
var num1 = Int()
var num2 = Int()
var userInput = 0
#IBAction func numbers(_ sender: UIButton) {
answerLabel.text = answerLabel.text! + String(sender.tag - 1)
userInput = Int(answerLabel.text!)!
answer()
}
override func viewDidLoad() {
super.viewDidLoad()
// Views configuration
question()
}
func answer() {
let answer = num1 + num2
if userInput == answer {
answerLabel.text = ""
viewDidLoad()
let scoreVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "ScoreViewController") as! ScoreViewController
self.navigationController?.pushViewController(scoreVC, animated: true)
}
}
func question() {
num1 = Int(arc4random_uniform(100))
num2 = Int(arc4random_uniform(100))
questionLabel.text = "\(num1) + \(num2)"
}

There are quit e few things that we can improve here. I'll start at basic.
As these numbers are unset at the beginning we don't have to assign them yet. So instead of settings them to a default value we can leave them as Optionals which allows us to just define the type and leave them be for an assignment later on.
var num1: Int?
var num2: Int?
Now we wanna show a Question. The thing is, the viewDidLoad shall only be used when - as the name suggests - the view loads. Because of this we simply create a Method and move our logic there. You already did that, I just renamed your function to a more speaking name
viewDidLoad() {
showNewQuestion()
}
showNewQuestion() { // previously question()
num1 = Int(arc4random_uniform(100))
num2 = Int(arc4random_uniform(100))
questionLabel.text = "\(num1) + \(num2)"
}
So far so good. Now we gotta check the input against the random value in the Button send function. Instead of force unwrapping (!) it is better practise to safely unwrap (?).
#IBAction func numbers(_ sender: UIButton) {
guard let userInput = answerLabel.text, let userInputAsInt = Int(userInput) else {
return
}
checkAnswer(userInput: userInputAsInt) // previously answere()
}
Lastly we improve your answer() function. The thing you left is to count how many questions have been answered. If we don't ever check that how should the program know when to show the score? To fix this problem we remember how many questions were asked (correctAnsweres ) and we define a threshold when to show the score (showScoreAfter)
var correctAnsweres = 0
var showScoreAfter = 5
func checkAnswer(userInputAsInt: Int) {
let answer = num1 + num2
if userInputAsInt != answers { // early exit if answered wrong
return
}
correctAnsweres += 1
answerLabel.text = ""
//either show score
if correctAnsweres % ShowScoreAfter == 0 {
let scoreVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "ScoreViewController") as! ScoreViewController
self.navigationController?.pushViewController(scoreVC, animated: true)
}
// or new Question
else {
showNewQuestion()
}
}

Related

Basic Sinch Sample in Swift - but no Sound

first of all thank you for reading my lines.
For an idea I'm currently trying to dive into the Swift world (I only have very basic programming knowledge - no Objective C knowledge
).
I tried to set up the following lines to create a very basic app-to-app sample in Sinch. After my code I let you know what the issues are.
import UIKit
import Sinch
var appKey = "APP_KEY_FROM_MY_ACCOUNT"
var hostname = "clientapi.sinch.com"
var secret = "SECRET_FROM_MY_ACCOUNT"
class CViewController: UIViewController, SINCallClientDelegate, SINCallDelegate, SINClientDelegate {
var client: SINClient?
var call: SINCall?
var audio: SINAudioController?
//Text field in the main storyboard
#IBOutlet weak var userNameSepp: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.initSinchClient()
}
//initialize and start the client as a fixed "userA"
func initSinchClient() {
client = Sinch.client(withApplicationKey: appKey, applicationSecret: secret, environmentHost: hostname, userId: "userB")
client?.call().delegate = self
client?.delegate = self
client?.startListeningOnActiveConnection()
client?.setSupportCalling(true)
client?.start()
}
//Did the Client start?
func clientDidStart(_ client: SINClient!) {
print("Hello")
}
//Did the Client fail?
func clientDidFail(_ client: SINClient!, error: Error!) {
print("Good Bye")
}
//Call Button in the main.storyboard ... if call==nil do the call ... else hangup and set call to nil
//the background color changes are my "debugging" :D
#IBAction func callSepp(_ sender: Any) {
if call == nil{
call = client?.call()?.callUser(withId: userNameSepp.text)
//for testing I change to callPhoneNumber("+46000000000").
// the phone call progresses (but I hear nothing),
// the phonecall gets established (but I hear nothing)
// and the phonecall gets ended (but of course I hear nothing)
self.view.backgroundColor = UIColor.red
call?.delegate = self
audio = client?.audioController()
}
else{
call?.hangup()
self.view.backgroundColor = UIColor.blue
call = nil
}
}
func callDidProgress(_ call: SINCall?) {
self.view.backgroundColor = UIColor.green
client?.audioController().startPlayingSoundFile("/LONG_PATH/ringback.wav", loop: true)
print("Call in Progress")
}
//I know that this works but I don't hear anything
func callDidEstablish(_ call: SINCall!) {
client?.audioController().stopPlayingSoundFile()
print("Call did Establish")
}
func callDidEnd(_ call: SINCall!) {
print("Call did end")
}
// this works fine
#IBAction func hangUpSepp(_ sender: Any) {
call?.hangup()
self.view.backgroundColor = UIColor.red
call = nil
}
// i work in a "sub view controller" - so i navigate here back to the main view controller
#IBAction func goBackMain(_ sender: Any) {
call?.hangup()
dismiss(animated: true, completion: nil)
client?.stopListeningOnActiveConnection()
client?.terminateGracefully()
client = nil
}
}
So I can call my private phone number or if I change to callUser I can call another app but I don't hear anything. What do I miss? It must have to do with the SINAudioController and the client's method audioController() but I don't know what I'm doing wrong. Thank you for your help.

How do I show a message after the right answer in the array has been chosen?

So this is the program I use an array for my questions and answers. The first answer in the array is always right, but it randomises in the actual app.
I want to find out how to do something like, if someone gets the answer right, the screen fades to a different colour and tells them if they're right or not, and say for example "Yes Germany have won the most derbies" shows it on screen for a few seconds and the game carries on.
Sorry if that's a bit too much, but if you can help I'd really appreciate it.
import UIKit
class ViewController: UIViewController {
// ARRAY THAT CONTAINS QUESTIONS
let questions = ["Who's won the most England-Germany derbies in the world cup?", "Which match has had the most cards in the world cup?", "Who has done the best in the world cup"]
// ARRAY THAT CONTAINS ANSWERS
let answers = [["Germany", "England"], ["Portugal vs Netherlands", "England vs Spain"], ["Lionel Messi", "Christiano Ronaldo"]]
//Variables
var currentQuestion = 0
var rightAnswerPlacement:UInt32 = 0
var points = 0;
//Label
#IBOutlet weak var lbl: UILabel?
// start button
#IBOutlet weak var start: UIButton? ///////////////////////////////////////
//Button
#IBAction func action(_ sender: AnyObject){
if (sender.tag == Int(rightAnswerPlacement)){
print ("RIGHT!")
points += 1
} else {
print ("WRONG!!!!!!")
}
if (currentQuestion != questions.count) {
newQuestion()
} else {
performSegue(withIdentifier: "showScore", sender: self)
}
}
override func viewDidAppear(_ animated: Bool) {
newQuestion()
}
//Function that displays new question
func newQuestion() {
lbl?.text = questions[currentQuestion]
rightAnswerPlacement = arc4random_uniform(2)+1
//Create a button
var button:UIButton? = UIButton()
var x = 1
for i in 1...2 {
//Create a button
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 viewDidLoad() {
super.viewDidLoad()
start?.layer.cornerRadius = 10
}
}
There are better ways to do what you're doing but I hope this sort of helps and gives you a few pointers. All the best and merry chirstmas!
let questions = ["Who's won the most England-Germany derbies in the world cup?", "Which match has had the most cards in the world cup?", "Who has done the best in the world cup"]
let answers = [["Germany", "England"], ["Portugal vs Netherlands", "England vs Spain"], ["Lionel Messi", "Christiano Ronaldo"]]
var questionNumber: Int = 0 // first question
var correctAnswer: Int = 0 // choose correct answer
var points: Int = 0
func handleAnswerChosen(_ sender: UIButton) {
if sender.tag == 0 { // make sure that your number tags are always 0 and 1. 0 for the first answer, 1 for the second.
if questionNumber == 0 && sender.tag == correctAnswer {
print("CORRECT")
// MARK: SHOW CORRECT ANSWER AND SELECTED ANSWER.
print(answers[questionNumber][correctAnswer]) // CORRECT ANSWER
print(answers[questionNumber][sender.tag]) // ANSWER SELECTED
handleNextQuestion()
}
}
}
func handleNextQuestion() {
questionNumber += 1 // Do this as well as update to the next question.
if questionNumber == 0 { // first question
correctAnswer = 1 // second answer
} else if questionNumber == 1 {
correctAnswer = 0 // first answer
}
performSegue(withIdentifier: "showScore", sender: self)
}
// The first [0] gets the two possible answers. The second [1] gets the second answer of the two possible answers chosen.
print(answers[0][1]) // this prints England which is the first question and the second answer of the first question

Update UILabel text in loop

I have a UILabel that needs to change its text to the next item in an array after a certain amount of time. I'm using a for-in loop to achieve this. My issue, though, is that the label won't change until the loop is complete, which defeats the purpose. I have done a lot of searching, and it seems as though using dispatch_async(dispatch_get_main_queue()... is the way to do this. I have tried to use it as other people have, but it doesn't update the label's text. So, if someone could help me use it in my code, or has a solution to update the text in the loop, it would be appreciated.
My code:
#IBAction func startEndTouch(sender: AnyObject) {
var wordsPerMinVal:Double = 60.0/sliderValueBen
for item in textEnterGo {
delay(wordsPerMinVal){
self.yourWordsLabel.text = item
print(item)
}
}
}
Two places I got some info from:
dslkfjsdlfjl & Rob
Vacawama
try it:
var textEnterGo:[String] = ["1","2","3","4","5","6","7","8","9","10"]
var wordsPerMinVal:Double = 0
var timer:NSTimer?
#IBAction func startEndTouch(sender: AnyObject) {
if timer != nil{
index = 0
self.lblText.text = ""
timer!.invalidate()
timer = nil
}
wordsPerMinVal = 1
self.timer = NSTimer.scheduledTimerWithTimeInterval(self.wordsPerMinVal, target: self, selector: #selector(ViewController.setTextForLabel), userInfo: nil, repeats: true)
}
var index:Int = 0
func setTextForLabel(){
self.lblText.text = self.textEnterGo[index]
index += 1
if index > self.textEnterGo.count-1{
index = 0
}
}

Can I use willSet and/or didSet to trigger a transition to a new view controller based on the value of a variable?

I'm new to programming and today I've been researching Property Observers in Swift. This got me wondering if it would be possible to use one to trigger an app to change screens when the value of a variable reaches a certain point.
For example, let's say I have a game that uses the variable 'score' to save and load the user's score. Could I use willSet or didSet to trigger a change in views based on the fact that the score will reach a certain value?
What I was thinking was using something like this:
var maxscore : Int = 0 {
didSet{
if maxscore == 5{
switchScreen()
}}
}
... would call the switchScreen function. Should this work? I haven't been able to find any info on this, so don't know if that's because it's not possible or I just haven't found it.
But I have attempted this with no success. It all compiles and runs, but when the score hits that magic number of 5 nothing happens.
For the sake of completeness, my switchScreen function code is below:
func switchScreen() {
let mainStoryboard = UIStoryboard(name: "Storyboard", bundle: NSBundle.mainBundle())
let vc : UIViewController = mainStoryboard.instantiateViewControllerWithIdentifier("HelpScreenViewController") as UIViewController
self.presentViewController(vc, animated: true, completion: nil)
}
And the code I've used for setting the value to 5 is below:
func CheckAnswer( answerNumber : Int)
{
if(answerNumber == currentCorrectAnswerIndex)
{
// we have the correct answer
labelFeedback.text = "Correct!"
labelFeedback.textColor = UIColor.greenColor()
score = score + 1
labelScore.text = "Score: \(score)"
totalquestionsasked = totalquestionsasked + 1
labelTotalQuestionsAsked.text = "out of \(totalquestionsasked)"
if score == 5 { maxscore = 5}
// later we want to play a "correct" sound effect
PlaySoundCorrect()
}
else
{
// we have the wrong answer
labelFeedback.text = "Wrong!"
labelFeedback.textColor = UIColor.blackColor()
totalquestionsasked = totalquestionsasked + 1
labelTotalQuestionsAsked.text = "out of \(totalquestionsasked)"
if score == 5 { maxscore = 5}
// we want to play a "incorrect" sound effect
PlaySoundWrong()
}
SaveScore()
buttonNext.enabled = true
buttonNext.hidden = false
}
This the method is inside the class than you should be able to do!. Check this answer!
//True model data
var _test : Int = 0 {
//First this
willSet {
println("Old value is \(_test), new value is \(newValue)")
}
//value is set
//Finaly this
didSet {
println("Old value is \(oldValue), new value is \(_test)")
}
}

How to make High Score of game to be saved on Leaderboard, with Swift?

I made a game using SpriteKit and Xcode 7 beta. I tried to put GameCenter and Leaderboard but the problem is that the score in leaderboard won't change it stay all the time 0 (High Score of game won't save in Leaderboard) and I don't know how to fix it. I'm using 3 different files: GameScene.swift, GameViewController.swift, and PointsLabel.swift.
GameScene.swift
func addPointsLabels() {
let pointsLabel = PointsLabel(num: 0)
pointsLabel.position = CGPointMake(30.0, view!.frame.size.height - 40)
pointsLabel.name = "pointsLabel"
addChild(pointsLabel)
//High Score
let highscoreLabel = PointsLabel(num: 0)
highscoreLabel.name = "highscoreLabel"
highscoreLabel.position = CGPointMake(view!.frame.size.width - 35, view!.frame.size.height - 40)
addChild(highscoreLabel)
}
func loadHighscore() {
let defaults = NSUserDefaults.standardUserDefaults()
let highscoreLabel = childNodeWithName("highscoreLabel") as! PointsLabel
highscoreLabel.setTo(defaults.integerForKey("highscore"))
}
GameViewController.swift:
import GameKit
class GameViewController: UIViewController,UIGestureRecognizerDelegate, GKGameCenterControllerDelegate {
var scoreManager = PointsLabel(num: 0)
override func viewDidLoad() {
super.viewDidLoad()
//initiate gamecenter
func authenticateLocalPlayer(){
let localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = {(GameViewController, error) -> Void in
if (GameViewController != nil) {
self.presentViewController(GameViewController!, animated: true, completion: nil)
}
else {
print((GKLocalPlayer.localPlayer().authenticated))
}
}
}
}
#IBAction func leaderboard(sender: UIButton) {
saveHighscore(scoreManager.score)
scoreManager.increment()
showLeader()
}
//send high score to leaderboard
func saveHighscore(score:Int) {
//check if user is signed in
if GKLocalPlayer.localPlayer().authenticated {
let scoreReporter = GKScore(leaderboardIdentifier: "Leaderboard_01")
scoreReporter.value = Int64(score)
let scoreArray: [GKScore] = [scoreReporter]
GKScore.reportScores(scoreArray, withCompletionHandler: {error -> Void in
if error != nil {
print("error")
}
})
}
}
//shows leaderboard screen
func showLeader() {
let vc = self.view?.window?.rootViewController
let gc = GKGameCenterViewController()
gc.gameCenterDelegate = self
vc?.presentViewController(gc, animated: true, completion: nil)
}
}
//hides leaderboard screen
func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController)
{
gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)
}
PointsLabel.swift:
import Foundation
import UIKit
import SpriteKit
class PointsLabel: SKLabelNode {
var score:Int = 0
init(num: Int) {
super.init()
fontColor = UIColor.blackColor()
fontSize = 30.0
score = num
text = "\(num)"
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func increment() {
score++
text = "\(score)"
}
func setTo(num: Int) {
self.score = num
text = "\(self.score)"
}
}
I think the problem is in file GameViewController.swift in code:
#IBAction func leaderboard(sender: UIButton) {
saveHighscore(scoreManager.score)
scoreManager.increment() //<-- Here
showLeader()
}
Maybe I didn't put it in right place
scoreManager.increment()
Well I see a couple things that can contribute to the issue. First is this method...
func loadHighscore() {
let defaults = NSUserDefaults.standardUserDefaults()
let highscoreLabel = childNodeWithName("highscoreLabel") as! PointsLabel
highscoreLabel.setTo(defaults.integerForKey("highscore"))
}
I don't see anywhere you are setting that so pulling it out won't help much. I added the saving to defaults in the saveHighscore: function bellow.
Second is...
saveHighscore(scoreManager.score)
scoreManager.increment() //<-- Here
showLeader()
You should increment before you save your score.
I would try adding these logs to see if this helps...
func saveHighscore(score:Int) {
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setInteger(score, forKey: "highscore")
defaults.synchronize()
//check if user is signed in
if GKLocalPlayer.localPlayer().authenticated {
println("authenticated")
let scoreReporter = GKScore(leaderboardIdentifier: "Leaderboard_01")
println("ScoreReporter: \(scoreReporter)")
scoreReporter.value = Int64(score)
let scoreArray: [GKScore] = [scoreReporter]
GKScore.reportScores(scoreArray, withCompletionHandler: {error -> Void in
if error != nil {
print("error")
}
else{
println("reported correctly")
}
})
}
}
Hopefully what does or don't print out in the logs plus actually saving your defaults will help. Good luck.
Edit
So it appears the root of the problem is that you have scoreManager (which is a PointsLabel) in your VC but you also have one that is in your Scene. The one in your scene you are updating the score and life is good. When you hit the button you are actually getting the score from the label in your VC that isn't getting updated. So what you really need is to get to that label in your scene to pull out the score.
So the easiest way I can think of getting it to work properly with as little changes to your code is this…
Remove this line completely…
var scoreManager = PointsLabel(num: 0)
and change your action to this...
#IBAction func leaderboard(sender: UIButton) {
let skView = self.view as! SKView
let scene = skView.scene
let scoreManager = scene.childNodeWithName("pointsLabel") as! PointsLabel
saveHighscore(scoreManager.score)
showLeader()
}
Hopefully that fixes everything =)
In your game, you need to increment the score AND send the score to Game Center whenever the user scores a point. I'm not sure how your game works, but whenever they should be awarded a point you need to update the score and send that score to game center and update the label. You just can't update the score when the leaderboard button is tapped. I'm not sure if I was clear so ask if you need clarification or help.
It sounds like you need to use core data, it is what you use to save data in an app. I don't know to much about it so I can only point you to a few recourses that might help:
Apple Docs
Core Data relationships (swift)
Swift - Core Data Seeding Class
Check if property is set in Core Data?
How do I access and use an entity using Core Data