Calling function inside closure, Swift - swift

private func showOrder() {
disableButtons()
var count = 0
_ = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { time in
self.popColor(color: self.colorOrder[count])
count += 1
if count == self.colorOrder.count {
time.invalidate()
self.depopAllButtons()
self.enableButtons()
}
}
}
In this function I am looping through an Array of colors. The popColor() method is used to add shadows and such to a button the screen.
The issue that I am having is that the popColor() function seems to execute after the closure has executed, so it is always one behind where it should be. The error this causes is on the last iteration of the loop the last color doesn't execute its popColor().
Is it possible to call a function inside of a closure as I am doing here with popColor()?

I have write your code with small changes:
var count = 0
_ = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { time in
debugPrint("set color")
count += 1
if count == 3 {
time.invalidate()
debugPrint("disable set color")
}
}
And the console printed:
"set color"
"set color"
"set color"
"disable set color"
Your timer is working correctly. Look for a problem elsewhere.

Related

Escaping closure captures 'inout' parameter

I have a function like below, but when I perform it , it's show "Escaping closure captures 'inout' parameter 'cani'"
and i missing anything?
func duration(out:Int,h2:Int,rep:Int,cani:inout Bool){
var io = 0
var b = 0
cani = false
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { // "Escaping closure captures 'inout' parameter 'cani'" //
timer in
io += 1
b += 1
if b <= out{
text = "come in"
}else if b <= out + h2 && b > out{
text = "Hold on"
}
if io == (out + h2 ) * rep{
textcange = "End"
timer.invalidate()
cani = true
}
}
}
it's show "Escaping closure captures 'inout' parameter 'cani'"
When you enter your function, the cani value is duplicated,
when you exit the function, the duplicated value, potentially modified, is written back. That's what inout does.
Your function is asynchronous, so it exits immediately and cani is not modified.
When your timer closure is called, first you don't even know if the caller is still living, but you can be sure the cani copy that was passed does not exist anymore since the function has already exit a while ago.
The compiler detects this non-sense and tells you to do differently.
You may consider adding a cani property in the object that calls the timer, and changes its value in the timer closure or better, since it is asynchronous, call a closure do do what you want to do when the timer issues.
func duration(out:Int,h2:Int,rep:Int,cani:inout Bool){
var io = 0
var b = 0
var test: String
Timer.scheduledTimer(withTimeInterval: 1.0,
repeats: true,
completion: (String)->Void) {
timer in
io += 1
b += 1
if b <= out {
text = "Come in"
} else if b <= out + h2 {
text = "Hold on"
}
if io == (out + h2 ) * rep {
text = "End"
timer.invalidate()
completion()
}
}
}
It's better to put simple and understandable code in StackOverflow questions. And if possible, buildable. ;)

Having trouble with a while loop in Swift 5

Essentially, I'm making an app about a virtual dog to help people take care of their dogs. One of the screens gives you fifteen seconds to pet the dog five times. Whenever I try to load the screen, the app freezes. The code is inside of viewDidLoad()
while timesPetted < 5 {
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1), execute: {
timer += 1
if timer == 15 {
self.failureLabel.isHidden = false
self.reset.isHidden = false
self.timesPetted = 0
}
})
}
When I delete the while loop, the screen loads normally and runs perfectly, but (obviously) there isn't a time limit. If I move the while loop out of viewDidLoad(), I get an error saying that Xcode "Expected declaration".
Either use a timer that is set to expire in 15 seconds
let timer = Timer.scheduledTimer(withTimeInterval: 15.0, repeats: false) { timer in
self.failureLabel.isHidden = false
self.reset.isHidden = false
self.timesPetted = 0
}
Or if you want to use DispatchQueue then use it only once
DispatchQueue.main.asyncAfter(deadline: .now() + 15) {
self.failureLabel.isHidden = false
self.reset.isHidden = false
self.timesPetted = 0
}
it appears the while loop is running infinitely due to the value of timesPetted. As the value of timesPetted is not changing at all once it enters the while loop.
To solve your issue you can make changes to your code as below :-
You must be updating your timesPetted value some where in the code.
lets say timesPetted is changed in function called "Petting", so when this function is called have check, which limits the user to pet till 5 times only and another check for 15 seconds. As shown below.
func petting() {
if reset.isHidden && timesPetted <= 5{ // here the reset.isHidden will become false in DispatchQueue (present in viewDidLoad) once the 15 seconds have been passed.
timesPetted += 1
}
}
And also make sure to add this line of code in your viewDidLoad.
DispatchQueue.main.asyncAfter(deadline: .now() + 15) {
self.failureLabel.isHidden = false
self.reset.isHidden = false
self.timesPetted = 0
}

Using dispatchQueue in loops

I am attempting to loop something with a delay (just as a proof of concept) this is for something else. But to simplify it, here is an example.
so in this example, I have a string with "text" and I want to loop the addition of another string lets say 10 times. The only thing is that I want there to be a delay in each iteration. here is my code thus far.
// global variable
var myString = "text"
// an action inside a button
let delayInSeconds = 1.0
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayInSeconds) {
for _ in 1...10 {
self.myString += "another text"
}
}
labelOne.text = myString
}
I should add that the result is that all 10 "another text" are added immediately without any delay.
thank you
In your example, you append your string ten times in the same work unit. Try dispatching work once per loop:
for delay in 1...10 {
let delayInSeconds = Double(delay)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayInSeconds) {
self.myString += "another text"
labelOne.text = myString
}
}
This loop won't work well for arbitrarily large values, though. It also doesn't provide the kind of precise timing we might want for user interface changes. For that, we can use Timer. Here’s the same problem reworked with Timer:
// Add to the class body…
var i = 1
// Lazy initialization so the timer isn’t initialized until we call it
lazy var timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) {timer in
guard self.i <= 20 else {
timer.invalidate()
return
}
self.label.text?.append(" another text")
self.i += 1
}
// Add to the button action…
timer.fire()
If I understand correctly, you want to show the text one by one with delay for each “text”.
You can use recursion here.
1.Move the dispatch code into a method.
2. Remove for loop.
3. Do your necessary actions inside dispatch it.
4.call that same method again inside dispatch.
Do remember to use a counter variable that increments each time method is called. Use it to break the recursion.

Index Out of Range Error in Swift

var numberOfPeople = 1 //get from host
var numberAtCounter = 0
func showNames() {
if peopleInMatch[numberAtCounter] == yourPeerID { //change to peopleinmatcheveryone
if numberOfPeople == 0 {
print("hoho")
personName.isHidden = true
connect.isHidden = false
connect.setTitle("PRESS READY", for: UIControlState.normal)
//change label to ready
} else {
numberAtCounter += 1
numberOfPeople -= 1 // buggy?
print("\(numberAtCounter)")
showNames()
}
} else {
personName.text = "TAKE PHOTO OF \(peopleInMatch[numberAtCounter])'s COLOR"
numberAtCounter += 1
if numberOfPeople <= 0 {
personName.isHidden = true
connect.isHidden = false
connect.setTitle("PRESS READY", for: UIControlState.normal)
//change label to ready
}
numberOfPeople -= 1 //buggy maybe fixed
}
}
I'm getting a Thread 1: EXC_BREAKPOINT error on the if peopleInMatch[numberAtCounter] == yourPeerID line. I'm not entirely sure what out of index means or what is potentially wrong. The code will be run through once then the function calls itself and on the second time through it breaks down on the line I mentioned above. I've checked all the variables and none of them are nill. Any ideas?
Here I made a short example for you to understand where your problem actually is.
ScreenShot 1:
Everything works fine when function is called for first time value at numberAtCounter is printed.
Here in first call either you are decrementing the value as numberAtCounter-=1 which take value from 0 to -1. Thus in second call when the function is called in line at:
if peopleInMatch[numberAtCounter] == yourPeerID // here you need to check value of `numberAtCounter`
make sure it's not getting a negative value or value more than your peopleInMatch array.
Thus if it becomes negative or more than count, result you will get as follows:

one score at a time, update method

I'm creating a simple game with SpriteKit (mostly for learning), and I got a question about score adding. some background: I'm checking if a sprite (SKShapeNode) contains another one, if true, I'm checking their color, if it is the same color, the player should get 1 score. I wrote this function:
func onMatch(){
for ring in mColorRings {
if(mPlayer.contains(ring.position)){
if mPlayer.fillColor.isEqual(ring.fillColor) {
score += 1
mScoreLbl.text = "\(score)"
}
}
}
}
which works, the problem is, I'm calling this function inside the update method. as the update method runs a lot, it calls my function a lot of time and as long as mPlayer contains ring it is adding 1 score to the player. How can I avoid that ?
This depends on your game mechanics. If the ring is supposed to give you a score one time then disappear you can safely remove it within that if test. If you want the ring to stay put and maybe be reused later you can add a boolean to the ring class called something like "scoreGiven" and redo your if test to something like this:
func onMatch(){
for ring in mColorRings {
if !ring.scoreGiven{
if(mPlayer.contains(ring.position)){
if mPlayer.fillColor.isEqual(ring.fillColor) {
score += 1
mScoreLbl.text = "\(score)"
ring.scoreGiven = true
}
}
}else if(!mPlayer.contains(ring.position)){
ring.scoreGiven = false
}
}
This is just an example, but note the "not"s in the updated if statements
Ok, so this is what i suggest:
var playerPassed = false
func onMatch(){
for ring in mColorRings {
if(mPlayer.contains(ring.position)) {
if ((mPlayer.fillColor.isEqual(ring.fillColor)) && playerPassed == false) {
score += 1
mScoreLbl.text = "\(score)"
playerPassed = true
}
}
}
}
You're creating a bool and you check if that is false(default) and if so, the block executes and when it does, the bool is set to true and the condition will be false and no longer return true.