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
}
}
Related
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()
}
}
I have the following code in Swift trying to get a simple random number generator as a simulator for a game.
var randomNumber = 0
override func viewDidLoad() {
super.viewDidLoad()
randomNumber = Int(arc4random_uniform(74) + 1)
label.text = "\(randomNumber)"
}
I'm new to programming Swift but I know to use timer() and import Foundation to use the timer function but I'm not sure how to implement and make it so a new number appears in the label every 10 seconds. Thanks for any help.
Use a Timer with an interval of 10 seconds to pull a new number from an array of numbers. Remove the number from the array so that you don't call the same number twice. When the stop button is pressed, or you are out of numbers call invalidate on the timer to stop it.
class BingoCaller: UIViewController {
#IBOutlet weak var label: UILabel!
var numbers = Array(1...75)
let letters = ["B", "I", "N", "G", "O"]
var timer: Timer?
override func viewDidLoad() {
timer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in
let index = Int(arc4random_uniform(UInt32(self.numbers.count)))
let number = self.numbers.remove(at: index)
self.label.text = "\(self.letters[(number - 1) / 15])-\(number)"
if self.numbers.isEmpty {
timer.invalidate()
}
}
}
#IBAction func stop(_ button: UIButton) {
timer?.invalidate()
}
}
Suggestions for next steps:
Add the numbers that have been pulled to a second array. Use that array to populate a tableView so that Gran is able to review the numbers when someone calls "Bingo!".
Use AVSpeechSynthesizer to have the iPhone actually speak the numbers.
Add a reset button to start a new game. Initialize the numbers to Array(1...75), the calledNumbers to [] and start again. It's a good idea to move the Timer loop to its own function so that it can be called from a start button.
You can define a helper array, that would let you check if that number was already returned:
var array = [Int]()
var timer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(ViewController.timerFunction), userInfo: nil, repeats: true)
}
func timerFunction(){
var n = arc4random_uniform(75) + 1
while array.contains(Int(n)){
n = arc4random_uniform(75) + 1
}
array.append(Int(n))
label.text = String(n)
if array.count == 75{
timer?.invalidate()
}
}
This way you are sure that the timer is invalidated when all the numbers have already been used and also avoid index-removal errors.
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)")
}
}
I was looking for a way in Swift to loop a changing label that cycles through an array of Strings. Most ways I've tried have stopped all other tasks while the loop was running.
You're view controller could look something like this:
class ViewController: UIViewController {
#IBOutlet weak var cycleLabel: UILabel!
var strings: [String]!
var timer: NSTimer!
var index: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
self.strings = ["Lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit", "Vestibulum", "erat", "lacus", "congue"]
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.cycleLabel.text = self.strings[self.index]
}
#IBAction func beginCyclingTapped(sender: UIButton) {
let interval = 1.0
if self.timer.valid {
self.timer.invalidate()
}
self.timer = NSTimer.scheduledTimerWithTimeInterval(interval, target: self, selector: "updateLabel", userInfo: nil, repeats: true)
}
func updateLabel() {
self.index += 1
self.cycleLabel.text = self.strings[self.index % self.strings.count]
}
}
This code will update the label text to the next string in the strings property every one second. If you'd like a different interval, change the interval constant in the beginCyclingTapped(:) method. The label will start restart from the beginning of the strings array after it reaches the last element in that array. The if statement in beginCyclingTapped(:) ensures that multiple timers are not scheduled to update that label, which would result in the label getting updated more frequently than desired. Also, make sure you hook up the IBOutlet to a UILabel on your Storyboard.
How do I print my variable eg. var Output:String = "Test" so that It prints into the textview one letter at a time? Like it's being typed out.
Thanks in advance!
You can use a timer with a random interval as follow:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var myTypeWriter: UITextField!
let myText = Array("Hello World !!!")
var myCounter = 0
var timer:NSTimer?
func fireTimer(){
timer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: "typeLetter", userInfo: nil, repeats: true)
}
func typeLetter(){
if myCounter < myText.count {
myTypeWriter.text = myTypeWriter.text + String(myText[myCounter])
let randomInterval = Double((arc4random_uniform(8)+1))/20
timer?.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(randomInterval, target: self, selector: "typeLetter", userInfo: nil, repeats: false)
} else {
timer?.invalidate()
}
myCounter++
}
override func viewDidLoad() {
super.viewDidLoad()
fireTimer()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
This should loop trough every character inside the provided string and print it out with delay
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
for char in "test" {
dispatch_async(dispatch_get_main_queue()) { textView.text += String(char) }
usleep(1000)
}
}
I wrote a blog post recently where I created a similar but different effect. In my example I did it using a UILabel and NSAttributedStrings.
In mine I used a fade in animation but you needn't do that if you don't want.
Given that you just want it letter by letter it will make it a lot less complex than mine.
Anyway, it should give you an idea of how I would approach it. Also, unless the use is actually typing into the UITextView then don't use one. Use a UILabel instead.
http://www.oliverfoggin.com/birdman-and-text-animations/