Update Live Activity once per second - Swift - 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.

Related

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)

NSImageView update delayed when set from an NSTimer

I'm working on a project in Swift that requires strict control of image display and removal timings in certain sections. I'm finding that when I set the image property of an NSImageView from inside a block that's fired by a Timer, the actual display of the image on the screen is delayed by up to a second after the assignment is complete. (This is measured by eyeballing it and using a stopwatch to gauge the time between when an NSLog line is written and when the image actually appears on-screen.)
Triggering image display with a click appears to happen instantaneously, whether it's done by setting the image property of an existing NSImageView, or constructing one on the spot and adding it as a subview.
I have attempted to reduce the behavior down to a fairly simple test case, which, after basic setup of the view (loading the images into variables and laying out several target image locations, which are NSImageViews stored to the targets array), sets a Timer, with an index into the targets array stored in its userInfo property, to trigger the following method:
#objc func testATimer(fromTimer: Timer) {
if let targetLocation = fromTimer.userInfo as? Int {
NSLog("Placing target \(targetLocation)")
targets[targetLocation].image = targetImage
OperationQueue.main.addOperation {
let nextLocation = targetLocation + 1
if (nextLocation < self.targets.count) {
NSLog("Setting timer for target \(nextLocation)")
let _ = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(ViewController.testATimer(fromTimer:)), userInfo: nextLocation, repeats: false)
}
}
}
}
The amount of time observed between the appearance of the log entry "Placing target x" and that of the image that is set to be displayed in the very next line seems to average between 0.3 and 0.6 seconds. This is far beyond the delay that this project can tolerate, as the image will only be on screen for a total of 1 second at a time.
My best guess as to why this is happening is that it is something to do with Timers running on a separate thread from the main display thread; however, I do not have enough experience with multi-threaded programming to know exactly how to mitigate that.
Any assistance would be most appreciated, and if I've left out information that would make answering easier (or possible) I'm more than happy to give it.
Well, after poking at it with some helpful people on in #macdev on irc.freenode.net, I found that the source of the problem was the program scaling the image down on the fly every time it set it to the NSImageView. Reducing the size of the image ahead of time solved the problem. (As did setting the image once, then hiding and showing the NSImageView instead.)

How Do I Manage Scheduled Events in Swift 4?

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.

Creating a CGEvent tap the right way

I am trying to learn how to create a CGEvent tap using examples on SO and and elsewhere.
I know how to create an event tap using:
let eventTap = CGEvent.tapCreate(*/arguments to create the tap*/)
After that I run
let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
Some, but not all examples I've found online also add the following two lines
CGEvent.tapEnable(tap: eventTap, enable: true)
CFRunLoopRun()
Can someone briefly explain if or when is adding the last two lines are useful/recommended?
The first is easy: the docs say:
Event taps are normally enabled when created. If an event tap becomes unresponsive, or if a user requests that event taps be disabled, then a kCGEventTapDisabled event is passed to the event tap callback function. Event taps may be re-enabled by calling this function.
The second one is probably for cases where the program doesn't have a run loop (on the current thread). For example, a command-line program which doesn't use NSApplication would need to create and run its own run loop.