CLAuthorizationStatus AuthorizedWhenInUse called only for the first time - swift

Here's a problem if I don't request "AuthorizedWhenInUse" status once my app first view controller is loaded I'll never get update after.
Let's say I have a map view controller. When I ask for status in the viewDidLoad method it updates my location, i.e. func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) is called.
Now I added an intro view controller, once user finishes intro he/she is going to the old map controller. But now func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) isn't called anymore!
What I noticed is if I go to settings and toggle authorization status manually for my application and then get back to my app the didUpdateLocations is called.
class MapViewController: UIViewController, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.distanceFilter = 100
locationManager.delegate = self
// >=iOS8
if (locationManager.respondsToSelector(Selector("requestWhenInUseAuthorization"))) {
locationManager.requestWhenInUseAuthorization()
} else {
locationManager.startUpdatingLocation()
}
}
// MARK: - CLLocationManagerDelegate
func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if status == .AuthorizedAlways || status == .AuthorizedWhenInUse {
manager.startUpdatingLocation()
}
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
if let location = locations.first as? CLLocation {
println("User's location: \(location.description)")
} else {
println("User's location is unknown")
}
}
}

I see several issues with what you posted, here are just two:
You are calling locationManager.startUpdatingLocation() from viewDidLoad this will only fire if the view is not already loaded into memory. Recommend moving this into viewWillAppear so it fires every time.
This code should be rewritten:
if (locationManager.respondsToSelector(Selector("requestWhenInUseAuthorization"))) {
locationManager.requestWhenInUseAuthorization()
} else {
locationManager.startUpdatingLocation()
}
You need to check the authorizationStatus instead, if it is kCLAuthorizationStatusNotDetermined then request permission using the if statement you have above. If you don't iOS 8 users will always drop into the requestWhenInUseAuthorization section. You don't want that because the OS will only ask for permission once. It will not ask again unless you rest your phones Location Privacy under Settings.

Related

CLLocationManager requestLocation not calling didUpdateLocations

I have a simple CLLocationManager implementation that works in one project but not in my new project.
The code is almost identical but I cannot get the .didUpdateLocations function to call. My code is below. Any ideas why I cannot get the update to work? I'm at a loss, I've build many apps using location services and never seen this situation.
Also I have the three settings in the PLIST set correctly for Privacy-Location Always etc.
There are no errors given, it simply doesn't call .didUpdateLocations
Weather Class
class DarkSkyWeatherController: UIViewController, CLLocationManagerDelegate {
var weatherGetterDelegate: DarkSkyWeatherControllerDelegate?
var locationManager = CLLocationManager()
var lat = String()
var long = String()
func getLocation() {
// Ask for Authorisation from the User.
locationManager.requestAlwaysAuthorization()
// For use in foreground
locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
locationManager.startUpdatingLocation()
}
locationManager.delegate = self
locationManager.requestLocation()
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let locValue: CLLocationCoordinate2D = manager.location?.coordinate else {return}
print("locations = \(locValue.latitude) \(locValue.longitude)")
lat = String(locValue.latitude)
long = String(locValue.longitude)
getDarkSkyWeather { (fetchedInfo) in
if let myFetchedInfo = fetchedInfo {
self.weatherGetterDelegate?.getMyWeather(weather: myFetchedInfo)
}
}
}
ViewDidLoad in main window
let weather = DarkSkyWeatherController()
weather.weatherGetterDelegate = self
weather.getLocation()
Thanks for looking at this.
Without seeing your full main window code, I bet that the problem is with the scope and lifecycle of your controller:
override func viewDidLoad() {
let weather = DarkSkyWeatherController()
weather.weatherGetterDelegate = self
weather.getLocation()
// Function exits. The weather constant dies off.
// This is why you don't get callbacks.
}
Do the following, instead.
let weather = DarkSkyWeatherController()
override func viewDidLoad() {
weather.weatherGetterDelegate = self
weather.getLocation()
// Function exits, but the weather constant lives on as a field of your main ViewController. You'll get your callbacks now.
}

How to display information about my app when I use location and background execution

I use location collection, I want to achieve the effect of the following images, when the collection location is executed and executed in the background, you can display your own app in the status column.
I thought that as soon as I used the location and background, he would automatically display it. But not as I thought.
The following is my code
import UIKit
import CoreLocation
class ViewController: UIViewController , CLLocationManagerDelegate{
var locationMgr : CLLocationManager!
override func viewDidLoad() {
super.viewDidLoad()
locationMgr = CLLocationManager()
locationMgr.delegate = self
locationMgr.desiredAccuracy = kCLLocationAccuracyBestForNavigation
locationMgr.allowsBackgroundLocationUpdates = true
locationMgr.pausesLocationUpdatesAutomatically = true
if CLLocationManager.authorizationStatus() != .authorizedAlways {
locationMgr.requestAlwaysAuthorization()
}else{
locationMgr.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let a = locations.last{
print("----location update----")
print(a.coordinate.latitude);
print(a.coordinate.longitude);
print("---------------")
}
}
}
I can get the updated data when I execute it, but I don't see the display of the status bar above.
I don't know what I missed?
The alert on the top shows when you app has permission to access the location when it is in foreground and uses when the app is in background.
Use
if CLLocationManager.authorizationStatus() != .authorizedWhenInUse {
locationMgr.requestWhenInUseAuthorization()
}else{
locationMgr.startUpdatingLocation()
}
instead of
if CLLocationManager.authorizationStatus() != .authorizedAlways {
locationMgr.requestAlwaysAuthorization()
}else{
locationMgr.startUpdatingLocation()
}

How can I find users location when app is in background or killed state?

I want to get a new location when my app is in the background or killed state. I searched a lot and found out the method in the Apple doc. startMonitoringSignificantLocationChanges() for updating location when app is in background or killed state. But when I try this method didn't getting the location in the killed state.
Here is my code :
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
locationManager.startMonitoringSignificantLocationChanges()
}
//MARK: LOCATION MANAGER:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let long :CLLocationDegrees = manager.location?.coordinate.longitude ?? 00.00000
let lat :CLLocationDegrees = manager.location?.coordinate.latitude ?? 00.00000
print(lat)
print(long)
let location = LocationTracking()
location.latitude = lat
location.longitude = long
try! realm.write {
realm.add(location, update: false)
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error",error.localizedDescription)
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if (status == CLAuthorizationStatus.denied) {
print("Location access denied")
}
}
After setting Background Modes Capabiity on, you can use startMonitoringSignificantLocationChanges() instead of startUpdatingLocations(). This method notifies you of changes in 500 meters.
And you can read this quote by public api.
Apps can expect a notification as soon as the device moves 500 meters or more from its previous notification. It should not expect notifications more frequently than once every five minutes. If the device is able to retrieve data from the network, the location manager is much more likely to deliver notifications in a timely manner.
https://developer.apple.com/documentation/corelocation/cllocationmanager/1423531-startmonitoringsignificantlocati

Problems with CoreLocation

For some reason I cannot seem to get the users location. I bought the iOS 9 CookBook and coped their code exactly
Here is the ViewController:
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
lazy var locationManager: CLLocationManager = {
let m = CLLocationManager()
m.delegate = self
m.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
return m
}()
func locationManager(manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]) {
//TODO: now you have access to the location. do your work
}
func locationManager(manager: CLLocationManager,
didFailWithError error: NSError) {
//TODO: handle the error
}
func locationManager(manager: CLLocationManager,
didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if case .AuthorizedWhenInUse = status{
manager.requestLocation()
} else {
//TODO: we didn't get access, handle this
print("No Access Granted")
}
}
#IBAction func requestLocation() {
locationManager.requestWhenInUseAuthorization()
}
}
The output in the console is:
No Access Granted
Any ideas what is going on? I have tested this on both the simulator and the device
Edit: I have added a button to the storyboard and licked that to the IBAction accordingly.
Have you added the NSLocationWhenInUseUsageDescription key to your plist? - The value should be a string describing why you want to use the users location.
<key>NSLocationWhenInUseUsageDescription</key>
<string>Required to provide information about your location.</string>
If its all working correctly the didUpdateLocations delegate method should return an array of locations, you can print these out like so...
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
for loc in locations {
print(loc)
}
}
If you want to continually monitor the location you can call manager.startUpdatingLocation() instead of manager.requestLocation()

Why does LocationManager calls startUpdatingLocation multiple times?

Why is the Location Manager calling startUpdatingLocation more than once? Sometimes it is calling once, other times it is calling it three times. I don't know why; maybe you could help me. I have this code from GitHub.
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate
{
let locationManager = CLLocationManager()
override func viewDidLoad()
{
super.viewDidLoad()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
CLGeocoder().reverseGeocodeLocation(manager.location!, completionHandler: {(placemarks, error) -> Void in
if (error != nil)
{
print("Error: " + error!.localizedDescription)
return
}
if placemarks!.count > 0 {
if let pm = placemarks?.first {
self.displayLocationInfo(pm)
}
}
else
{
print("Error with the data.")
}
})
}
func displayLocationInfo(placemark: CLPlacemark)
{
self.locationManager.stopUpdatingLocation()
print(placemark.locality)
print(placemark.postalCode)
print(placemark.administrativeArea)
print(placemark.country)
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError)
{
print("Error: " + error.localizedDescription)
}
}
Yes, this is standard behavior. When you start location services you will generally receive a series of increasingly accurate CLLocation updates (i.e. with horizontalAccuracy decreasing over time) as the device "warms up". For example, it might start reporting location information that it might already have on the basis of cell towers, but as the GPS chip gets more information by which it can better triangulate your location, it will give you updates. Etc.
If you want to reduce this behavior, you can use a combination of a larger distanceFilter, a lower desiredAccuracy, or call stopUpdatingLocation once you get a location that you will geocode.
Right now you are calling stopUpdatingLocation, but you're doing it from the asynchronously called closure of reverseGeocodeLocation. This means that more location updates are able to slip in before the completion handler of reverseGeocodeLocation is called. If you call stopUpdatingLocation synchronously (e.g. before reverseGeocodeLocation), then you will avoid this behavior.