I want to upload a user's location to a server to send them a location-sensitive alert, so the location needs to be fairly accurate:
import Foundation
import CoreLocation
protocol LocationServiceDelegate {
func tracingLocation(currentLocation: CLLocation)
func tracingLocationDidFailWithError(error: NSError)
}
class LocationService: NSObject, CLLocationManagerDelegate {
class var sharedInstance: LocationService {
struct Static {
static var onceToken: dispatch_once_t = 0
static var instance: LocationService? = nil
}
dispatch_once(&Static.onceToken) {
Static.instance = LocationService()
}
return Static.instance!
}
var locationManager: CLLocationManager?
var lastLocation: CLLocation?
var delegate: LocationServiceDelegate?
override init() {
super.init()
self.locationManager = CLLocationManager()
guard let locationManager = self.locationManager else {
return
}
if #available(iOS 9.0, *) {
locationManager.allowsBackgroundLocationUpdates = true
} else {
// Fallback on earlier versions
};
if CLLocationManager.authorizationStatus() == .NotDetermined {
// you have 2 choice
// 1. requestAlwaysAuthorization
// 2. requestWhenInUseAuthorization
locationManager.requestAlwaysAuthorization()
}
locationManager.desiredAccuracy = kCLLocationAccuracyBest // The accuracy of the location data
locationManager.distanceFilter = 2000 // The minimum distance (measured in meters) a device must move horizontally before an update event is generated.
locationManager.delegate = self
}
func startUpdatingLocation() {
print("Starting Location Updates")
self.locationManager?.startUpdatingLocation()
}
func stopUpdatingLocation() {
print("Stop Location Updates")
self.locationManager?.stopUpdatingLocation()
}
// CLLocationManagerDelegate
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
return
}
// Check if Application is active
// Check Current View Controller
// Refresh Weather
// singleton for get last location
self.lastLocation = location
// use for real time update location
updateLocation(location)
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
// do on error
updateLocationDidFailWithError(error)
}
// Private function
private func updateLocation(currentLocation: CLLocation){
let todaysDate:NSDate = NSDate()
let dateFormatter:NSDateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy HH:mm"
let DateInFormat:String = dateFormatter.stringFromDate(todaysDate)
print(DateInFormat)
print(currentLocation.coordinate.latitude.description + ", " + currentLocation.coordinate.longitude.description)
guard let delegate = self.delegate else {
return
}
delegate.tracingLocation(currentLocation)
}
private func updateLocationDidFailWithError(error: NSError) {
guard let delegate = self.delegate else {
return
}
delegate.tracingLocationDidFailWithError(error)
}
}
This code does work, although I'm not sure how well the location accuracy and update works. Please review this code.
2 Answers 2
LocationManager Singleton
It is not necessary to use dispatch_once
to make LocationService a Singleton.
This pattern can be achieved by doing the following:
- Exposing a
static
type property that creates a new instance ofLocationService
- Marking the initializer of the class as
private
For example:
class LocationService: NSObject, CLLocationManagerDelegate {
static let shared = LocationSerivce()
// This class cannot be instantiated outside of this class
override private init() {
// Initialization code
}
}
shared
is lazily instantiated. A performance benefit that you inherit by implementing the Singleton this way.
From Apple’s documentation on Type Properties:
"Stored type properties are lazily initialized on their first access. They are guaranteed to be initialized only once, even when accessed by multiple threads simultaneously, and they do not need to be marked with the lazy modifier."
The private
initializer is key to the implementation of the Singleton pattern because it prevents anyone from outside of the class from creating another instance. That said, under other circumstances you may still want instances to be created to give your consumer more choices/functionality.
Ultimately these changes make your code easier to read, ensure you have Singleton and are inline with Apple’s documented approach to Singletons using Swift.
Private CLLocationManager
Your LocationSerivce
appears to be a wrapper around Apple’s CLLocationManager
class. It would probably be best to mark this constant as private. This prevents outside code from changing the reference or state of your locationManager
property.
private let locationManager = CLLocationManager()
Self
In the following functions, the use of self
is not required. The reference to locationManager
is not ambiguous and self
is not required to explicitly state otherwise.
func startUpdatingLocation() {
print("Starting Location Updates")
self.locationManager.startUpdatingLocation()
}
func stopUpdatingLocation() {
print("Stop Location Updates")
self.locationManager.stopUpdatingLocation()
}
CLLocationManager Desired Accuracy
You can remove the following code from your initializer:
locationManager.desiredAccuracy = kCLLocationAccuracyBest
On iOS, kCLLocationAccuracyBest
is the default value of the desiredAccuracy
property.
Accuracy Constants
In terms of accuracy, you have six options:
kCLLocationAccuracyBestForNavigation
kCLLocationAccuracyBest
kCLLocationAccuracyNearestTenMeters
kCLLocationAccuracyHundredMeters
kCLLocationAccuracyAccuracyKilometer
kCLLocationAccuracyThreeKilometers
At first glance one may think to use kCLLocationAccuracyBestForNavigation
, however because increasing the accuracy requires more device resources (like GPS or Cellular Radio), it also means that more device power will be required. This can have a negative impact on the user experience if expectations are not set. There are also some other caveats that should be understood when setting the desiredAccuracy
property.
Apple’s desiredAccuracy
documentation: https://developer.apple.com/reference/corelocation/cllocationmanager/1423836-desiredaccuracy
When deciding which constant to use, careful consideration will be required based on the app’s intended use and the user’s expectations (and delivering a great mobile experience). I recommend reading the CLLocationManager
documentation to better understand its usage and how to strike a good balance between your app’s functionality and device performance.
Apple’s CLLocationManager
documentation: https://developer.apple.com/reference/corelocation/cllocationmanager
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.
Explore related questions
See similar questions with these tags.