I'm writing an application that requires location updates every n minutes and sends data to a server even when the application is in background mode. I have gone through so many links about this task. As I found the proper solution is to use a timer and make it as a background task.
Concerning to this I have two questions:
How can I implement these regular background location updates? I understand that Apple allows background tasks for an only certain time. So how can I make long-running background timer?
Will apple reject the application with such kind of logics?
inside appdelegate create method like this
func getLocation()
{
locationManager.allowsBackgroundLocationUpdates = true
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
inside didFinishLaunchingWithOptions method in appdelegate
var n = 10 //seconds
var timer = Timer.scheduledTimer(timeInterval: n, target: self, selector: #selector(getLocation), userInfo: nil, repeats: true)
set the delegate method for cllocationmanager
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
locationManager.stopUpdatingLocation()
locationManager.delegate = nil
//get the coordinates or do some code
}
Use following code.This will work for 3 minutes
Call this didenterbackground or foreground
var time = 180
func applicationDidEnterBackground(_ application: UIApplication)
{
self.doBackgroundTask()
}
var timerBack = Timer()
func doBackgroundTask() {
DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async {
self.beginBackgroundUpdateTask()
print("Background time remaining = \(UIApplication.shared.backgroundTimeRemaining) seconds")
self.timerBack = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(AppDelegate.displayAlert), userInfo: nil, repeats: true)
RunLoop.current.add(self.timerBack, forMode: RunLoopMode.defaultRunLoopMode)
RunLoop.current.run()
self.endBackgroundUpdateTask()
}
}
var backgroundUpdateTask: UIBackgroundTaskIdentifier!
func beginBackgroundUpdateTask() {
self.backgroundUpdateTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
self.endBackgroundUpdateTask()
})
}
func endBackgroundUpdateTask() {
UIApplication.shared.endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskInvalid
}
//Do whatever you want
func displayAlert{
}
Related
I want to send user current Location to a server in every 15 minutes. When the app is in the foreground the timer is running but when the app is in background timer is not running. I searched a lot but did not find any solution. Can you please tell me what I do. I am using below code for background location sync but it's not working.
var locationManager = CLLocationManager()
var currentLocation: CLLocation?
var timer : Timer?
var backgroundTaskIdentifier: UIBackgroundTaskIdentifier?
func getCurrentLocation()
{
// Do any additional setup after loading the view, typically from a nib.
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
//
backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
UIApplication.shared.endBackgroundTask(self.backgroundTaskIdentifier!)
})
let timer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: true) { (timer) in
self.locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
print("locations = \(locations)")
if let location = locations.last
{
print("location\(String(describing: location.coordinate))")
currentLocation = location
locationManager.stopUpdatingLocation()
}
}
I am also having similar kind of requirement but time interval is 5minutes.
May be your app in idle for longer time(15 minutes) that's y OS is not updating your locations.
Try to keep shorter interval and try it will work.
Did you handle location update from AppDelegate for application state? If not you can update your code with below methods in AppDelegate.
var locationManager = CLLocationManager()
func doBackgroundTask() {
DispatchQueue.global(qos: .background).async {
self.beginBackgroundUpdateTask()
self.StartupdateLocation()
RunLoop.current.run()
}
}
func beginBackgroundUpdateTask() {
backgroundUpdateTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
self.endBackgroundUpdateTask()
})
}
func endBackgroundUpdateTask() {
UIApplication.shared.endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskInvalid
}
func StartupdateLocation() {
locationManager.startUpdatingLocation()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.requestAlwaysAuthorization()
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
}
Call the method when application is going in background state.
func applicationWillResignActive(_ application: UIApplication) {
self.doBackgroundTask()
}
This methods will help you for getting update of location when app is in background.
You can take a look at this link if you face any other difficulties. Update location in Background
i want to sent the location to the server when the app is background, i tried with many way but can't get the solution
I tried with the timer function using the timer and using function
self.bgtimer = Timer.scheduledTimer(timeInterval:60, target: self, selector: #selector(AppDelegate.bgtimer(_:)), userInfo: nil, repeats: true)
RunLoop.current.add(self.bgtimer, forMode: RunLoopMode.defaultRunLoopMode)
RunLoop.current.run()
but i when i stop the timer when the app becomes active
if UIApplication.shared.applicationState == .active {
RunLoop.current.cancelPerformSelectors(withTarget: self.bgtimer)
timer.invalidate()
}
but the app not function properly collection view and back function everything is not working properly
In DidBackground:
stop normalTimer.
start New backgroundTaskTimer
In WillForeground:
stop backgroundTaskTimer.
start normalTimer
Find below sample code:
In Appdelegate:
Declaration:
var backgroundUpdateTask: UIBackgroundTaskIdentifier!
var backgroundTaskTimer:Timer! = Timer()
Implementation:
func doBackgroundTask() {
DispatchQueue.global(qos: .default).async {
self.beginBackgroundTask()
if self.backgroundTaskTimer != nil {
self.backgroundTaskTimer.invalidate()
self.backgroundTaskTimer = nil
}
//Making the app to run in background forever by calling the API
self.backgroundTaskTimer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(self.startTracking), userInfo: nil, repeats: true)
RunLoop.current.add(self.backgroundTaskTimer, forMode: RunLoopMode.defaultRunLoopMode)
RunLoop.current.run()
// End the background task.
self.endBackgroundTask()
}
}
func beginBackgroundTask() {
self.backgroundUpdateTask = UIApplication.shared.beginBackgroundTask(withName: "Track trip", expirationHandler: {
self.endBackgroundTask()
})
}
func endBackgroundTask() {
UIApplication.shared.endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskInvalid
}
In ApplicationDidEnterBackground:
func applicationDidEnterBackground(_ application: UIApplication) {
//Start the background fetch
self.doBackgroundTask()
}
In ApplicationWillEnterForeground:
func applicationWillEnterForeground(_ application: UIApplication) {
if self.backgroundTaskTimer != nil {
self.backgroundTaskTimer.invalidate()
self.backgroundTaskTimer = nil
}
}
I am building an app where the user clicks a button and for 60mins (or any amount of time) we keep track of them by uploading their location to a server. Currently we are using 'Did Update Locations' function to send the users location to firebase in real-time.
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
}
This system works but it spams the server sending the location of the user to the server once every second.
This is too much data and we would only need to send the users location to the server once every 10-30 seconds.
What can we do send the users location once every 10-30 seconds?
class ViewController: UIViewController, CLLocationManagerDelegate {
private var locman = CLLocationManager()
private var startTime: Date? //An instance variable, will be used as a previous location time.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let loc = locations.last else { return }
let time = loc.timestamp
guard var startTime = startTime else {
self.startTime = time // Saving time of first location, so we could use it to compare later with second location time.
return //Returning from this function, as at this moment we don't have second location.
}
let elapsed = time.timeIntervalSince(startTime) // Calculating time interval between first and second (previously saved) locations timestamps.
if elapsed > 30 { //If time interval is more than 30 seconds
print("Upload updated location to server")
updateUser(location: loc) //user function which uploads user location or coordinate to server.
startTime = time //Changing our timestamp of previous location to timestamp of location we already uploaded.
}
}
import UIKit
import CoreLocation
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate,CLLocationManagerDelegate {
var window: UIWindow?
var locationManager = CLLocationManager()
var backgroundUpdateTask: UIBackgroundTaskIdentifier!
var bgtimer = Timer()
var latitude: Double = 0.0
var longitude: Double = 0.0
var current_time = NSDate().timeIntervalSince1970
var timer = Timer()
var f = 0
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
self.doBackgroundTask()
return true
}
func applicationWillResignActive(_ application: UIApplication) {
}
func applicationWillEnterForeground(_ application: UIApplication) {
print("Entering foreBackground")
}
func applicationDidBecomeActive(_ application: UIApplication) {
}
func applicationWillTerminate(_ application: UIApplication) {
}
func applicationDidEnterBackground(_ application: UIApplication) {
print("Entering Background")
// self.doBackgroundTask()
}
func doBackgroundTask() {
DispatchQueue.main.async {
self.beginBackgroundUpdateTask()
self.StartupdateLocation()
self.bgtimer = Timer.scheduledTimer(timeInterval:-1, target: self, selector: #selector(AppDelegate.bgtimer(_:)), userInfo: nil, repeats: true)
RunLoop.current.add(self.bgtimer, forMode: RunLoopMode.defaultRunLoopMode)
RunLoop.current.run()
self.endBackgroundUpdateTask()
}
}
func beginBackgroundUpdateTask() {
self.backgroundUpdateTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
self.endBackgroundUpdateTask()
})
}
func endBackgroundUpdateTask() {
UIApplication.shared.endBackgroundTask(self.backgroundUpdateTask)
self.backgroundUpdateTask = UIBackgroundTaskInvalid
}
func StartupdateLocation() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.requestAlwaysAuthorization()
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error while requesting new coordinates")
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let locValue:CLLocationCoordinate2D = manager.location!.coordinate
self.latitude = locValue.latitude
self.longitude = locValue.longitude
f+=1
print("New Coordinates: \(f) ")
print(self.latitude)
print(self.longitude)
}
#objc func bgtimer(_ timer:Timer!){
sleep(2)
/* if UIApplication.shared.applicationState == .active {
timer.invalidate()
}*/
self.updateLocation()
}
func updateLocation() {
self.locationManager.startUpdatingLocation()
self.locationManager.stopUpdatingLocation()
}}
I added the sleep function to delay of calling the location and send the information to server
Since this is running in both the app is active and goes to background. If you want only background process, remove or comment the function self.doBackgroundTask() from didFinishLaunchingWithOptions and remove the comment for self.doBackgroundTask() in the applicationdidEnterBackground. And then remove the comment in the function bgtimer(), since the background process has to stop once the app comes to active state.
Apps normally get suspended (no longer get CPU time) a moment after being moved to the background. You can ask for extra background time, but the system only gives you 3 minutes.
Only a very limited class of apps are allowed to run in the background for longer than that. Mapping/GPS applications are one of those categories. However, your app is not a mapping/GPS application, so I doubt if Apple would approve it.
Bottom line: I think you might be out of luck running your location queries for more than 3 minutes.
EDIT:
As Paulw11 points out, you can use the significant location change service to get location updates when the device moves by large distances.
I want to ;
Get User Location Every 5 minutes with background modes in Swift 3
But my codes don't any action. I need to get and send server ,longitude , latitude , and altitude values every 5 minutes
My codes under below.
AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
sleep(2)
BackgroundLocationManager.instance.start()
return true
}
BackgroundLocationManager - Class
import Foundation
import CoreLocation
import UIKit
class BackgroundLocationManager :NSObject, CLLocationManagerDelegate {
static let instance = BackgroundLocationManager()
static let BACKGROUND_TIMER = 15.0 // restart location manager every 150 seconds
static let UPDATE_SERVER_INTERVAL = 60 * 5 // 5 minutes server send
let locationManager = CLLocationManager()
var timer:Timer?
var currentBgTaskId : UIBackgroundTaskIdentifier?
var lastLocationDate : NSDate = NSDate()
private override init(){
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
locationManager.activityType = .other;
locationManager.distanceFilter = kCLDistanceFilterNone;
if #available(iOS 9, *){
locationManager.allowsBackgroundLocationUpdates = true
}
NotificationCenter.default.addObserver(self, selector: #selector(self.applicationEnterBackground), name: NSNotification.Name.UIApplicationDidEnterBackground, object: nil)
}
func applicationEnterBackground(){
// FileLogger.log("applicationEnterBackground")
start()
}
func start(){
if(CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedAlways){
if #available(iOS 9, *){
locationManager.requestLocation()
} else {
locationManager.startUpdatingLocation()
}
} else {
locationManager.requestAlwaysAuthorization()
}
}
func restart (){
timer?.invalidate()
timer = nil
start()
}
private func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case CLAuthorizationStatus.restricted: break
//log("Restricted Access to location")
case CLAuthorizationStatus.denied: break
//log("User denied access to location")
case CLAuthorizationStatus.notDetermined: break
//log("Status not determined")
default:
//log("startUpdatintLocation")
if #available(iOS 9, *){
locationManager.requestLocation()
} else {
locationManager.startUpdatingLocation()
}
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if(timer==nil){
// The locations array is sorted in chronologically ascending order, so the
// last element is the most recent
guard locations.last != nil else {return}
beginNewBackgroundTask()
locationManager.stopUpdatingLocation()
let now = NSDate()
if(isItTime(now: now)){
//TODO: Every n minutes do whatever you want with the new location. Like for example sendLocationToServer(location, now:now)
}
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error)
beginNewBackgroundTask()
locationManager.stopUpdatingLocation()
}
func isItTime(now:NSDate) -> Bool {
let timePast = now.timeIntervalSince(lastLocationDate as Date)
let intervalExceeded = Int(timePast) > BackgroundLocationManager.UPDATE_SERVER_INTERVAL
return intervalExceeded;
}
func sendLocationToServer(location:CLLocation, now:NSDate){
//TODO
}
func beginNewBackgroundTask(){
var previousTaskId = currentBgTaskId;
currentBgTaskId = UIApplication.shared.beginBackgroundTask(expirationHandler: {
// FileLogger.log("task expired: ")
})
if let taskId = previousTaskId{
UIApplication.shared.endBackgroundTask(taskId)
previousTaskId = UIBackgroundTaskInvalid
}
timer = Timer.scheduledTimer(timeInterval: BackgroundLocationManager.BACKGROUND_TIMER, target: self, selector: #selector(self.restart),userInfo: nil, repeats: false)
}
}
I have the following in my touchesBegan function in my GameScene so that the timer only starts when the user touches a certain sprite:
let start = SKAction.runBlock({
NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: "printDuration:", userInfo: NSDate(), repeats: true)
})
tapToStartNode.runAction(start)
and the following function:
func printDuration(timer: NSTimer) {
if self.view?.paused == false {
guard let userInfo = timer.userInfo else {
return
}
guard let startDate = userInfo as? NSDate else {
return
}
let duration = NSDate().timeIntervalSinceDate(startDate)
currentTime = NSDate().timeIntervalSinceDate(startDate)
currentTimeValueLabel.text = "\(NSString(format:"%3.2f", duration))"
}
}
However, when applicationDidEnterBackground or applicationWillResignActive the timer is not paused and continues to run when the app is in the backgorund. How do I make it stop?
Here a tips you can use for your problem
You can use func applicationDidEnterBackground(_ application: UIApplication) or func applicationWillResignActive(_ application: UIApplication) on AppDelegate
Access your controller by initialize rootViewController example :
let vc = self.window?.rootViewController as? ViewController
Then you can access your function (make sure function is on public state)
vc.pauseTimer() (customize based on your situation)
Hope it will be helpful