CLLocationManager requestLocation not calling didUpdateLocations - swift

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.
}

Related

Error when invoke requestWhenInUseAuthorization for location permissions

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.

Calculate total distance swift iOS

So in my current project Im doing a method which calculates the saved emission when driving a moped compared to a average car. The function contains two parts, the method (the calculation) and the tracker function. The main problem is that the tracker function somehow does not seem to track at all.
My main question is, how do I get the tracker function to always track while the app is on?
This is the tracker function
var startLocation:CLLocation!
var lastLocation: CLLocation!
var traveledDistance:Double = 0
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if startLocation == nil {
startLocation = locations.first
} else {
if let lastLocation = locations.last {
let distance = startLocation.distanceFromLocation(lastLocation)
let lastDistance = lastLocation.distanceFromLocation(lastLocation)
traveledDistance += lastDistance
print( "\(startLocation)")
print( "\(lastLocation)")
print("FULL DISTANCE: \(traveledDistance)")
print("STRAIGHT DISTANCE: \(distance)")
var travelDistance = setData("distance")
}
}
lastLocation = locations.last
}
And this is the method
func calculateEmission(numbers: Int...) -> Double{
let recordedDistance = getData("distance")
let dis = recordedDistance
let emissionAve = 0.16
let calculatedEmission : Double = Double(dis) * Double(emissionAve)
print(calculatedEmission, "kg Co2")
return calculatedEmission
}
Make sure you have the following in your info.plist. Then you should get prompted to allow access to the location services.
<key>NSLocationAlwaysUsageDescription</key>
<string>Needs access to access GPS</string>
<key>NSLocationUsageDescription</key>
<string>Needs access to access GPS</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Needs access to access GPS</string>
You should have some thing like this in viewDidLoad.
override func viewDidLoad() {
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}
}

swift variable scope in closure wrt CLGeocoder()

Can someone please help me understand why in the following code will have output like this:
[<+37.49638550,-122.31160280> +/- 5.00m (speed 32.65 mps / course
294.26) # 7/27/16, 4:34:19 AM Eastern Daylight Time]
n/a Belmont Belmont
Belmont 1
That is, why the variable locality will have value "n/a" when printed at a specific point whereas localityVC & localityGlobal never do? Is this purely a scope issue or something to do with CLGeocoder()? Thanks
import UIKit
import MapKit
import CoreLocation
var localityGlobal: String = "n/a"
class ViewController: UIViewController, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
var localityVC: String = "n/a"
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
print("hello")
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print(locations)
let userLocation: CLLocation = locations[0]
var locality: String = "n/a"
CLGeocoder().reverseGeocodeLocation(userLocation, completionHandler: {(placemarks, error) -> Void in
if error != nil {
print("CLGeocoder.reverseGeocodeLocation() failed")
return
}
if placemarks!.count > 0 {
locality = placemarks![0].locality!
self.localityVC = placemarks![0].locality!
localityGlobal = placemarks![0].locality!
print(locality, placemarks!.count)
} else {
print("CLGeocoder.reverseGeocodeLocation() error in geocoder data")
}
})
print(locality, localityVC, localityGlobal)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
In order for the above to run you also need to have:
Build Phases -> Link Binary with Libraries include
CoreLocation.framework,
Info.plist must have variables
NSLocationWhenInUseUsageDescription and
NSLocationAlwaysUsageDescription set,
Simulator -> Debug -> Location set to something like Apple, City Bicycle Ride, City Run or Freeway Drive
I'm running xcode 7.3.1 (7D1014)
This isn't a scope issue, it's a timing one. You get n/a because the closure has yet to run when that print statement is executed.
The other variables are retaining the value from a prior call.

swift - How to delay method call until variables value is stored regarding updating current location

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.
}

EXC_BAD_INSTRUCTION for Map location App

I'm very new to coding so please forgive me. I'm trying to run a program on my Apple Watch that tells me my location coordinates, altitude, speed, and course.
Everything was working up to my println(userlocationInfo) line, but then I got an error at my let row = table.rowControllerAtIndex(index) as! tableRowController:
Thread 1:EXC_BAD_INSTRUCTION(code=EXC_I386_INVOP,subcode=0x0)
By the way, what exactly does that error message mean? How can I solve an error message like this in the future by myself?
import WatchKit
import Foundation
import CoreLocation
class InterfaceController: WKInterfaceController, CLLocationManagerDelegate {
#IBOutlet weak var table: WKInterfaceTable!
var locationManager = CLLocationManager()
var userLocationInfo = [String]()
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
let locationArray = locations as NSArray
let location = locationArray.lastObject as! CLLocation
userLocationInfo.removeAll(keepCapacity: true)
userLocationInfo.append("\(location.coordinate.latitude)")
userLocationInfo.append("\(location.coordinate.longitude)")
userLocationInfo.append("\(location.altitude)")
userLocationInfo.append("\(location.speed)")
userLocationInfo.append("\(location.course)")
println(userLocationInfo)
table.setNumberOfRows(userLocationInfo.count, withRowType: "tableRowController")
for (index, value) in enumerate(userLocationInfo) {
let row = table.rowControllerAtIndex(index) as! tableRowController
row.tableRowLabel.setText(value)
}
}
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
}
You should move your table function out of the didUpdateLocations delegate and call it after you have the location data. The didUpdateLocations will continue to run until you stop updating locations.