swift variable scope in closure wrt CLGeocoder() - swift

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.

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

UILabel will not update to show fetched location coordinates

So I'm just trying to do a simple test in Xcode, where the app will fetch the user's current location and display the coordinates on screen. This can then be updated by pressing a 'Fetch Location' button.
The app doesn't seem to be fetching any coordinates (the UILabel only ever displays default text).
It's just a single-page app. And yes, the #IBOutlet and #IBAction are correctly linked.
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var labelLocation: UILabel!
var locationManager = CLLocationManager()
var myPosition = CLLocationCoordinate2D()
#IBAction func fetchLocation(_ sender: Any) {
locationManager.startUpdatingLocation()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func locationManager(manager: CLLocationManager!, didUpdateToLocation newLocation: CLLocation!, fromLocation oldLocation: CLLocation!) {
print("Got Location \(newLocation.coordinate.latitude), \(newLocation.coordinate.longitude)")
myPosition = newLocation.coordinate
locationManager.stopUpdatingLocation()
labelLocation.text = "Got Location \(newLocation.coordinate.latitude), \(newLocation.coordinate.longitude)"
}
}
Step 1:
Enable the Privacy - Location When In Use Usage Description in your info.plist file
Go to your info.plist file
Click on + on Information property list and type Privacy - Location When In Use Usage Description
In the value type why you want to request the users location (this will be shown to the user)
Step 2:
Get the location
Use this code:
guard let latitude = manager.location?.coordinate.latitude, let longitude = manager.location?.coordinate.longitude else { return }
locationManager.stopUpdatingLocation()
labelLocation.text = "Got Location \(latitude), \(longitude)"
Ok, I think that you can resolve it adding the "Privacy - Location When In Use Usage Description" key into Info.plist, this is an important step in Location topic.
Regards!

CLLocationManager.location is nil in 10.10, but works in 10.11

In my DC Metro tracking application, I use CoreLocation to select the Metro station nearest the user and also present a list of stations near them.
It works perfectly in macOS 10.11, but I'm having trouble getting it to work on macOS 10.10. To debug this, I inserted a line in the locationManager(_:didUpdateLocations:) method in TodayViewController.swift to print the location fetched by the application.
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [AnyObject]) {
LocationManager.sharedManager.stopUpdatingLocation()
print(LocationManager.sharedManager.location)
...
}
On El Capitan, this outputs the following to the console (success):
Optional(<+38.92208178,-77.22920176> +/- 65.00m (speed -1.00 mps / course -1.00) # 8/18/16, 5:04:37 PM Eastern Daylight Time)
On Yosemite, it just outputs nil.
I have also tried to simulate my location to no avail.
Does anyone have any suggestions? Location services are enabled on the Yosemite machine, and I know that it is working because the Weather Notification Center widget is correctly fetching its location.
Thank you!
More relevant code:
override func viewWillAppear() {
super.viewWillAppear()
...
switch CLLocationManager.authorizationStatus() {
case .Authorized:
if !didSelectStation {
selectedStationLabel.stringValue = "Determining closest station..."
}
LocationManager.sharedManager.startUpdatingLocation()
case .NotDetermined:
getCurrentLocationButton.hidden = false
mainPredictionView.hidden = true
default: // Denied or Restricted
WMATAfetcher.getPredictionsForSelectedStation()
}
}
class LocationManager {
static let sharedManager: CLLocationManager = {
let locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = 100.0
return locationManager
}()
}
I also have a button, getCurrentLocationButton, that calls startUpdatingLocation()
#IBAction func getCurrentLocation(sender: NSButton) {
LocationManager.sharedManager.startUpdatingLocation()
}
Have you tried accessing location before calling stopUpdatingLocation()?

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.

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.