How Do I Manage Scheduled Events in Swift 4? - swift

Looking for some direction on how to best manage scheduled events in swift 4 (either external library or Apple support).
I would like to be able to put a task (with a timer) in to some kind of hub that will then execute the task once the timer has run down and then perform the task's completion block at the end of the task.
Can I do this with NSNotification?
Anyone know of a good example app (e.g. GitHub) that I can download and play with to learn how to schedule tasks.
More detail
I am creating timers like this
timer_iosActionsRefresh = Timer(fireAt: date, interval: timeInterval, target: self, selector: #selector(refreshIosActions), userInfo: nil, repeats: true)
RunLoop.main.add( timer_iosActionsRefresh!, forMode: RunLoopMode.commonModes)
I then do this to stop timers
if timer_iosActionsRefresh != nil {
timer_iosActionsRefresh?.invalidate()
timer_iosActionsRefresh = nil
}
This works great in app foregoround mode but does not work in background mode since Apple only supports special states for background mode activities (e.g. CLLocationManager didEnterRegion)
1) Is it possible to create something like a Timer but that will still execute in background mode? Not looking to do heavy or continuous work, just small background queries.
2) Even if there is some way around Timers not working in background mode, will that code make it through Apple app review?

I would recomment using GCD (Grand Central Dispatch), which is fairly easy:
let queue = DispatchQueue.global(qos: .background) // or some higher QOS level
// Do somthing after 10.5 seconds
queue.asyncAfter(deadline: .now() + 10.5) {
// your task code here
DispatchQueue.main.async {
// maybe update UI here after task has finished
}
}
If you dont need a background queue and want to perform the task simply in the main thread, just do:
DispatchQueue.main.asyncAfter(deadline: .now() + 10.5) {
// task etc.
}
If you want to be able to cancel tasks you sheduled, you should take a look at NSOperation (which ist based on GCD and allows cancelling and other higher level stuff).

If I am understanding this question correctly, I would first add the observer to the view controller that you want to run the task's completion block
NotificationCenter.default.addObserver(self, selector: #selector(self.methodOfReceivedNotification(notification:)), name: Notification.Name("runCompletionBlock"), object: nil)
then in the method you called from the NSNotification you add the completion block.
then create a timer in the class of the hub you want to make
var timer = Timer()
initialise it
timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(runTimedCode), userInfo: nil, repeats: true)
Then create a method runTimedCode() that gets called when the timer finishes.
You can then add a NSNotification to notify the observer you created with
NotificationCenter.default.post(name: Notification.Name("runCompletionBlock"), object: nil)
Not sure if this is what you were after, but it may help.

Related

Update Live Activity once per second - Swift

I'm searching for a way to update the Live Activity every second or every 2 seconds without using Push Notifications while keeping the app in background.
Do you have any suggestions? I tried something like this, but after few seconds it's stop working:
var bgTask = UIBackgroundTaskIdentifier(rawValue: 1324)
bgTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
UIApplication.shared.endBackgroundTask(bgTask)
})
let timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(updateInfo), userInfo: nil, repeats: true)
RunLoop.current.add(timer, forMode: .default)
As mentioned in the documentation, "The delay between the time you schedule a background task and when the system launches your app to run the task can be many hours".
Unfortunately, background tasks are not suitable for your needs. Using Push notifications is the only way (that I know of at least) to achieve what you are trying to do.
It seems like there is no way to update the Live Activity every single second or in any other too frequent manner. Looks like battery saving is crucial here and system cuts off the updates after a few seconds, even if the application is running in the background.
If you need to display a timer on the LiveActivity, according to the docs, it's possible to use the following initializer of Text which seems to be dedicated for that use case:
init(timerInterval: ClosedRange<Date>, pauseTime: Date? = nil, countsDown: Bool = true, showsHours: Bool = true)
For example:
Text(timerInterval: context.state.deliveryTimer, countsDown: true)
It creates an instance that displays a timer counting within the provided interval. It just counts time automatically so it's not necessary to think about implementing timer on your own. However, there is no way of listening when timer ends to update the activity so looks like it might be necessary to schedule a notification or background task anyway and receive/run and handle it when timer ends.
Seems like apps mentioned here use a mechanism I described above. You have to consider your use case and decide whether Live Activities are suitable for your app.

How to set a timer for a string to trigger periodically?

I'm making a FlashCards app and I would like to set a timer for the cards in a way so that if the user knows the answer, the card will trigger again next day, then if he knows it again, it will trigger in 5 days and so on.
I haven't found anything related to this, any help with it?
Get track of the Dates and the User's performance with each card. (You can use a Timer here. For instance, if the User 'knows' the answer within a minute, the card will be marked as 'correct' (known), and beyond that time, the card will be marked as 'wrong' (not yet mastered and needs to be repeated).
Create a logic wherein the cards marked as 'wrong' will popup after a few days or so.
I suggest you use CoreData to save the Dates when the User uses the Flashcard app.
Also, you will need to learn how to use DateComponents().
Here's a great resource.
In case when users don't quit app you can use Timer. However, this is only expected for short intervals.
let minute = 60
let anotherTimeInterval = 2 * minute
var timer = Timer.scheduledTimer(timeInterval: minute, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: false)
#objc func updateTimer() {
// do something
timer.invalidate()
timer = Timer.scheduledTimer(timeInterval: anotherTimeInterval, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: false)
}
Normally, you need to prepare a specific data source for a specific day. For example, in your case, you can add a flag or date to each card and use that flag or date to add that card to the data source on a specific date.

How to detect (say) 2 seconds of inactivity in swift?

This question could be rephrased as: How to invoke a function if 2 seconds pass without an event (re)occurring?
I'm playing with SFSpeechRecogniser. While the user is speaking it sends continuous updates (maybe 2-3 per second). I'm trying to detect when the user stops speaking. If I don't receive any updates for (say) 2 seconds, I can assume that the user has paused speaking.
How to implement this in Swift?
I am aware that I could do:
var timer : Timer?
func f() {
on_event = { result, error in
print( "Got event, restarting timer" )
self.timer?.invalidate()
self.timer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { _ in
print( "2s inactivity detected" )
self.timer?.invalidate()
NotificationCenter.default.post( name: inactivity_notification, object: nil )
}
}
}
But is it possible to do it without repeatedly creating and destroying the Timer instance (and thus creating a boatload of temporary Timer instances that never get used)?
One way to do it is to:
Record the current time when an event occurs
Set up a recurring timer with a granularity you are comfortable with (for example 0.25 seconds).
When the timer pops, check difference between current time and last event time. If that is greater than 2 seconds, fire your notification.
This is what I'd do if I had to recognize that a person had stopped typing for 2 seconds. Invalidating and creating timers at typing speed would be a lot of churn. You can tune this to your requirements depending on how close to exactly 2 seconds you need to be.
You could also do this by just having a timeSinceLastEvent variable, and set it to 0 when an event occurs. The recurring timer would increment this by the granularity, and check if it has reached 2 seconds and fire the notification if it had. This is cruder than doing the time math since the timer interval isn't guaranteed, but simpler.
Timer's .fireDate property is writable.
So every time a speech event occurs just do timer.fireDate = Date(timeIntervalSinceNow: 2)

How to clear UserDefault for a specific key after 12 hours

I'm trying to remove a user default key by setting up a Timer after user login.After research and try, i found that timer will not run when app enter background.
You could store a loginDate in user default, and do a check with Date().timeIntervalSince(loginDate) > 12 * 60 * 60 in applicationDidBecomeActive(_ application: UIApplication)
If you want to do some task in background, you have to enable background mode for the app. But that will allow only some minutes to run app in background.
Rather than that, you should save time stamp of login time and check on every didFinishLaunchingWithOptions about time difference of current time and time you saved.
Use DispatchQueue.global instead of NSTimer , set global queue to background. And in execution use DispatchWorkItem.
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + TimeInterval(yourtime interval value), execute: workItem)
var workItem = DispatchWorkItem {
// write your flush userdefault code here
}
After using workItem cancel it.
self.workItem?.cancel()
Try This
DispatchQueue.global(qos: .background).async {
// Write your code here timer running while app will not turminate
}

swift 4 Timer to start at specific time, then do the interval

I want to call a method at the same time every day (at the end of the day, like 23.59).
I understand to set a repeating method to be called I do like this:
let date = Date()
let timer = Timer(fireAt: date, interval: 86400, target: self, selector:
#selector(runCode), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
This repeats every 86400 seconds, which mean every day.
But how to set the date parameter to start at 23.59, the specific time I want. Currently, it just goes off at the time the code calls it.
Thanks very much
sounds like you might have to do silent notifications:
Silent Push Notification Payload
What is difference between remote notification and silent notification in iOS?
There is also a UNNotifcationRequest you can look into but it's not silent:
Use UNNotificationRequest (UserNotificationFramework) for data only local notification on iOS