I'm having some issues with CLLocationManager. This code used to work on iOS 8.2 but since upgrading to 8.3 it doesn't work. Here is the code for setting up the location manager which is called on startup.
let distanceThreshold:CLLocationDistance = 100.0
var currentLocation:CLLocationCoordinate2D?
override init() {
assert(locMan == nil)
super.init()
locMan = self
let locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
currentLocation = manager.location.coordinate
if UIApplication.sharedApplication().applicationState == .Background {
PlacesManager.fetchNearbyPlaces(LocationManager.getLocationManager().currentLocation!, radius: distanceThreshold, callback: placesCallback)
}
}
With this code didUpdateLocations is never called despite it being called before.
I have added the relevant entries to the Info.plist file:
I have tried it on both a device and the simulator and neither works. In fact it seems that it is no longer requesting location authorisation anymore if I delete the app and reinstall.
I know I'm missing something stupid but I can't workout what the hell it is.
I'd appreciate any help people can provide.
Cheers,
Gerard
Answer from Anna:
The CLLocationManager documentation says: "To configure and use a CLLocationManager object to deliver events...Create an instance of the CLLocationManager class and store a strong reference to it somewhere in your app. Keeping a strong reference to the location manager object is required until all tasks involving that object are complete. Because most location manager tasks run asynchronously, storing your location manager in a local variable is insufficient."
In addition to the accepted answer, if you are using the simulator. You have to select a location after the app is running (Debug->Location->apple for apple headquarters). I set the location and assumed that the next time I ran the app that didUpdateLocations would be called with what I had set previously, but that assumption was wrong.
Related
I have two constraints in my app, one is user location and the other one is time. Following is the simple location implementation.
func determineMyCurrentLocation() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.allowsBackgroundLocationUpdates = true
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.requestAlwaysAuthorization()
locationManager.distanceFilter = 20
if CLLocationManager.locationServicesEnabled() {
locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("*******Location updated*******")
}
With this code, I expect to get triggered every time the user location changes by 20m (in background as well). However, I also need to track the time the user stays in a particular location. Since i need to track the background case, I cannot use timer.
I followed https://www.raywenderlich.com/143128/background-modes-tutorial-getting-started to try background task but as stated in the article, the background time allowed that I got was around 3 min (which is variable). So i believe I cannot go with this.
How can I solve this?
EDIT: I also need to make an api call if the user stays in a location for X minutes. So for that case, it would not be feasible to wait for location updates and calculate the time differences. I could previously solve this by removing the distance filter and continuously checking the location and comparing the time and location. I guess continuous location tracking will get the app rejected, that's why I went for filter. But i am not sure if it will still get rejected or not since iOS will need to track the location for filter as well.
You can use simple Date objects to track the time spent between location updates even if your app is in the background. Simply declare a Date property for your class, update its value from func locationManager(_:, didUpdateLocations:) and compare that to the current time.
// Declare this in a scope that can be accessed from `didUpdateLocations` and where its value won't be released from memory
var lastUpdateTimestamp = Date()
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let now = Date()
let timeSinceLastUpdate = now.timeIntervalSince(lastUpdateTimestamp)
print("\(timeSinceLastUpdate) seconds passed since last location update")
lastUpdateTimestamp = now
}
Response to the question edit: making an API call after X minutes have passed in the background without a location update is not possible, since there's no supported background mode for executing arbitrary functions at specific points in time. Getting rid of the distance filter as you explained in your edit could be a valid alternative, however if your app is only using location updates to be able to make the API calls and isn't actually doing anything with those locations, your app might indeed get rejected.
When you receive a new location update, check the time difference from the previous update.. giving you the time that they were at the last location.
Depending on what you are doing with these location updates, this might just be pulling the latest update from some DB, or posting to an API.
An example..
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("*******Location updated*******")
let lastUpdateDate = // get the last datetime you received information
let currentDate = Date()
let minutesDifference = let componentsLeftTime = Calendar.current.dateComponents([.minute], from: lastUpdateDate, to: currentDate)
}
I try to make a running app, and I want to use the method startMonitoringSignificantLocationChanges, but I'm missing something because is not firing the didUpdateLocations that the documentation says it should.
I have successfully used the startUpdatingLocation method of the CLLocationManager class and I can totally can use it for my interests, but I'd like to understand why my implementation doesn't work.
So, I have this code:
let locationManager = CLLocationManager()
func determineMyCurrentLocation() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.allowsBackgroundLocationUpdates = true
print("authorization Status: \(CLLocationManager.authorizationStatus().rawValue)")
if CLLocationManager.locationServicesEnabled() {
locationManager.startUpdatingLocation() //(1)
//(2)locationManager.startMonitoringSignificantLocationChanges()
locationManager.distanceFilter = 20
//locationManager.start
//locationManager.startUpdatingHeading)(
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { print("yaaay!") }
If you comment (1) and uncomment 2 the func locationManager...etc is never fired.
Also, I have my info.plist with the proper keys set, "Privacy - Location When In Use Usage Description" and "Privacy - Location Always and When In Use Usage Description").
Any idea?
Thanks in advance.
EDITED: My question was imprecise. I put the locationManager property as a class member and I completed the properties in my info.plist
The startMonitoringSignificantLocationChanges requires always authorisation. That also means you should turn on "Background Modes" in Capabilities section and selection "Location Updates". You also require the Privacy - Location Always Usage Description Try these two and you should receive significant location change updates.
How do I get the current location of my iOS device?
It seems that Apple has made changes that to how get location and I could not find an up-to-date post. I answered this question myself below:
I couldn't find an up-to-date solution to getting the current location so here's how I did it.
My source is apple's sample code on location tracking and smart watch called PotLocCoreLocationwithiPhoneandAppleWatch: https://github.com/robovm/apple-ios-samples/tree/master/PotLocCoreLocationwithiPhoneandAppleWatch
Here's what you have to do in your app.
Note that everything from steps 2-6 are in this gist: https://gist.github.com/JonMercer/75b3f45cda16ee5d1e0955b2492f66dd
In the project settings in xcode, click on the capabilities tab (it's the tab beside the general tab where you put in your bundle identifier). Then turn on Background Mode and enable Location updates. EDIT thanks to #Rob: And also add NSLocationWhenInUseUsageDescription to your plist. The value can be anything you like
Inside your view controller, extend the CLLocationManagerDelegate
class YourClass: UIViewController, CLLocationManagerDelegate {
Implement these two functions
/**
Increases that location count by the number of locations received by the
manager. Updates the batch count with the added locations.
*/
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
//use the "locations" variable here
}
/// Log any errors to the console.
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("Error occured: \(error.localizedDescription).")
}
Create a let manager = CLLocationManager() variable
On viewDidLoad() set the following: manager.requestWhenInUseAuthorization(), manager.allowsBackgroundLocationUpdates = true, and manager.startUpdatingLocation()
In order to stop tracking do the following: manager.stopUpdatingLocation() and manager.allowsBackgroundLocationUpdates = false
Here is my code from a ViewController implementing CLLocationManagerDelegate:
func startLocationManager() {
let locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
println("I'm called")
locationManager.requestAlwaysAuthorization()
// locationManager.startMonitoringSignificantLocationChanges()
locationManager.startUpdatingLocation()
let status = CLLocationManager.authorizationStatus()
println(status.rawValue) // This print 0 which stands for kCLAuthorizationStatusNotDetermined
println(CLLocationManager.locationServicesEnabled()) // true
}
func locationManager(manager: CLLocationManager!,
didUpdateLocations locations: [AnyObject]!) {
println("nobody call me ever, and I'm sad")
}
For some reason, I never get the prompt / alter to autorise location updates. I have tried on my device iOS 8.1 and the simulartor. I followed the advices found here: requestAlwaysAuthorization not showing permission alert :
"Add Core Location framework to Project Settings / Targets / Capabilities / Background Modes set "Location Updates" and "Uses Bluetooth LE Accessories" Add key at Info.plist NSLocationAlwaysUsageDescription".
I have also tried to clean up and rebuild, nothing change. I feel clueless.
EDIT: This question seems related: iOS: App is not asking user's permission while installing the app. getting kCLAuthorizationStatusNotDetermined every time - Objective-c & Swift but the selected answer and its article doesn't expose anything new
Your CLLocationManager object is local object and thus will be deallocated immediately after it falls out of scope. Make it a class property and then asynchronous processes like requesting authorization and determining the location will have a chance to run.
I was testing out CoreLocation to learn how to capture and record the location of the user. I built a simple Master-Detail app which, in the detail pane, showed the user's live location. Everything worked as expected.
Next, I moved on to making my real app which also uses CoreLocation. I built the app with a Master-Detail style and also made it so that when the user opens the detail pane, it should show their current, live, location. However, nothing happens at all.
What I determined after a LOT of debugging and research is that as soon as my locationManager is created and I call locationManager.startUpdatingLocation() the authorization to get the location is changed to denied (or false, or whatever its called). The only way I can get live tracking is build and run the app in Xcode, once it opens in the simulator, open up Location Services and change the app setting to allow location tracking to "Always". Then I can see in the console that locations are being acquired.
I don't understand why I have to keep changing the authorization to "Always" over and over. I have deleted the app on the simulator, done a "clean" from XCode to start fresh, and still it starts on the simulator without authorization.
Anyone have ideas?
UPDATE Now I see that, when I build and it runs in the simulator, I get ONE location, then the authorization is changed to not allow location services.
Here's my code (which worked on another app):
import UIKit
import CoreLocation
class DetailViewController: UIViewController, CLLocationManagerDelegate {
var locationManager : CLLocationManager!
func startTrackingLocation() {
println("tracking engaged")
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization() // <-- this was originally commented out
locationManager.startUpdatingLocation()
println("and we're tracking")
println(locationManager.location)
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
println("location acquired") // <--- Only see this when I manually allow location when app is running
self.newLongLabel?.text = "\(locations[0].coordinate.longitude)"
self.newLatLabel?.text = "\(locations[0].coordinate.latitude)"
}
override func viewDidLoad() {
super.viewDidLoad()
self.startTrackingLocation()
self.configureView()
}
// more class stuff...
If you're certain you have added the key to plist file try checking for it in the didChangeAuthorizationStatus: location manager delegate method.
Be sure you have both keys in plist as type string and with the values you would like to display to the user:
NSLocationAlwaysUsageDescription
NSLocationWhenInUseUsageDescription
- (void) locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status{
if (status == kCLAuthorizationStatusAuthorizedWhenInUse || status == kCLAuthorizationStatusAuthorizedAlways){
if ([CLLocationManager locationServicesEnabled]) {
[yourLocationManager startMonitoringSignificantLocationChanges];
//run your code here
}
}
}