I've read many many articles, read many StackOverflow posts and tried different solutions, but I can't get it to work.
For an app that communicates with bluetooth headsets, the user must be able to start/stop a stopwatch when the user presses a button on the headset. When this button is pressed I receive a bluetooth event and start the stopwatch (receiving this bluetooth event in the background is not a problem). When the stopwatch is started I use the AVSpeechSynthesizer to speak the text 'Stopwatch started'. Now comes my problem: I want to have a text to speech every minute, speaking the amount of minutes that elapsed. But also when the app is in the background...
I've read about solutions like this:
var bgTask = UIBackgroundTaskIdentifier()
bgTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
UIApplication.shared.endBackgroundTask(bgTask)
})
let timer = Timer.scheduledTimer(timeInterval: 60.0, target: self, selector: #selector(notificationReceived), userInfo: nil, repeats: true)
RunLoop.current.add(timer, forMode: RunLoopMode.defaultRunLoopMode)
It did work, but I can't find anything about how stable this code is on production (when the app is downloaded from the App Store). Also scheduled local notifications can not trigger any code when the app is in the background. Even the property postUtteranceDelay of AVSpeechUtterance seems unstable. I set the property to 460 seconds, but the text was spoken after a random 56 seconds...
I turned on almost all the background modes for my app.
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>bluetooth-central</string>
<string>fetch</string>
<string>remote-notification</string>
</array>
Other people suggest to make a silent audio track. But it seems that apps using that kind of 'silent tracks' are being refused by the Apple Review team.
What depresses me the most, is that apps like Runkeeper and Seconds Interval Timer are able to do his. Even when I turn on flight mode and disable all settings (like push, motion sensing) from the apps. So, why can't I find a working solution...?
Does anyone have a suggestion where to start?
Related
I'd like to prevent iOS from killing my app after a few minutes.
I've read this thread here on SO: Prevent iOS from killing my app after 3 minutes . It says that if I have no backgroundtasks longer than 3 minutes my app wont be killed. Can someone verify that this is true? Because my background-task is not running longer than 3 minutes and even though my app gets killed after this time.
My background-task is a timer that updates a widget. Heres some code:
self.backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
self?.endBackgroundTask()
//endBackGroundTask looks like this
UIApplication.shared.endBackgroundTask(self.backgroundTask)
self.backgroundTask = UIBackgroundTaskInvalid
//
}
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(self.updateTimer)), userInfo: nil, repeats: true)
.
// at the beginning of the class
var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
.
// in viewWillDisappear
self.timer.invalidate()
if self.backgroundTask != UIBackgroundTaskInvalid {
self.endBackgroundTask()
}
You need to structure your app so that it doesn't require continual execution in the background. As I understand it, your app shows a count down timer and can show the same count down timer in a Today Widget. The approach I would use is follows:
Store the "end date" for the timer in user defaults to share with your widget
When your app is in the foreground, use a Timer to periodically update your UI
When your Widget is being displayed use a Timer in the widget to periodically update its UI
When your app moves to the background, schedule a local notification for the expiration time
When your app moves back to the foreground, you can cancel that scheduled notification if it hasn't yet fired.
Support app restoration for those cases where your app is legitimately terminated (e.g. due to memory pressure or being suspended for a long period)
If you do this then you never need to call beginBackgroundTask. If you do call beginBackgroundTask and don't call endBackgroundTask within 3 minutes of entering the background, then your app will be terminated, even if you aren't using any CPU.
Short answer: You can't run a background task for longer than 3 minutes unless you are a turn-by-turn navigation app or an audio player. Apple doesn't allow it by design.
Your background task is a timer that is running longer than 3 minutes. So your app is correctly being killed. Consider it confirmed as that is Apple's design.
It's not what your timer is executing that is killing the app, it's the timer itself.
You can read up on Apple's Documentation for more information.
Always try to avoid doing any background work unless doing so improves the overall user experience. An app might move to the background because the user launched a different app or because the user locked the device and is not using it right now. In both situations, the user is signaling that your app does not need to be doing any meaningful work right now. Continuing to run in such conditions will only drain the device’s battery and might lead the user to force quit your app altogether. So be mindful about the work you do in the background and avoid it when you can.
I have a framework that returns a ViewController to my app upon request.
This view controller contains a QR code that is subjected to change after a certain period of time - say 3 days.
I would like to call a method contained inside the framework after 3 days so that the changed/updated QR is readily available even if the user is not using the app actively. When user opens the app - the updated QR is there!
For that, I have used below in my ViewController inside the framework - (from tutorial link)
//MARK:- Add Timer to run QR Logic after 5 seconds
let date = Date().addingTimeInterval(5)
let timer = Timer(fireAt: date, interval: 0, target: self, selector: #selector(QrUIViewController.performQrFetchLogic), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
This piece of code works perfectly when app is active/background. Is it possible, and how to achieve this even if the app is killed?
PS: I read Jobscheduler and AlarmManager will work for Android - is there something similar to these in iOS?
The best way to achieve this is probably using a scheduled Local Notification.
You schedule local notification for some time in the future and then afterwards react to it. This is the case both for when the app is active, in background or inactive.
Remember that notifications do not fire if the user kills your app.
I am making an app that helps people with tracking down the working intervals.
What I need is the timer should run at least 30 minutes regardless of the app is in foreground or background.
func startFocus() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(Pomodoro.focusIntervalCounter), userInfo: nil, repeats: true)
}
func focusIntervalCounter() {
dynamic_focusIntervalSecond -= 1
focusDelegate?.updatingFocusCountingDown(timeStamp: seconds2Timestamp(intSeconds: dynamic_focusIntervalSecond), secondLeft: dynamic_focusIntervalSecond)
if dynamic_focusIntervalSecond == 0 {
timer.invalidate()
focusDelegate?.focusCountingDowndid(end: true)
}
}
focusIntervalCounter() should invalid the timer when the `dynamic_focusIntervalSecond` is 0.
It works fine when the program is in the foreground, but after the screen is shut, the timer just works a little while and stop.
Is this any approach to continue the timer's counting?
Thanks in advance.
No, you can run background tasks for up to 5 minutes, use the background update functionality to trigger app refresh, or use notifications to trigger background actions. There is no way in iOS to guarantee that any code will be run consistently in the background after 30 minutes. Local notifications will enable your code to run after the user selects an action in a notification. Silent push notifications can run some code in the background open receipt, but require an internet connection and are not guaranteed to be delivered.
See this question for more info:
iOS Timer in the background
No, You cannot run Timers in background mode. You can create a UIBackgroundTaskIdentifier which will give you 180sec as i have observed, i'm not sure it might vary with OS version.
You can try scheduling local notifications for 30 mins.
You can enable back ground modes if you're using push notification, Airplay, Location, VOIP apps, Bluetooth, News stand, Background fetch, for more details read this apple developer document BackgroundExecution
Some things are not possible in background, Have you switched your project to enable the background modes? You can reed more about it here
How do I trigger a UILocalNotification from the iPhone which would have no alert but only play a sound / haptic feedback on the Apple Watch?
Further background:
I am making a watchOS2 timer app. I use a UIlocalNotification triggered by the iPhone to tell the user that end of timer is reached (to handle the scenario where the watch is not active).
The problem is that Apple uses its own logic to determine if a notification appears on the watch or the phone. If I trigger a notification with no alert but only sound, this notification always plays on phone, never on the watch.
I'd prefer that notification sound/haptic to play on the watch. How can I achieve this?
The downside of what you're asking:
A sound-only notification on the watch would be confusing.
Without a message associated with the sound, the user wouldn't see any reason for the notification, when they glanced at their watch.
The downside of how to currently do what you're asking:
The WKInterfaceDevice documentation points out Apple's intention for playing haptic feedback:
You can also use this object to play haptic feedback when your app is active.
What follows is a misuse of the API to accomplish something it wasn't intended to do. It's fragile, potentially annoying, and may send users in search of a different timer app.
Changes for iOS 10 prevent this from working, unless your complication is on the active watch face.
How you could currently do what you're asking in watchOS 2:
To provide haptic feedback while your app is not active, you'd need a way for the watch extension to immediately wake up in the background, to provide the feedback:
WKInterfaceDevice.currentDevice().playHaptic(.Notification)
To do this, you could misuse the WCSession transferCurrentComplicationUserInfo method. Its proper use is to immediately transfer complication data from the phone to the watch (that a watch face complication might be updated). As a part of that process, it wakes the watch extension in the background to receive its info.
In your phone's timerDidFire method:
After checking that you have a valid Watch Connectivity session with the watch, use transferCurrentComplicationUserInfo to immediately send a dictionary to the watch extension.
guard let session = session where session.activationState == .Activated && session.paired && session.watchAppInstalled else { // iOS 9.3
return
}
let hapticData = ["hapticType": 0] // fragile WKHapticType.Notification.rawValue
// Every time you misuse an API, an Apple engineer dies
session.transferCurrentComplicationUserInfo(hapticData)
As shown, the dictionary could contain a key/value pair specifying the type of haptic feedback, or simply hold a key indicating that the watch should play a hardcoded notification.
Using an internal raw value is fragile, since it may change. If you do need to specify a specific haptic type from the phone, you should setup an enum instead of using a magic number.
In the watch extension's session delegate:
watchOS will have woken your extension in preparation to receive the complication user info. Here, you'd play the haptic feedback, instead of updating a complication, as would be expected.
func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
if let hapticTypeValue = userInfo["hapticType"] as? Int, hapticType = WKHapticType(rawValue: hapticTypeValue) {
WKInterfaceDevice.currentDevice().playHaptic(hapticType)
}
}
The proper solution:
Request that Apple provide a way to schedule local notifications on the watch. Apple's timer app does this already.
You may also wait to see what is announced at WWDC 2016, to decide if any new functionality available in watchOS 3 would help to create a proper standalone watch timer app.
I know there is NSTimer.scheduledTimerWithInterval
Which is used like this:
override func viewDidLoad() {
super.viewDidLoad()
var timer = NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: Selector("update"), userInfo: nil, repeats: true)
}
func update() {
// Something cool
}
But I assume that the view where var timer is living must be living too, ie: can't close that view (am I right?)
How can I schedule something once a day in iOS, eg: everyday at 8pm send a notification?
In Android I've achieved this the following way:
I've used a thing called AlarmManager, that is similar to the iOS scheduledTimerWithInterval but for large intervals, which is running inside a background service.
At the startup (boot), there is another background service that setups the alarm again. This covers the case when the Android device is shut down (and so the background service is shut down too)
So, in iOS, is there something like scheduledTimerWithInterval for large intervals?
Will I need to set again the interval if the iPhone/iPad is rebooted?
Yes, to use NSTimer, the app has to be running either in foreground or background. But Apple is quite particular only allowing certain types of apps to continue to run in the background (in an effort to make sure we don't have apps randomly running on their own prerogative and killing our batteries in the process and/or affecting our performance while using the device).
When you say "notification", do you really mean notifying the user of something?
In that case, the alternative here is to create a UILocalNotification, which is a user notification (assuming they've granted your app permission to perform notifications), which is presented even when your app is not running.
For example, to register for local notifications:
let application = UIApplication.sharedApplication()
let notificationTypes: UIUserNotificationType = .Badge | .Sound | .Alert
let notificationSettings = UIUserNotificationSettings(forTypes: notificationTypes, categories: nil)
application.registerUserNotificationSettings(notificationSettings)
And then to schedule the repeating notification:
let notification = UILocalNotification()
notification.fireDate = ...
notification.alertTitle = ...
notification.alertBody = ...
notification.repeatInterval = .CalendarUnitDay
application.scheduleLocalNotification(notification)
For more information, see the Local and Remote Notification Programming Guide.
Or do you mean initiating some process, such as fetching data from a remote server.
If you want the app to fetch data even if your app isn't running, you can use background fetch. See Fetching Small Amounts of Content Opportunistically in the App Programming Guide for iOS.
Note, with background fetch, you don't specify when data is to be retrieved, but rather the system will check for data at a time of its own choosing. It reportedly factors in considerations ranging from how often the user uses the app, how often requests to see if there is data result in there actually being new data to retrieve, etc. You have no direct control over the timing of these background fetches.