I’m using CLLocationManager, looking at the location property and examining the level of the floor, if any. The documentation suggests that if it couldn’t determine the floor, that it would just return nil. In practice, I am getting a CLFloor instance, but its level is 2146959360. Converting that to hex, 0x7FF80000, which looks suspiciously like some cryptic sentinel value.
lazy var locationManager: CLLocationManager = {
let locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
return locationManager
}()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
switch CLLocationManager.authorizationStatus() {
case .notDetermined: locationManager.requestWhenInUseAuthorization()
case .denied: redirectToSettings()
default: break
}
}
#IBAction func didTapGetLocation(_ sender: Any) {
updateLabels(for: locationManager.location)
}
func updateLabels(for location: CLLocation?) {
guard let location = location else {
floorLabel.text = "No location."
return
}
if let floor = location.floor {
let hexString = "0x" + String(format: "%08x", floor.level)
floorLabel.text = "\(floor.level) (\(hexString))"
} else {
floorLabel.text = "No floor."
}
}
I’m seeing this behavior in on a physical iOS 13.3.1 devices, only. FWIW, older iOS versions (I’ve only got iOS 10 device sitting here) appear to return nil, as expected, as does the simulator.
What’s going on?
This problem goes away if you call startUpdatingLocation. If you do that, then the floor property will be nil. This CLFloor instance with a level value of 2146959360 (0x7FF80000) only appears if you query the location of the CLLocationManager without having first called startUpdatingLocation.
The documentation suggests that this location property is populated with the last known value. Regardless, the floor should be nil (for my particular location, at least) but isn’t. The level is invalid.
See this repo for example of manifestation of the bug and demonstration of how calling startUpdatingLocation works.
I’ve filed a bug report (FB7638281).
Related
The app pertinent to this question is meant to track user location with a high degree of precision, including GPS coordinates and accelerometer readings, for a time period of 30 minutes, even if the user has pressed the sleep button.
To this ends, the plist file and app capabilities settings have been changed to reflect the reason for always on navigation access and to enable background processes for the provision of location based services.
The app does ask the user for GPS permissions when it is run, and if they are granted, the activation of this view controller (the view controller that contains the following code) does cause the GPS/Navigation icon to display on an iPhone.
The problem is, so far none of the four "print" commands seen below result in any printed messages, and so the "newLocation" and "myLocation" variables both do not yield any data. If this code is remotely close to being able to serve the purpose outlined in the first sentence, then the question is "How can it be fixed?". If this is a bad way to accomplish the goal, then a better answer would explain how this should be done.
import UIKit
import CoreMotion
import MapKit
import CoreLocation
class ActiveSession: UIViewController, CLLocationManagerDelegate {
lazy var locationManager: CLLocationManager! = {
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.delegate = self
return manager
}()
func locationManager(_manager: CLLocationManager!, didUpdateToLocation newLocation: CLLocation!, fromLocation oldLocation: CLLocation!) {
let myLocation:CLLocationCoordinate2D=CLLocationCoordinate2DMake(oldLocation.coordinate.latitude, oldLocation.coordinate.longitude)
print(myLocation)
if UIApplication.shared.applicationState == .active {
print("at least it's active at all")
} else {
print(newLocation)
print("it's active when the app isn't")
}
}
func getPermission () {
locationManager = CLLocationManager()
switch CLLocationManager.authorizationStatus() {
case .denied, .restricted:
return
case .notDetermined:
locationManager!.requestAlwaysAuthorization()
break
case .authorizedAlways, .authorizedWhenInUse:
break
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.getPermission()
locationManager = CLLocationManager()
locationManager!.desiredAccuracy = kCLLocationAccuracyBest
locationManager?.startUpdatingLocation()
}
}
You’re creating new CLLocationManager instances in three places—the lazy locationManager initializer, getPermission, and viewDidLoad—and in the latter two of those, you’re neither setting the desired accuracy nor the delegate. Delete the locationManager = CLLocationManager() lines and you should have better results.
As ohr pointed out, an additional permission was required. The following line was added to the lazy locationManager initializer to rectify this omission:
manager.allowsBackgroundLocationUpdates = true
In accordance with Noah and Paulw11's advice, the getPermission function was deleted, and the line:
locationManager = CLLocationManager()
was removed from viewDidLoad, as there was a great deal of redundancy present. This alone did not fix the problems, but adding logic to the locationManager func so that recent map data was stored to an array, like so:
let annotation = MKPointAnnotation()
annotation.coordinate = newLocation.coordinate
locations.append(annotation)
while locations.count > 100 {
let annotationToRemove = locations.first!
locations.remove(at: 0)
}
resulted in working code. This solution was derived from code found here: https://www.raywenderlich.com/92428/background-modes-ios-swift-tutorial
(Thanks, Ray)
Ultimately the code functioned in the foreground and background without removing anything from the class or implementing anything in appdelegate.
I am developing a code that should get my location every 10 minutes and salvation in CoreData. When I walk into background with conectavo app to xcode can not see the log that the service is running, but when I go out walking the streets he simply not saved or saved too few times.
This is part of my code that should do this function.See save in codeData:
var saveLocationInterval = 60.0
func applicationDidEnterBackground(application: UIApplication) {
UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler(nil)
self.timer = NSTimer.scheduledTimerWithTimeInterval(saveLocationInterval, target: self, selector: #selector(AppDelegate.saveLocation), userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(self.timer, forMode: NSRunLoopCommonModes)
locationController.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
saveLocation()
}
func saveLocation(){
print("======")
let logUser = NSEntityDescription.insertNewObjectForEntityForName("LOG_GPS", inManagedObjectContext: self.managedObjectContext) as! LOG_GPS
if locationController.location == nil{
logUser.latitude = ""
logUser.longitude = ""
} else {
logUser.latitude = "\(locationController.location!.coordinate.latitude)"
logUser.longitude = "\(locationController.location!.coordinate.longitude)"
}
logUser.velocidade = userSpeed > 0 ? Int(userSpeed) : 0
logUser.address = "\(userSpeed)"
if _usuario.chave != nil {
logUser.chave_usuario = "\(_usuario.chave!)"
}
if _empresa.chave != nil {
logUser.chave_licenca = "\(_empresa.chave!)"
}
print("localizaçao salva no bd \(logUser.latitude)")
let date = NSDate()
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "dd/MM/yy HH:mm:ss"
let dateString = dateFormatter.stringFromDate(date)
logUser.data = dateString
do {
try self.managedObjectContext.save()
} catch {
}
}
Another major error in my code I can not solve is the User's speed. In the method the low I'm trying to save your speed in a variable and then save the CoreData however this is me always returning a negative value:
func locationManager(manager: CLLocationManager, didUpdateToLocation newLocation: CLLocation, fromLocation oldLocation: CLLocation) {
var speed: CLLocationSpeed = CLLocationSpeed()
speed = newLocation.speed
print(speed * 3.6)
userSpeed = speed * 3.6
}
this is my background mode
Possible solution 1
You need a key in your Info.plist that describes why your app needs background location.
Go to your Info.plist, find the Bundle Version key and click the + that appears when you hover over that. Then add the key NSLocationAlwaysUsageDescription, set it to be a string, and set the value as whatever you want the description to be, like "We need your location in the background so we share your location with friends."
Now your app should work. If it doesn't....
Possible solution 2 (more likely solution if you know what you're doing)
With iOS 9, Apple made it so that apps on physical devices need a special line of code to run location services in the background. The change was not widely reported on (if at all?) but I managed to figure this one out a while ago. Here's what you need to do to get location services working in the background on physical devices again:
In your main location tracking view controller's ViewDidLoad put...
if #available(iOS 9.0, *) {
locationManager.allowsBackgroundLocationUpdates = true
} else {
// You don't need anything else on earlier versions.
}
This will (mysteriously enough) likely be all you need to solve your problem.
I have a locationManager function to grab the users current location and posting the name of the city and state. I have a print statement so I can check in my console if everything is working properly...and it is. However, it prints the city location 3 times. This actually causes an issue in my actual app but thats beyond the point of this question.
My function is as follows:
var usersLocation: String!
var locationManager: CLLocationManager!
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation: CLLocation = locations[0]
CLGeocoder().reverseGeocodeLocation(userLocation) { (placemarks, error) -> Void in
if error != nil {
print(error)
} else {
let p = placemarks?.first // ".first" returns the first element in the collection, or nil if its empty
// this code above will equal the first element in the placemarks array
let city = p?.locality != nil ? p?.locality : ""
let state = p?.administrativeArea != nil ? p?.administrativeArea : ""
self.navigationBar.title = ("\(city!), \(state!)")
self.usersLocation = ("\(city!), \(state!)")
self.locationManager.stopUpdatingLocation()
print(self.usersLocation)
self.refreshPosts()
}
}
}
So in my print(self.usersLocation) it will print in my console three times. Is this normal?
UPDATE TO SHOW VIEWDIDLOAD
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 250.0
}
I'd first suggest a few things:
Call stopUpdatingLocation before you perform reverseGeocodeLocation.
You are calling stopUpdatingLocation inside the reverseGeocodeLocation completion handler closure. The problem is that this runs asynchronously, and thus didUpdateLocations may receive additional location updates in the intervening period. And often, when you first start location services, you'll get a number of updates, often with increasing accuracy (e.g. horizontalAccuracy values that are smaller and smaller). If you turn off location services before initiating asynchronous geocode request, you'll minimize this issue.
You can also add add a distanceFilter in viewDidLoad, which will minimize redundant calls to the delegate method:
locationManager.distanceFilter = 1000
You can use your own state variable that checks to see if the reverse geocode process has been initiated. For example:
private var didPerformGeocode = false
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// if we don't have a valid location, exit
guard let location = locations.first where location.horizontalAccuracy >= 0 else { return }
// or if we have already searched, return
guard !didPerformGeocode else { return }
// otherwise, update state variable, stop location services and start geocode
didPerformGeocode = true
locationManager.stopUpdatingLocation()
CLGeocoder().reverseGeocodeLocation(location) { placemarks, error in
let placemark = placemarks?.first
// if there's an error or no placemark, then exit
guard error == nil && placemark != nil else {
print(error)
return
}
let city = placemark?.locality ?? ""
let state = placemark?.administrativeArea ?? ""
self.navigationBar.title = ("\(city), \(state)")
self.usersLocation = ("\(city), \(state)")
print(self.usersLocation)
self.refreshPosts()
}
}
I had the same problem and Rob's answer didn't do it for me.
When the location service first starts, the location is updated multiple times regardless of the distanceFilter.
You might still want the location to be updated and you don't want to lose the location accuracy(which is the whole point of updating location multiple times on start-up), so calling stopUpdatingLocation(or using a local variable) after the first geolocating call isn't the way to go either.
The most intuitive way is to wrap your geocode call in an #objc function and call the the function with a delay:
NSObject.cancelPreviousPerformRequests(withTarget: self)
perform(#selector(myGeocodeFunction(_:)), with: location, afterDelay: 0.5)
I am developing a location based app which is supposed to fetch user location always.Im using standard location service. But the problem is that the app after keeping idle for some time in background will not fetch the coordinates even after we move to some other locations. As per apple documentation, when a new location arrives, app should wake up automatically, but that is not happening here. I'm sharing the code and using to fetch location and screenshot of my plist.
class SALocation: NSObject,CLLocationManagerDelegate
{
static let sharedInstance : SALocation = SALocation()
var locationManager : CLLocationManager!
var location : CLLocation!
var address : String!
var latitude : NSString?
var longitude : NSString?
var isAdderssLoaded : Bool = false
var locdictionary : NSMutableDictionary = NSMutableDictionary()
func startLocationManager()
{
if self.locationManager == nil
{
self.locationManager = CLLocationManager()
if CLLocationManager.locationServicesEnabled(){
print("location service enabled")
}
self.locationManager.delegate = self
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.distanceFilter = kCLDistanceFilterNone
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
if ( Float (UIDevice.currentDevice().systemVersion) >= 9) {
if #available(iOS 9.0, *) {
self.locationManager.allowsBackgroundLocationUpdates = true
} else {
// Fallback on earlier versions
};
}
self.locationManager.startUpdatingLocation()
//self.locationManager.stopMonitoringSignificantLocationChanges()
}
else
{
self.locationManager.startUpdatingLocation()
}
}
// MARK: CLLocationManagerDelegate
func locationManager(manager: CLLocationManager, didFailWithError error: NSError)
{
UIAlertView(title:"Alert", message:error.description, delegate: nil, cancelButtonTitle:nil, otherButtonTitles:"Ok").show()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
if locations.count > 0
{
self.location = locations[0]
/* storing date and location to plist
*/
let datenow = NSDate()
let dateformatternow = NSDateFormatter ()
dateformatternow.dateFormat = "yyyyMMdd HH:mm:ss"
let timenow:NSString = dateformatternow.stringFromDate(datenow)
let documetsdirectorypath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true).last
latitude = NSString(format: "%f",self.location.coordinate.latitude)
longitude = NSString (format: "%f",self.location.coordinate.longitude)
let latlong : NSString = NSString(format:"%#~%#",latitude!,longitude!)
NSUserDefaults.standardUserDefaults().setObject(latlong, forKey: "latlong")
let aFilePath = NSString(format: "%#/location.plist",documetsdirectorypath!)
locdictionary.setObject(latlong, forKey: timenow as String)
locdictionary.writeToFile(aFilePath as String, atomically: true)
///////////// ||storing date and location to plist code ends here||\\\\\\
// self.getAddressFromLocation(locations[0] )
// if (NSUserDefaults.standardUserDefaults().objectForKey(SettingAppRefresh) != nil)
// {
// if (NSUserDefaults.standardUserDefaults().objectForKey(SettingAppRefresh) as! NSString).isEqualToString(FalseString)
// {
// // self.locationManager.stopUpdatingLocation()
// }
// }
}
}
}
What i'm doing here is just get location and write it to a plist file. This works in foreground, background etc fine. But when i keep the app idle for 20 minutes, location is not fetched even if i move to some other locations as the app is suspended
Capabilities tab looks like this
To start location in background you must start background service from the following path
Click on your name -> Click on your app name (target) -> goto capabilities -> find the background mode -> enable the location update mode
I am not sure you started that or not because you not put any screenshot about this.
And also check that your user started background refresh in settings.refer below link for this.
Background App Refresh checking, enabling and disabling programatically for whole device and for each particular application in iOS 7
Update::
For location update in background used below link(objective c)
http://www.creativeworkline.com/2014/12/core-location-manager-ios-8-fetching-location-background/
Well, I don't know how you're getting location updates - significant-location change as example and how you exit from background.
I suggest checking if your app is truly in background mode - UIApplication.sharedApplication().applicationState as it can be terminated.
And I also suggest checking out Apple's Execution States for Apps. - especially for your possible use case Implementing Long-Running Tasks part. There is also a good tutorial at rayywenderlich.com called Background modes.
Please use
self.locationManager.requestAlwaysAuthorization()
and don't forget to update your Info.plist to define the NSLocationAlwaysUsageDescription key.
my viewController looks like following
import CoreLocation
class MyViewController: UIViewController {
let locationManager = CLLocationManager()
override func viewDidAppear(animated: Bool) {
if #available(iOS 8.0, *) {
self.locationManager.requestWhenInUseAuthorization()
} else {
// Fallback on earlier versions
}
if (CLLocationManager.locationServicesEnabled()) {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
let lon = locationManager.location!.coordinate.longitude
let lat = locationManager.location!.coordinate.latitude
print("lat = \(lat) and long = \(lon)")
}
}
}
// MARK: - CLLocationManagerDelegate
extension MyViewController : CLLocationManagerDelegate {
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
self.locationManager.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("\(error.localizedDescription)")
}
}
When I execute my program, it prompts a message like
Allow application to access your location while you use the app?
But the Don't Allow and Allow buttons are disabled.
Can someone guide me on where I am going wrong and what I should be doing.
I can manually allow my application to access location services by going to settings. But I would like to know why the buttons are disabled and what should I do to enable it.
note: I have added NSLocationWhenInUseUsageDescription to my info.plist file.
Thanks.
Dt: 29Oct2015
EDIT:
Uninstalled the app and installed again and tried. Now I am able to access the buttons.
Also, I noticed that sometimes the screen goes unresponsive i.e., screen cannot take any input from user. I noticed it today with a textbox. I am not able to get the cursor to the text box.
Is it something to do with IOS update? is anyone else experiencing this type of weird behaviour. Is there any workaround for the same?
Any help is highly appreciated.
Thanks.