Here is a section of my code where I am trying to delay a function called dropText that drops a name from the top of the screen. I tried using a delay function but it delays then drops them all at once. What am I missing, or is this method just plain wrong? Thanks in advance:
func delay(_ delay:Double, closure:#escaping ()->()) {
DispatchQueue.main.asyncAfter(
deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure)
}
//New group choose method
func groupChoose()
{
//loop through the players
for x in 0...players - 1{
//drop the name in from the top of the screen
delay(2.0) {
self.dropText(playing[x])
}
}
This issue is because you are delaying all of them at once! You should try to assign different delay time to each one:
for x in 1...players {
//drop the name in from the top of the screen
delay(2.0 * x) {
self.dropText(playing[x-1])
}
Refactored
Try to not call array elements by index:
for playing in playing.enumerated() {
// drop the name in from the top of the screen
let player = playing.offset + 1
delay(2.0 * player) {
self.dropText(playing.element)
}
Look at the loop. You are calling asyncAfter almost immediately one after another. So the text is dropped after the delay almost immediately one after another, too.
I recommend to use a Timer
func delay(_ delay: Double, numberOfIterations: Int, closure:#escaping (Int) -> Void) {
var counter = 0
Timer.scheduledTimer(withTimeInterval: delay, repeats: true) { timer in
DispatchQueue.main.async { closure(counter-1) }
counter += 1
if counter == numberOfIterations { timer.invalidate() }
}
}
and
func groupChoose()
{
delay(2.0, numberOfIterations: players) { counter in
self.dropText(playing[counter])
}
}
Related
I want to show multiple quotation in one label. when one quotes fade out then another quotes fade in. I am doing this -:
for i in self.splashModel?.quotations ?? [] {
self.quoteLabel.alpha = 0
print("CheckQuotes\(i)")
Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true, block: { timer in
self.quoteLabel.text = i
self.quoteLabel.fadeIn(completion: {
(finished: Bool) -> Void in
self.quoteLabel.fadeOut()
})
})
}
this only show last item of array
Try this -:
self.splashModel?.quotations.enumerated().forEach { (index,item) in
DispatchQueue.main.asyncAfter(deadline: .now() + Double( index * 2) ) {
self.quoteLabel.alpha = 0
self.quoteLabel.text = item
self.quoteLabel.fadeIn(completion: {(finished: Bool) -> Void in
self.quoteLabel.fadeOut()
})
}
}
I am currently working on a sorting visualizer, but I need my for loop to "run slower", since I would like to visualize slowly how an algorithm works, for example, bubble sort.
This is my code
func bubbleSort(array: inout [Rectangle], view: UIView) {
for i in 1 ... array.count {
for j in 0 ..< array.count - i {
changeRectColor(rect: array[j])
changeRectColor(rect: array[j+1])
Thread.sleep(forTimeInterval: 1)
if (array[j].height > array[j+1].height){
sortRectColor(rect: array[j])
sortRectColor(rect: array[j+1])
Thread.sleep(forTimeInterval: 1)
rectGenerator.removeRectangleView(view: view, tag: array[j].rectView.tag)
rectGenerator.removeRectangleView(view: view, tag: array[j+1].rectView.tag)
let temp = array[j].xPos
array[j].xPos = array[j+1].xPos
array[j+1].xPos = temp
rectGenerator.regenerateRectangleView(rect: &array[j], view: view)
rectGenerator.regenerateRectangleView(rect: &array[j+1], view: view)
array.swapAt(j, j+1)
}
returnRectColor(rect: array[j])
returnRectColor(rect: array[j+1])
Thread.sleep(forTimeInterval: 1)
}
}
}
But if I do this, sleep() freezes my UI, and it does not show the process.
How can I do something similar but without freezing the UI?
You can use a timer e.x for 1 second delay
var counter = 0
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
guard counter < array.count else { timer.invalidate() ; return }
// do job
counter += 1
}
I have ViewController with stars rating that looks like this (except that there are 10 stars)
When user opens ViewController for some object that have no rating I want to point user's attention to this stars with very simple way: animate stars highlighting (you could see such behaviour on some ads in real world when each letter is highlighted one after another).
One star highlighted
Two stars highlighted
Three stars highlighted
......
Turn off all of them
So this is the way how I am doing it
func delayWithSeconds(_ seconds: Double, completion: #escaping () -> ()) {
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
completion()
}
}
func ratingStarsAnimation() {
for i in 1...11 {
var timer : Double = 0.6 + Double(i)*0.12
delayWithSeconds(timer) {
ratingStars.rating = (i < 10) ? Double(i) : 0
}
}
}
What is going on here? I have function called delayWithSeconds that delays action and I use this function to delay each star highlighting. And 0.6 is initial delay before animation begins. After all stars are highlighted - last step is to turn off highlighting of all stars.
This code works but I can't say that it is smooth.
My questions are:
How can I change 0.6 + Double(i)*0.12 to get smooth animation feel?
I think that my solution with delays is not good - how can I solve smooth stars highlighting task better?
Have a look at the CADisplaylink class. Its a specialized timer that is linked to the refresh rate of the screen, on iOS this is 60fps.
It's the backbone of many 3rd party animation libraries.
Usage example:
var displayLink: CADisplayLink?
let start: Double = 0
let end: Double = 10
let duration: CFTimeInterval = 5 // seconds
var startTime: CFTimeInterval = 0
let ratingStars = RatingView()
func create() {
displayLink = CADisplayLink(target: self, selector: #selector(tick))
displayLink?.add(to: .main, forMode: .defaultRunLoopMode)
}
func tick() {
guard let link = displayLink else {
cleanup()
return
}
if startTime == 0 { // first tick
startTime = link.timestamp
return
}
let maxTime = startTime + duration
let currentTime = link.timestamp
guard currentTime < maxTime else {
finish()
return
}
// Add math here to ease the animation
let progress = (currentTime - startTime) / duration
let progressInterval = (end - start) * Double(progress)
// get value =~ 0...10
let normalizedProgress = start + progressInterval
ratingStars.rating = normalizedProgress
}
func finish() {
ratingStars.rating = 0
cleanup()
}
func cleanup() {
displayLink?.remove(from: .main, forMode: .defaultRunLoopMode)
displayLink = nil
startTime = 0
}
As a start this will allow your animation to be smoother. You will still need to add some trigonometry if you want to add easing but that shouldn't be too difficult.
CADisplaylink:
https://developer.apple.com/reference/quartzcore/cadisplaylink
Easing curves: http://gizma.com/easing/
I am making a game similar to pitfall in Swift and I am trying to make a boolean that shows whether the player is jumping or not. I want the boolean to become false after 3 seconds so that the player moves down again. I have tried using a delay function but it didn't work.
Thanks in advance.
Try this:
let delay = 3 // seconds to wait before firing
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(delay)) {
// set your var here
}
Replace DispatchQueue.main with whichever queue you're using.
The follow code snippet describes Player objects that have a isJumping property. When it's set to true (using didSet), it automatically starts a timer that after 3 seconds resets isJumping to false.
Please not that the snippet makes use of a NSTimer extensions for comfortably starting and handling the timer. Credits to https://gist.github.com/natecook1000/b0285b518576b22c4dc8
class Player {
private var resetJumpingTimer: NSTimer?
var isJumping: Bool = false {
didSet {
resetJumpingTimer?.invalidate() // Stops the timer in case it was already started
if isJumping {
self.resetJumpingTimer = NSTimer.schedule(3.0) { [weak self] _ in
self?.isJumping = false
}
}
}
}
}
extension NSTimer {
class func schedule(delay delay: NSTimeInterval, handler: NSTimer! -> Void) -> NSTimer {
let fireDate = delay + CFAbsoluteTimeGetCurrent()
let timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, fireDate, 0, 0, 0, handler)
CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopCommonModes)
return timer
}
}
After the player jumps, you create an NSTimer.
Declare global variables let timer = NSTimer() and var seconds = 3
Then after the player jumps you set the timer:
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(YOUR_CLASS_NAME.updateTimer()), userInfo: nil, repeats: true)
Then the method:
func updateTimer() {
seconds -= 1
if seconds == 0 {
// Do stuff
timer.invalidate() // Stop the timer
seconds == 3 // Reset # of seconds
}
Give it a try.
func update() {
seconds -= 1
if seconds <= 0 {
score = 5
}
}
You may want to make it <= if you don't invalidate the timer. That way it stays the variable, etc.
I am having a bit of trouble using / implementing NSTimer into code. So far, i have this:
//Delay function from http://stackoverflow.com/questions/24034544/dispatch-after-gcd-in-swift/24318861#24318861
func delay(delay:Double, closure:()->()) {
dispatch_after(
//adds block for execution at a specific time
dispatch_time(
//creates a dispatch time relative to default clock
DISPATCH_TIME_NOW,
//Indicates that something needs to happen imediately
Int64(delay * Double(NSEC_PER_SEC))
//a 64bit decleration that holds the delay time times by the amount of nanoseconds in one seconds, inorder to turn the 'delay' input into seconds format
),
dispatch_get_main_queue(), closure)
}
#IBAction func computerTurn(){
if(isFirstLevel){levelLabel.text = ("Level 1"); isFirstLevel = false}
else{ level++ }
var gameOrderCopy = gameOrder
var randomNumber = Int(arc4random_uniform(4))
gameOrder.append(randomNumber)
var i = 0
var delayTime = Double(1)
println(Double(NSEC_PER_SEC))
for number in self.gameOrder{
if number == 0{
delay(delayTime++){self.greenButton.highlighted = true}
self.delay(delayTime++){
self.greenButton.highlighted = false
}
}
else if number == 1{
delay(delayTime++){self.redButton.highlighted = true}
self.delay(delayTime++){
self.redButton.highlighted = false
}
}
else if number == 2{
delay(delayTime++){self.yellowButton.highlighted = true}
self.delay(delayTime++){
self.yellowButton.highlighted = false
}
}
else if number == 3{
delay(delayTime++){self.blueButton.highlighted = true}
self.delay(delayTime++){
self.blueButton.highlighted = false
}
}
println(delayTime)
}
}
What i need to do, is replace the timer function, or get rid of it, and do the same as whats happening here, but using NSTimer.
Thanks
NSTimer can be a little clunky in this context because it requires using either target-action or NSInvocation. However, NSTimer is toll-free bridged with CFRunLoopTimer, which you can call with a block:
func delay(delay:Double, closure:()->()) {
let fireDate = delay + CFAbsoluteTimeGetCurrent()
let timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, fireDate, 0, 0, 0) { _ in
closure()
}
CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopCommonModes)
}
I would suggest:
Setting up some class property to maintain the "current" numeric index (which you'd start at 0);
Start your repeating timer;
The timer's handler would use the index to figure out which button to change and then do one "turn highlight on" and then do a dispatch_after for the "turn highlight back off";
Then, this handler would look at the index and determine if you're at the end of the list or not. If you are, then cancel the timer and you're done. If you're not at the end of the list, then increment the "current index" and wait for the next scheduled timer fire off.