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
}
Related
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.
I would like to know how to limit the time my app is running in background.
The purpose is to show the LaunchScreen again and start the app from initial state after 5 or 10 minutes in background run.
thanks in advance
Here are some ideas and thoughts to achieve what you want.
First, when your app moves into the background, it makes a call to applicationDidEnterBackground(_:) in your App Delegate. If you are using scenes with a Scene Delegate then you should perform any last logic in sceneDidEnterBackground(_:)
As per the docs, you have 5 seconds to perform any task once the above functions have been called
applicationDidEnterBackground(_:)
Return from applicationDidEnterBackground(_:) as quickly as possible.
Your implementation of this method has approximately five seconds to
perform any tasks and return.
After this, your app goes into a suspended state and if you need more than 5 seconds, you can request more by calling beginBackgroundTask(withName:expirationHandler:) but for your purposes I don't think you need this.
More on the above topics here
You have little to no control on when your app gets to run on the background again, that is moved from a suspended to a background state.
There is a property for BGTaskRequest called earliestBeginDate but as per the docs:
earliestBeginDate
Specify nil for no start delay.
Setting the property indicates that the background task shouldn’t
start any earlier than this date. However, the system doesn’t
guarantee launching the task at the specified date, but only that it
won’t begin sooner.
So long story short, you can't really tell when your app will get to run on the background again and you can't decide how much time it will be given to run.
What you could do is implement either applicationDidEnterBackground(_:) or sceneDidEnterBackground(_:) if you are using scenes to track at what time the app goes into the background like so:
func sceneDidEnterBackground(_ scene: UIScene)
{
// Get the current date / time
let currentDate = Date()
let userDefaults = UserDefaults.standard
// Start tracking time as soon as the app goes
// into the background
userDefaults.setValue(currentDate,
forKey: "PreviousLaunchDate")
// Just for testing, remove from production app
print("start tracking")
}
And then when your app comes into the the foreground, check how much time has elapsed and react accordingly:
func sceneWillEnterForeground(_ scene: UIScene)
{
let userDefaults = UserDefaults.standard
let currentDate = Date()
// Check if you have saved a date before
if let previousLaunchDate
= userDefaults.object(forKey: "PreviousLaunchDate") as? Date
{
// Retrieve the minutes elapsed
// Check if minutes elapsed is greater than 10 minutes or as you wish
if let minutesElapsed = Calendar.current.dateComponents([.minute],
from: previousLaunchDate,
to: currentDate).minute,
minutesElapsed > 10
{
// Reset your window's root view controller to start again
// showing the splash view or reset your storyboard etc
// For example
// let splashVC = SplashViewController()
// window?.rootViewController = splashVC
// just for testing, remove from production app
print("show splash vc")
return
}
// Do nothing since 10 minutes haven't elapsed so you can
// show the current state of the app to the user
// just for testing, remove from production app
print("do nothing")
}
}
I think this should get you close to what you are looking for.
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)
I'm developing a simple app in Swift and I need to schedule a function execution every 24 hours. I'm aware of the method:
DispatchQueue.main.asyncAfter(deadline: .now() + 10.0, execute: {
self.functionToCall()
})
that could solve my problem but, is this the right solution for a 24 hours delay?
Thanks
Theoretically, this is possible.
The problem is that your app would have to run in the foreground for 24 hours, which is very unlikely to happen. Unfortunately, you can not run background tasks just like that.
The solution:
Just make it look like the function would execute in the background. Every time the update function is called, simply save the Int(Date().timeIntervalSince1970) to UserDefaults. This works like a timestamp and saves the last time you called your update function. Every time in the viewDidLoad()-function (not sure if it's called the same on Mac apps, but you can imagine what I mean) call:
If let timestamp = UserDefaults.standard.integer(forKey: "yourTimestampKey") {
let currentTimestamp = Date().timeIntervalSince1970
if (currentTimestamp - timestamp) > 86400 { // number of seconds in 24 hours
// the last time your function was updated was at least 24h ago
update()
}
}
That's how you can make it appear like it was updated in the background. I use this all the time in my apps and it works perfectly.
EDIT:
Maybe, just in case the app does indeed run 24 hours in a row, I would set up the upper function that you posted first as well.
I have this two functions that measure the elapsed time when the phone is locked or the app is in background:
func saveTimeInBackground(){
startMeasureTime = Int(Date.timeIntervalSinceReferenceDate)
}
func timeOnAppActivated(){
stopMeasureTime = Int(Date.timeIntervalSinceReferenceDate)
elapsedTime = stopMeasureTime - startMeasureTime
seconds = seconds - elapsedTime + 2
if seconds > 0 {
timerLbl.text = "time: \(seconds)"
} else {
seconds = 0
timerLbl.text = "time: \(seconds)"
}
}
and then in the viewDidLoad() i have observers that are trigger the functions when the app becomes active/inactive:
NotificationCenter.default.addObserver(self, selector: #selector(saveTimeInBackground), name: Notification.Name.UIApplicationWillResignActive, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(timeOnAppActivated), name: Notification.Name.UIApplicationDidBecomeActive, object: nil)
The problem is that when the app becomes active there are 2 seconds (approximately) of difference so i've added 2 seconds and it seems to work fine, but only if the elapsed time is > 15 seconds.
If i lock the phone and immediately unlock it the there are like 5 or more seconds that are missing. For example, if there are 50 seconds left, when i lock and immediately unlock it there are like 42 seconds left.
Can anyone please explain, what i am doing wrong?
Edit: The logic of the app is this:
It starts a match between 2 players with 60 seconds for a game. The problem is that when one of the players locks the phone the app stop to measure the time. This way if the player1 has 10 seconds left to make a move, the player2 still has 50 seconds left. I'm looking for a reliable way to calculate the time even if the player locks the phone or put the app in background.
Edit 2: I think i figured out what the problem is: I think the issue has to do with the fact that the “seconds” are Int, and the Date not and when it gets converted it’s rounded up. I didn't tested it, but when i ahve the solution i'll post the answer. Thanks all for your time!
You're relying on exact timing of notifications that aren't guaranteed to have any exact timing. There's no guarantee about when, exactly, either of those notifications will arrive, and there's nothing you can do about that. Even your two-second fix is, as you say, approximate. It'll probably be different on different models of iPhone or even at different times on the same iPhone, depending how busy iOS is when you check.
What's more, when you go into the background, you can't be certain that you'll stay there. Once in the background, iOS might decide to terminate your app at any time.
I'm not sure what the goal is here but I think you'll need to reconsider what you want to do and see if there's some other approach. Your current two-second hack will, at best, spawn a bunch of other hacks (like the 15 second threshold you mention) without ever being especially accurate. And then it'll probably all break in the next iOS update when some iOS change causes the timing to change.
I would use Date object to track game time.
func gameStart() {
gameStartDate = Date()
}
func timeOnAppActivated() {
let secondsLeft = 60 - abs(gameStartDate?.timeIntervalSinceNow ?? 0)
if secondsLeft > 0 {
timerLbl.text = "time: \(secondsLeft)"
} else {
timerLbl.text = "time: 0"
}
}
Ok, like I mention in the edit 2 of the question:
The first issue was because "seconds" is a Int and then it almost always gains or lose when converting it from Double.
But the main problem was that i had to invalidate the timer when the app enter in background and i didn't.
So now with invalidating the timer when the app gets the notification that will enter background and then starting it when it enter foreground everything works fine.
To test this properly call those methods on button click. It may be coz of delay in releasing some resources in background.