I'm trying to retrieve the user's location in the app using a location manager; as explained in Apple's documentation I created the following method:
func startReceivingLocationChanges() {
let authorizationStatus = CLLocationManager.authorizationStatus()
if authorizationStatus != .authorizedWhenInUse && authorizationStatus != .authorizedAlways {
locationManager.requestWhenInUseAuthorization()
startReceivingLocationChanges()
return
}
if !CLLocationManager.locationServicesEnabled() {
displayError(withTitle: "Location Not Available", withDescription: "Enable Location Services at Settings > Privacy > Location Services", sender: self)
return
}
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = 100.0 // In meters.
locationManager.pausesLocationUpdatesAutomatically = true
locationManager.activityType = .other
locationManager.delegate = self
locationManager.startUpdatingLocation()
}
But When I launch the app, this crash showing the error "Thread 1: EXC_BAD_ACCESS (code=2, address=0x16f0a7f60)" near the line:
locationManager.requestWhenInUseAuthorization()
I specify that I added the relative "Privacy - Location Always and When In Use Usage Description" and "Privacy - Location When In Use Usage Description" keys inside the info.plist.
Does anyone know what causes the issue? Thanks.
There are couple of mistakes in your code like recursion in case user don’t give the permission for when in use so look at my code. Assuming you had added "Privacy - Location Always and When In Use Usage Description" and "Privacy - Location When In Use Usage Description" keys inside the info.plist. Below code works perfect without any issue and apple recommended.
import Foundation
import CoreLocation
typealias LocateMeCallback = (_ location: CLLocation?) -> Void
class LocationTracker: NSObject {
static let shared = LocationTracker()
var locationManager: CLLocationManager = {
let locationManager = CLLocationManager()
locationManager.activityType = .automotiveNavigation
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
locationManager.distanceFilter = 10
return locationManager
}()
var locateMeCallback: LocateMeCallback?
var currentLocation: CLLocation?
var isCurrentLocationAvailable: Bool {
return currentLocation != nil
}
func enableLocationServices() {
locationManager.delegate = self
switch CLLocationManager.authorizationStatus() {
case .notDetermined:
// Request when-in-use authorization initially
locationManager.requestWhenInUseAuthorization()
case .restricted, .denied:
// Disable location features
print("Fail permission to get current location of user")
case .authorizedWhenInUse:
// Enable basic location features
enableMyWhenInUseFeatures()
case .authorizedAlways:
// Enable any of your app's location features
enableMyAlwaysFeatures()
}
}
func enableMyWhenInUseFeatures() {
locationManager.startUpdatingLocation()
}
func enableMyAlwaysFeatures() {
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = true
locationManager.startUpdatingLocation()
}
func locateMe(callback: #escaping LocateMeCallback) {
self.locateMeCallback = callback
enableLocationServices()
}
private override init() {}
}
// MARK: - CLLocationManagerDelegate
extension LocationTracker: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else { return }
print("locations = \(location.coordinate.latitude) \(location.coordinate.longitude)")
locateMeCallback?(location)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
enableLocationServices()
}
}
Usage
LocationTracker.shared.locateMe { location in
guard let location = location else {
print("Cann't retrieve current location of user")
return
}
// do what ever you want to do with location
}
Found the issue, it was related somehow to the recursive call to the function
startReceivingLocationChanges
inside the function itself. I solved it asking for location permissions inside ViewDidLoad method and removing the recursive call.
Related
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.
}
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()
}
I would like to call a method but it requires coordinates that have not been stored in variables yet.
So far I have:
1) acquired current location
2) Think I store them?
What I want to do:
1) Call method After these variables are stored so the program can run
class ViewController: UIViewController, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
var clLatitude : CLLocationDegrees!
var clLongitude: CLLocationDegrees!
override func viewDidLoad() {
super.viewDidLoad()
self.locationManager.requestAlwaysAuthorization()
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location:CLLocationCoordinate2D = manager.location!.coordinate
//Test printing coordinates to screen
print("locations = \(location.self.clLatitude) \(location.self.clLongitude)")
//place where I think I store the variables?
self.clLatitude = location.latitude
self.clLongitude = location.longitude
}
func methodToBeCalled(){
//to be called after variables are stored.
}
I believe I have covered everything regarding my problem
i think you just need to call the method at the end of locationManager(manager: didUpdateLocations:)
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location:CLLocationCoordinate2D = manager.location!.coordinate
//Test printing coordinates to screen
print("locations = \(location.self.clLatitude) \(location.self.clLongitude)")
//place where I think I store the variables?
self.clLatitude = location.latitude
self.clLongitude = location.longitude
// call method
methodToBeCalled()
}
func methodToBeCalled(){
//to be called after variables are stored.
}
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.
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.