1

I know I can delay a part of code in Swift 3 using the followign syntax (How to program a delay in Swift 3):

let when = DispatchTime.now() + 2 // change 2 to desired second delay.
DispatchQueue.main.after(when: when) {
// run your code with delay
}

Or a similar post: How to create dispatch queue in Swift 3

However these are not the delay methods I could use. I need to insert a delay in the loop. For example imagine that there's a label component and a button. When I click the button, I want the label to show the loop variable consecutively:

@IBOutlet weak var showIntegers: UILabel!

@IBAction func countNums(_ sender: AnyObject) {

    for i in 1...5 {
        showIntegers.text = String(i)
        //delay code here, sleep(1) doesn't work
    }
}

I used sleep as the delay but then the app sleeps for 5 seconds and then displays 5. I cannot see 1, 2, 3, 4 and 5 consecutively with 1 seconds of delay.

I also couldn't figure out how I can use the DispatchQueue class inside the loop. Thanks for your help in advance.

Community
  • 1
  • 1
Graeme
  • 41
  • 1
  • 5
  • 1
    I will give you a really important advice - don't insert delays inside loops. Whenever you think you need `sleep(...)`, you should really think how to write the function differently without blocking waits. – Sulthan Aug 29 '16 at 13:25
  • 3
    I don't get the reason you want to pause the loop. But in general you should never block the main thread (IBAction will be triggered within the main thread). There is always a better solution to it. You need to learn to handle asynchronous events and respond to them at the right time. – M_G Aug 29 '16 at 13:27
  • @M_G Thanks for the advice but I didn't ask for an advice, if you know how to insert a delay here I'd be happy if you share... – Graeme Aug 29 '16 at 13:31
  • What do you want to achieve actually? I didn't get your example right. If you want to show the numbers by the label one after the other with a given delay blocking does not help here because the label will show the last value set after the loop returns and the main thread can run as usual. – M_G Aug 29 '16 at 13:39
  • In short: how to achiveve the delay function equivalent of C (http://www.programmingsimplified.com/c/dos.h/delay) in Swift 3. You don't need to give a minus to the question just because you didn't get it. – Graeme Aug 29 '16 at 13:47
  • Sorry, but I didn't give a minus here. You could use pure C as well in Swift. – M_G Aug 29 '16 at 14:00
  • Sorry and thanks for your answers. – Graeme Aug 29 '16 at 14:04
  • Possible duplicate of [How to program a delay in Swift 3](http://stackoverflow.com/questions/38031137/how-to-program-a-delay-in-swift-3) – owlswipe Jan 28 '17 at 18:55

2 Answers2

8

sleep won't work because GUI takes some time to be updated but you immediately sleep your main queue right after you update the label. You application will be unresponsive during the time and the label will just jump from 0 to 5 with some occasional changes.

Use Grand Central Dispatch async_after instead (used to be dispatch_after in Swift 2):

@IBAction func countNums(_ sender: AnyObject) {
    for i in 1...5 {
        // Always update your GUI on the main thread
        DispatchQueue.main.asyncAfter(deadline: .now() + Double(i)) {
            self.showIntegers.text = "\(i)"
        }
    }
}
Code Different
  • 90,614
  • 16
  • 144
  • 163
2

It seems like you're looking for something similar to what I use.

Please note that I don't use the DispatchQueue, but a much simpler Timer:

@IBAction func countNums(_ sender: AnyObject) {
    for i in 1...5 {
        Timer.scheduledTimer(timeInterval: Double(i - 1), // first timer triggers immediately, the others with 1 second delay from the previous timer
                             target: self,
                             selector: #selector(...myFunc(_:)), // please define here your func to call after the timer has triggered (see example below)
                             userInfo: i, // pass here the value that will be read inside the method myFunc
                             repeats: false))
    }
}

// method called when the timer triggers
func myFunc(_ timer: Timer) {
    guard let index = timer.userInfo as? Int else { return }
    showIntegers(index)
}

// finally, actual action
fun showIntegers(_ i: Int) {
    showIntegers.text = String(i)
}

Also note that you can merge the two functions showIntegers and myFunc: I've separated them because, in my opinion, the code looks much clearer this way.

Federico Zanetello
  • 3,321
  • 1
  • 25
  • 22