Timer Loops - not NSTimer - Swift - ios

There are lots of questions about using NSTimer for timer functions but I am instead using a CocoaPod "Countdown Label" https://github.com/suzuki-0000/CountdownLabel
My question/problem specifically is how to trigger an action/notification once it is complete I don't believe this to be the same as the many "NSTimer" questions/tutorials but please correct me if I'm wrong!
I've read the Timer documentation here https://developer.apple.com/documentation/foundation/timer but, again, don't see how it would apply to a 'custom timer library' like this Cocoapod.
I am currently trying this....
func testTimer() {
timerLabel.countdownDelegate = self
timerLabel.start()
if self.timerLabel.isFinished {
print("timer it's finished")
} else {self.timerLabel.start()
}
}
testTimer() is then called when an IBAction is pressed to star the timer.
My logic/thinking was that this function starts the timer, then checks if it finished. If it isn't finished it starts/continues the timer (timerLabel.start) and checks again basically in a loop until it is finished and then prints "timer is finished" but this isn't working and I'm not sure why? (but builds fine)
I know I could just scrap the pod and follow a Timer() tutorial but I'm trying to learn/understand how this sort of 'internal notification' for non Apple libraries would work generally at the same time as solving this specific problem. I hope this all makes sense.
NB. Some of the questions/answers I've read through that I don't believe are what I need are How to check if a NSTimer is running or not in Swift?, Check if Timer is running, Perform segue when timer is finished but they all use the Apple "Timer()".
NB. I am setting up the timer is ViewDidLoad with data from a Segue like this :
let timerLabelTime = Int(selectedWorkoutTime)
timerLabel.setCountDownTime(minutes: Double((timerLabelTime)!*60))
timerLabel.countdownDelegate = self
timerLabel.pause()
which works fine

Related

Clean up SimplePing code with RunLoop.current.run

I found the SimplePing library from Apple and want to use it in a SwiftUI Project.
To use the library I code online which works fine. The start function is as follows:
public func start(hostName: String) {
let pinger = SimplePing(hostName: "192.168.178.20")
pinger.delegate = self
pinger.start()
var count = 5
repeat {
if (self.canStartPinging) {
pinger.send(with: nil)
count-=1
if count == 0{
self.canStartPinging = false
break
}
}
RunLoop.current.run(mode: RunLoop.Mode.default, before: NSDate.distantFuture)
} while(true)
I don't really understand why I need the RunLoop.current.run(mode: RunLoop.Mode.default, before: NSDate.distantFuture) line. When I remove it the delegates of SimplePing doesn't get called.
How can I simplify this code and use it without blocking the Main thread?
The run(mode:before:) is there to allow the run loop to process events while this repeat-while loop spins. It’s a way to make a blocking loop allow things to occur on the run loop.
You haven’t shared the code that is setting canStartPinging, but I’m guessing that, at the very least, SimplePingDelegate method didStartWithAddress sets it. So, if you’re spinning on the main thread without calling that run(mode:before:), the SimplePing delegate method probably never gets a chance to be called. By adding that run call, at least the delegate method can run.
Your suspicion about this this whole pattern of spinning and calling run(mode:before:) is warranted. It’s horribly inefficient pattern. It should be eliminated.
If this were a standard Swift project, I’d suggest just using the delegate-protocol pattern and you’d be done. Since this is Swift UI, I’d suggest refactoring this to be a Combine Publisher, which you can then integrate into your SwiftUI project.

Timer is not starting after explicitly telling it to [duplicate]

This question already has answers here:
Why would a `scheduledTimer` fire properly when setup outside a block, but not within a block?
(3 answers)
Swift Timer.scheduledTimer() doesn't work
(2 answers)
Closed 3 years ago.
This is slowly beginning to drive me insane because it doesn't make sense...
I'm using Swift and can't seem to get this timer to start no matter what I do.
I have an NSObject that's handling all my time-related things in my app, and in that NSObject I have a function that initializes a timer. It looks like this:
class Time: NSObject {
static let sharedInstance = Time()
private override init(){}
var roundTimer = Timer()
//This is called from my splash screen while the app is loading
func initializeTimers(){
//Initialize the round countdown timer
roundTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(updateRoundTimerCountdown)), userInfo: nil, repeats: true)
print("The round timer should have started")
}
#objc func updateRoundTimerCountdown(){print("Round Timer ran"); Time.roundTimerIsAt -= 1; print("Round Timer: \(Time.roundTimerIsAt)")}
}
From my splash screen where the app loads all the user data, I call Time.sharedInstance.initializeTimers() in order to start it. I get to the point of it printing "The round timer should have started" to the debugger (which happens after the line that should start the timer) but the selector isn't hit at all. The "Round Timer ran" isn't printed to the debugger nor does the timer appear to have started, so what am I doing wrong?
I appreciate any help, even if the answer is glaringly obvious lol :P I've spent too much time on this!
I actually finally got it working! I'm sorry...there would have been too much code if I posted my entire splash screen view controller and Time NSData, but it appears the issue was elsewhere. But maybe someone else may run into this so this may help.
On my splash screen, I perform 15 'app-loading steps' that include several steps that gather information from servers and such... those steps I progress through by using completion handlers to ensure that the data is actually collected.
It's after collecting some of this data that I call the Time.sharedInstance.initializeTimers() function. My call to the servers runs on a different thread other than the main, so apparently by calling this function after the completion handler runs, it's still on the other thread and these timers can't start on anything other than the main thread!?
So all I did was this:
DispatchQueue.main.async {Time.sharedInstance.initializeTimers()}
And it works now... (facepalm). Doesn't make a whole lot of sense to me but... it works :P

Multithreading - DispatchQueue asyncAfter delay in Swift

I am using the following code to invoke a function periodically every second. The problem is delay is actually 1.1 seconds and gets drifted more and more eventually as can be seen in NSLogs (and it is visible in other parts of the code as well apart from NSLog). Am I doing it wrong, or should I be using timer?
private func updateTimeCode() {
NSLog("Updating time")
//Some more code that doesn't take time
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0) {
[weak self] in
self ? .updateTimeCode()
}
}
019-08-06 17:15:19.713234+0530 MyApp-Swift[8299:2685215] Updating time
2019-08-06 17:15:20.812652+0530 MyApp-Swift[8299:2685215] Updating time
2019-08-06 17:15:21.913188+0530 MyApp-Swift[8299:2685215] Updating time
2019-08-06 17:15:23.028814+0530 MyApp-Swift[8299:2685215] Updating time
It's because of the part that you say some more code not relevant taking time. They take time and time passes between each invocation of asyncAfter so basically .now() becomes something more than exactly 1 second ago.
Anyway, it's not a conventional way to achieve it. You need to use timer for that purpose. Here's a useful tutorial about how to use it. Timer in Swift

UIAutomation and XCTestCase: how to wait for a button to activate

I'm writing a UIAutomation test case and I need to wait for the user to be activated before continuing. There doesn't seem to be a nice way to check for a button to change to the enabled state.
Whats the best was to wait for something to happen in the UI before checking it's status?
Neither dispatch_after nor NSTimer seem to work. They just block then fail.
It's actually pretty easy if you use NSPredicates and expectations. You can even set a timeout value. This example shows you how to do it with a 5 second timeout.
let exists = NSPredicate(format:"enabled == true")
expectationForPredicate(exists, evaluatedWithObject: app.tables.textFields["MY_FIELD_NAME"], handler: nil)
waitForExpectationsWithTimeout(5, handler: nil)
The better way to wait and check an element isn't the delay() function, but the pushTimeout() one. Apple recommends to use the second function. Here is a code sample:
UIATarget.localTarget().pushTimeout(10)
button.tap()
UIATarget.localTarget().popTimeout()
Apple will repeatedly try to tap the button and will wait up to 10 seconds. Here's a link to the documentation.
Etienne's answer is correct but in my scenario it required something extra.
I'm using React Native and had a <TouchableWithoutFeedback disabled={true}> component. However I could see that whenever XCUI tried to check its state it considered it enabled. Indeed, using a breakpoint and checking element.IsEnabled clearly contradicted what I was seeing in the UI.
Using AccessibilityState will achieve this however, for example:
<TouchableWithoutFeedback
accessibilityState={{
disabled: this.props.disabled,
}}
>
Experienced with RN 0.62.2
You should be able to implement a while loop to check for the condition you want (e.g. button enabled). That will stop the test case progress until the while condition is met and the tests will continue. Build in a delay to slow polling and make sure you have a timeout so you don't get stuck indefinitely.
Pseudocode:
While (/*button is disabled*/) {
if (/*timeout condition met*/) {
/*handle error*/
break;
}
UIATarget.delay(<duration in seconds>);
}

Cocos2d (iOS): scheduleOnce called in callback won't fire

I am scheduling a callback via scheduleOnce (Cocos 1.1b), and when the callback is executed and once all tasks were performed there, I try to reschedule the same callback again (just with a different delay). The reasoning is to achieve a varying delay between the callbacks.
However, while it is called properly the first time, the second scheduling will never fire it again. Stepping through the Cocos libs, it eventually adds a timer to the list, but it won't fire.
Any clue what I am doing wrong and need to do differently?
Edit: just saw this entry in the log on the second scheduling:
CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: 0.00 to 0.00
I tried now to unschedule all timers explicitly first, however it doesn't make a difference. I would anyway expect scheduleOnce to reset that timer on callback.
It may be a bug in Cocos2D, after all you're using the very latest beta version. So I won't divulge into that, you may want to report this through the official channels however (cocos2d forum, google code issues for cocos2d-iphone).
In the meantime you can simply do this:
-(id) init
{
…
[self scheduleSelector:#selector(repeat) interval:0];
}
-(void) repeat
{
// simply schedule the selector again with a new interval
[self scheduleSelector:#selector(repeat) interval:CCRANDOM_0_1()];
}
Alternatively, if you want to re-schedule the selector at a later time, you can unschedule it as follows within the repeat method (the _cmd is shorthand for the selector of the current method):
-(void) repeat
{
[self unschedule:_cmd];
// re-schedule repeat at a later time
}

Resources