Having trouble with a while loop in Swift 5 - swift

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
}

Related

Step Counter Sprite Kit

I am trying to implement a step counter in my sprite Kit game.
And it should work like this:
The counter adds 1 to a value each second.
Every fifth second the duration (in this case 1) gets divided by 1.1
But if I create a func that returns the new duration, the repeat forever SKAction only uses this value for one time and then the duration never changes again.
you should make an action that calls itself, rather than using SKAction.repeatForever(...). you can recalculate values that way. not sure i entirely understand your use case, but here is an example that fires an event after a duration, and modifies that duration every fifth cycle.
var isLoopEnabled:Bool = true
var counter:Int = 0
var duration:TimeInterval = 1.0
func updateDuration() {
duration /= 1.1
}
/*
creates an event loop. the action waits, fires, then calls itself again (before exiting)
turn the loop off using the isLoopEnabled flag
*/
func loop() {
let wait = SKAction.wait(forDuration: duration)
let run = SKAction.run {
self.counter += 1 //increment counter
//update duration every fifth count
if self.counter % 5 == 0 {
self.updateDuration()
}
}
let end = SKAction.run{
print("\(self.counter) -- duration: \(self.duration)")
guard self.isLoopEnabled else { return } //flag allows you to exit loop
self.loop() //repeats by calling itself
}
let sequence = SKAction.sequence([ wait, run, end ])
self.run(sequence, withKey:"loop action")
}
override func didMove(to view: SKView) {
loop()
}

DispatchSourceTimer schedule (GCD swift macOS) stops after 40 seconds when the app is hidden [duplicate]

I am writing a Mac OS app in Swift and want to repeat a task every 0.5s (more or less, high precision is not required). This app should run in the background while I use other applications.
I'm currently using a Timer:
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true)
It starts fine with updates roughly every 0.5s but after some time in the background, the Timer slows down considerably to intervals of roughly 1s or 2s (it's very close to these values to it seems that the timer skips ticks or has a slowdown of a factor 2 or 4).
I suspect it's because the app is given a low priority after a few seconds in the background. Is there a way to avoid this? It can be either in the app settings in XCode by asking to stay active all the time, or possibly from the system when the app is run (or even but doing things differently without Timer but I'd rather keep it simple if possible).
Here is a minimal working example: the app only has a ViewController with this code
import Cocoa
class ViewController: NSViewController {
var lastUpdate = Date().timeIntervalSince1970
override func viewDidLoad() {
super.viewDidLoad()
let timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) {
timer in
let now = Date().timeIntervalSince1970
print(now - self.lastUpdate)
self.lastUpdate = now
}
RunLoop.current.add(timer, forMode: .common)
}
}
Output at start is
0.5277011394500732
0.5008649826049805
0.5000109672546387
0.49898695945739746
0.5005381107330322
0.5005340576171875
0.5000457763671875
...
But after a few seconds in the background it becomes
0.49993896484375
0.49997520446777344
0.5000619888305664
1.5194149017333984
1.0009620189666748
0.9984869956970215
2.0002501010894775
2.001321792602539
1.9989290237426758
...
If I bring the app back to the foreground, the timer goes back to 0.5s increments.
Note: I'm running Mac OSX 10.15.5 (Catalina) on an iMac
This is because of the App Nap. You can disable App Nap but it is not recommended.
var activity: NSObjectProtocol?
activity = ProcessInfo().beginActivity(options: .userInitiatedAllowingIdleSystemSleep, reason: "Timer delay")
The default tolerance value of timer is zero but The system reserves the right to apply a small amount of tolerance to certain timers regardless of the value of tolerance property.
As I stated in my comment below, if you want granularities lower than 1.0 s, you should not use Timer objects, but rather GCD. I wrote a class MilliTimer you can use where you have improved granularity down to a few milliseconds. Please try this in a Playground and then in your app. In this example, I set the granularity of the timer based on GCD to 50 milliseconds. To adjust the delay pass the delay you want in milliseconds in the respective parameter of the initializer. In your case, you might be interested in 500 ms = 0.5 s.
import Cocoa
public class MilliTimer
{
static let µseconds = 1000000.0
static var lastUpdate = DispatchTime.now()
var delay = 0
var doStop = false
var runs = 0
let maxRuns = 50
private class func timer(_ milliseconds:Int, closure:#escaping ()->())
{
let when = DispatchTime.now() + DispatchTimeInterval.milliseconds(milliseconds)
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
init(delay:Int) {
self.delay = delay
}
func delta() -> Double {
let now = DispatchTime.now()
let nowInMilliseconds = Double(now.uptimeNanoseconds) / MilliTimer.µseconds
let lastUpdateInMilliseconds = Double(MilliTimer.lastUpdate.uptimeNanoseconds) / MilliTimer.µseconds
let delta = nowInMilliseconds - lastUpdateInMilliseconds
MilliTimer.lastUpdate = now
return delta
}
func scheduleTimer()
{
MilliTimer.timer(delay) {
print(self.delta())
if self.doStop == false {
self.scheduleTimer()
self.runs += 1
if self.runs > self.maxRuns {
self.stop()
}
}
}
}
func stop() {
doStop = true
}
}
MilliTimer(delay: 50).scheduleTimer()
CFRunLoopRun()

How to prevent Timer slowing down in background

I am writing a Mac OS app in Swift and want to repeat a task every 0.5s (more or less, high precision is not required). This app should run in the background while I use other applications.
I'm currently using a Timer:
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true)
It starts fine with updates roughly every 0.5s but after some time in the background, the Timer slows down considerably to intervals of roughly 1s or 2s (it's very close to these values to it seems that the timer skips ticks or has a slowdown of a factor 2 or 4).
I suspect it's because the app is given a low priority after a few seconds in the background. Is there a way to avoid this? It can be either in the app settings in XCode by asking to stay active all the time, or possibly from the system when the app is run (or even but doing things differently without Timer but I'd rather keep it simple if possible).
Here is a minimal working example: the app only has a ViewController with this code
import Cocoa
class ViewController: NSViewController {
var lastUpdate = Date().timeIntervalSince1970
override func viewDidLoad() {
super.viewDidLoad()
let timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) {
timer in
let now = Date().timeIntervalSince1970
print(now - self.lastUpdate)
self.lastUpdate = now
}
RunLoop.current.add(timer, forMode: .common)
}
}
Output at start is
0.5277011394500732
0.5008649826049805
0.5000109672546387
0.49898695945739746
0.5005381107330322
0.5005340576171875
0.5000457763671875
...
But after a few seconds in the background it becomes
0.49993896484375
0.49997520446777344
0.5000619888305664
1.5194149017333984
1.0009620189666748
0.9984869956970215
2.0002501010894775
2.001321792602539
1.9989290237426758
...
If I bring the app back to the foreground, the timer goes back to 0.5s increments.
Note: I'm running Mac OSX 10.15.5 (Catalina) on an iMac
This is because of the App Nap. You can disable App Nap but it is not recommended.
var activity: NSObjectProtocol?
activity = ProcessInfo().beginActivity(options: .userInitiatedAllowingIdleSystemSleep, reason: "Timer delay")
The default tolerance value of timer is zero but The system reserves the right to apply a small amount of tolerance to certain timers regardless of the value of tolerance property.
As I stated in my comment below, if you want granularities lower than 1.0 s, you should not use Timer objects, but rather GCD. I wrote a class MilliTimer you can use where you have improved granularity down to a few milliseconds. Please try this in a Playground and then in your app. In this example, I set the granularity of the timer based on GCD to 50 milliseconds. To adjust the delay pass the delay you want in milliseconds in the respective parameter of the initializer. In your case, you might be interested in 500 ms = 0.5 s.
import Cocoa
public class MilliTimer
{
static let µseconds = 1000000.0
static var lastUpdate = DispatchTime.now()
var delay = 0
var doStop = false
var runs = 0
let maxRuns = 50
private class func timer(_ milliseconds:Int, closure:#escaping ()->())
{
let when = DispatchTime.now() + DispatchTimeInterval.milliseconds(milliseconds)
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
init(delay:Int) {
self.delay = delay
}
func delta() -> Double {
let now = DispatchTime.now()
let nowInMilliseconds = Double(now.uptimeNanoseconds) / MilliTimer.µseconds
let lastUpdateInMilliseconds = Double(MilliTimer.lastUpdate.uptimeNanoseconds) / MilliTimer.µseconds
let delta = nowInMilliseconds - lastUpdateInMilliseconds
MilliTimer.lastUpdate = now
return delta
}
func scheduleTimer()
{
MilliTimer.timer(delay) {
print(self.delta())
if self.doStop == false {
self.scheduleTimer()
self.runs += 1
if self.runs > self.maxRuns {
self.stop()
}
}
}
}
func stop() {
doStop = true
}
}
MilliTimer(delay: 50).scheduleTimer()
CFRunLoopRun()

Timer initialised in 'for in' loop firing twice for each loop

I'm building an application that displays a custom driving route on a map using MapKit. It takes an array of coordinates and goes through a for loop, in each loop the corresponding coordinates (i.e [0] and [1] or [7] and [8]) are assembled into an individual request and then drawn on the map.
In order to bypass MapKit's throttling error, I have a timer that is set so that each request is spaced out 1 second apart.
My issue is that the timer is firing twice for each individual request, which is resulting in double the number of necessary requests being made.
I'm using Xcode 10 and Swift 4, this is the function where I believe the issue is occurring.
func requestDirections(arrays coordinates: [CLLocationCoordinate2D]) {
var dest = 1
let coordinatesArray = coordinates
let end = coordinatesArray.count - 2
var timerDelay:Double = 1
for origin in 0...end {
if dest <= coordinatesArray.count {
let startCoord = MKPlacemark(coordinate: coordinatesArray[origin])
let destCoord = MKPlacemark(coordinate: coordinatesArray[dest])
let request = MKDirections.Request()
request.source = MKMapItem(placemark: startCoord)
request.destination = MKMapItem(placemark: destCoord)
request.transportType = .automobile
request.requestsAlternateRoutes = false
print("Starting timer for \(dest) at \(timerDelay) seconds")
Timer.scheduledTimer(withTimeInterval: timerDelay, repeats: false) { timer in
self.createIndividualDirectionsRequest(request)
print("Timer fired")
}
dest = dest + 1
timerDelay = timerDelay + 1
}
}
}
I'm expecting the timer to fire once for each loop, if this is happening the expected console output would be
"Starting timer for 'dest' at 'timerDelay' seconds" printed 18 times (or whatever the size of the array is)
"Timer fired" being printed 18 times as well
While "Starting timer for 'dest' at 'timerDelay' seconds" is in fact being printed the correct number of times, "Timer fired" is being printed twice as often as it should.
Thank you very much for your help and your patience, I am quite new to programming and am struggling to wrap my head around this issue.
Actually I do not know why "Timer fired" is printed twice for each individual request :).
But if you are using Timer.scheduledTimer just to delay the execution of the block, you can use DispatchQueue.main.asyncAfter instead of Timer
DispatchQueue.main.asyncAfter(deadline: .now() + timerDelay) {
self.createIndividualDirectionsRequest(request)
print("Timer fired")
}
You can also use DispatchQueue.global.asyncAfter but I think you want to execute the block in main ui thread

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.