Getting Coordinates with CLLocationManager - ios

In my iOS app, I have the following code. With this code I am hoping to get the Latitude and Longitude values of the device's current location.
CODE AppDelegate
import UIKit
import CoreLocation
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {
var window: UIWindow?
var locationManager: CLLocationManager!
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
application.setStatusBarHidden(true, withAnimation: .None)
initializeLocationManager()
return true
}
func initializeLocationManager() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
}
}
CODE ViewController
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let localtionManager = appDelegate.locationManager
var currentLat = localtionManager.location.coordinate.latitude
var currentLng = localtionManager.location.coordinate.longitude
let forecastURL = NSURL(string: "\(currentLat),\(currentLng)", relativeToURL: baseURL)
On the third line of code in my ViewController from debugging I can see that the value returned is: 6.9530045620513319E-310.
I have been told that this value can't be right, in other words something has gone wrong. Can you please tell me why this is wrong? and how I can get the latitude and longitude as strings so that I may pass them to a RESTful API?
Thanks

6.9530045620513319E-310 is not a valid value that should be returned for latitude or longitude, so you've most likely made an error somewhere else in your code. Without the rest of your code we can only make guesses as to what you did wrong.
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
println(manager.location.coordinate.latitude)
println(manager.location.coordinate.longitude)
}
Try dropping this code into your CLLocationManagers delegate and if it doesn't output a valid latitude and longitude make sure that
You have requested permission to have a users location and have added the reasoning into your info.plist
You have called locationManager.startUpdatingLocation()while settings up your locationManager
If you are using the simulator make sure you have gone to Debug-> Location and set a location
if it still doesn't work you're going to need to post more of your project code so we can figure out where you went wrong.

You just need to add NSLocationAlwaysUsageDescription=true in the app's plist. That did it for me.

Related

How to catch GPS data of func LocationManager?

i got some problems with this whole UIViewController thing. My thoughts were, that the viewDidLoad() would be something like a main() in other languages, but especially in this case I don't see any functions called in the viewDidLoad() func.
First of all I am totally confused by the var locationManager which is actually a CLLocationManager and a func at the same time. How?
Where do I call the func locationManager? Can I return the locValue.latitude and the locValue.longitude? How do I catch them in the viewDidLoad()? Finally I want to send these two parameters to something, after I pressed a button (see: func SendButtonAction).
But my problem is, that I don't know how to bring these two guys from the body of func locationManager to an input in func SendButtonAction.
Appreciate any help :) I guess I need more basic knowledge.
import UIKit
import MapKit
import CoreLocation
class GPSNew: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var zurueckButton: UIButton!
#IBOutlet weak var SendButton: UIButton!
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
// Ask for Authorisation from the User.
self.locationManager.requestAlwaysAuthorization()
// For use in foreground
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}
// Do any additional setup after loading the view.
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
//guard let locValue: CLLocationCoordinate2D = manager.location?.coordinate else { return }
let locValue: CLLocationCoordinate2D = manager.location!.coordinate
print("locations = \(locValue.latitude) \(locValue.longitude)")
let userLocation = locations.last
let viewRegion = MKCoordinateRegion(center: (userLocation?.coordinate)!, latitudinalMeters: 600, longitudinalMeters: 600)
self.mapView.setRegion(viewRegion, animated: true)
//return (locValue.latitude, locValue.longitude)
}
#IBAction func SendButtonAction(_ sender: Any) {
//send the user location to something
//end updating location
locationManager.stopUpdatingLocation()
}
}
Actually the method that gives the location is asynchronous so you can try
#IBAction func sendButtonAction(_ sender: Any) {
if let loc = locationManager.location?.coordinate {
print(loc.latitude, loc.longitude)
locationManager.stopUpdatingLocation()
}
}
From your question it sounds like you're used to procedural programming. In a C command-line utility, for example, you have a main() function that gets called at the launch-time. Main calls other functions to do setup, then might have a loop that steps through the work it has to do, and then either loops, waiting for input from the user, or returns if it's a "one-and-done" utility.
Apps for most (all?) GUI-based OS'es don't work that way. They are event-driven, and usually use an object-oriented design. You should read up on event-driven development. Until you study it, you're going to be very confused and won't be able to get off of square one.
Here is a short intro to the concepts, but this is a much deeper topic than we can cover in a simple post:
In an object-oriented,event-driven program, you create objects that have methods (functions) that respond to things that happen. Your program defines a set of starting objects, and then those objects wait for stuff to happen.
The method viewDidLoad() is an example of a method that gets called when something happens. It gets called when a view controller's (an object that manages a view) view gets created. It gives you a chance to do one-time setup to get ready for the user to "do stuff." Your viewDidLoad() function does that one-time setup, and then returns.
Control then returns to the system, and your app just waits to get called again.
You might also add methods that respond to the user tapping on buttons, sliding, notifications about updated GPS locations, etc.
The location manager (CLLocationManager) is an object that you create when you want to get information about the device's location. You create one, and ask it to notify you about various types of location events. You set up an object to be the location manager's "delegate". This is like giving the location manager a phone number and saying "call this number when the user's location changes."
The location manager calls its delegate when events occur that you told it you care about.

Current location in not working in Google Map

I have integrated google map in swift 3, when map screen appear than current location in not showing, i have added two keys in .plist file and also set CLLocationManager delegate and requestAlwaysAuthorization
class MapViewController: UIViewController, CLLocationManagerDelegate, UITextFieldDelegate {
#IBOutlet var mapView: GMSMapView!
var marker: GMSMarker?
override func viewDidLoad() {
super.viewDidLoad()
self.title = "MapVC"
self.doSetupUI()
self.searchLocation()
}
override func viewWillAppear(_ animated: Bool) {
let locationManager : CLLocationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
}
func doGoogleMapSetup(lat : Double , lng : Double) {
let camera = GMSCameraPosition.camera(withLatitude: lat, longitude:lng, zoom:16)
let mapView = GMSMapView.map(withFrame: .zero, camera:camera)
mapView.isMyLocationEnabled = true
let marker = GMSMarker()
marker.position = CLLocationCoordinate2D(latitude: lat, longitude: lng)
marker.snippet = ""
marker.appearAnimation = kGMSMarkerAnimationPop
marker.map = mapView
let arrPoints : NSMutableArray = NSMutableArray()
arrPoints.add(UserDefaults.standard.object(forKey: "addressPoints"))
for i in 0..<arrPoints.count {
let path : String = (arrPoints.object(at: i)as! NSMutableArray).object(at: 0) as! String
let route : GMSPath = GMSPath.init(fromEncodedPath: path)!
let polyLine : GMSPolyline = GMSPolyline.init(path: route)
polyLine.strokeWidth = 2.0
polyLine.strokeColor = UIColor.red
polyLine.map = mapView
}
}
For showing current location we don't need any location manager in case of GoogleMaps. All we need is to add one of the keys or both in the .plist. So make sure the key is there. I have used NSLocationWhenInUseUsageDescription key.
<key>NSLocationWhenInUseUsageDescription</key>
<string>Allow location</string>
Also make sure that you have called GMSServices provideAPIKey method and replaced with the API_KEY you generated in google developer console. Also all the relevant Google APIs as per requirement should be enabled.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
GMSServices.provideAPIKey("YOUR_API_KEY")
return true
}
So, I am assuming you have done all the settings and things right in google developer console.
By just writing the below line in your controller where you have made the GoogleMap can show the location allow/disallow prompt and take the permission of the user.
mapView.isMyLocationEnabled = true
However this will not animate your map to your current location. But you can manually drag the map to check the current location and you will see a blue dot at your current location.
But now we also want to animate to the current location whenever we load that ViewController. Now the need for CLLocationManager arrives. So that in its didUpdateLocation delegate, we can fetch the current location and can just animate the graph to the current location.
So here is my complete controller.
import UIKit
import GoogleMaps
class ViewController: UIViewController,GMSMapViewDelegate,CLLocationManagerDelegate {
#IBOutlet weak var mapView: GMSMapView!
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
mapView.isMyLocationEnabled = true
mapView.delegate = self
//Location Manager code to fetch current location
self.locationManager.delegate = self
self.locationManager.startUpdatingLocation()
}
//Location Manager delegates
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations.last
let camera = GMSCameraPosition.camera(withLatitude: (location?.coordinate.latitude)!, longitude:(location?.coordinate.longitude)!, zoom:14)
mapView.animate(to: camera)
//Finally stop updating location otherwise it will come again and again in this delegate
self.locationManager.stopUpdatingLocation()
}
}
Another way of doing is not using the didUpdateLocation and not using the location manager is just by using the GMSMapViewDelegate delegate method mapViewDidFinishTileRendering
func mapViewDidFinishTileRendering(_ mapView: GMSMapView) {
let location = mapView.myLocation
let camera = GMSCameraPosition.camera(withLatitude: (location?.coordinate.latitude)!, longitude:(location?.coordinate.longitude)!, zoom:14)
mapView.animate(to: camera)
}
It will be called everytime the map rendering is finished.
But this comes with a limitation, it will always bring you to the current location whenever you drag/pinch/zoom map as the rendering finish everytime you play with map. So, you can just implement some kind of bool variable logic here.
You can get your location by using
let yourCurrentLocation = mapView.myLocation
Make sure to do this on a device rather than simulator. If you are using simulator, you have to choose some custom location and then only you will be able to see the blue dot.
I already gave this type of answer. Check this Link. But that was in Swift 2.x. The one which I posted in this answer is in Swift 3.x
This is a bit detailed, so I'd like to leave it in a full answer. This is the most common reason I have encountered for a nil location, since figuring out the basics, a few years ago. So, you call CLLocationManager.startLocating(), in your viewDidLoad. Then you call the method that sets up your map. Sometimes this works, and sometimes it doesn't, because of a race condition caused by the amount of time it takes the CLLocationManager to set up permissions, on the one hand, and access the user's location, in another part of the code. Let's look at an order of events, where it doesn't work:
1) you call requestAlwaysAuthroization and startLocating
2) User permissions setup is triggered on one thread
3) In your ViewController, you request the user's location, to set up your map
4) It comes back nil
5) NOW, step 2 finishes, and the app has access to the user's location, but it's too late
The core problem, is that the process that starts with requesting permissions and location, takes more than a few milliseconds. And if your view is already set up, it takes few milliseconds for it to go through the methods in your viewDidLoad. By the time you have the location you need, you've already requested it. This has caused me too many crashes, in my location-based apps.
My workaround, has been to craft a singleton CLLocationManager, make my starting view a delegate, and requestAlwaysAuthorization and startLocating, in that view. That way, when I get to the view that needs the location, the app has already started locating, and the locationManager.location is not nil.
This is an approach that will obviously not work for every app. If you need clarification, let me know, and if you need code, as well. I have a few public iO git repos, with projects where I have encountered and fixed this problem.

Swift: Utilizing CLLocationManagerDelegate and CoreLocation.framework results in "Use of undeclared type" error

I'm going off a youtube video that gives a basic example of utilizing the location services of Xcode to program a sort of maps user interface to a program. I followed the steps in the video of setting up the program and get an assumedly simple "Use of undeclared type" error when trying to compile the application.
https://www.youtube.com/watch?v=qrdIL44T6FQ - Link to the youtube video.
The problem occurs at the class creation line.
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate
{ // ^ Use of undeclared type
#IBOutlet weak var mapView: MKMapView!
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
self.mapView.showsUserLocation = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreate-d.
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations.last
let center = CLLocationCoordinate2D(latitude: location!.coordingate.latitude, longitude: location!.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1))
self.mapView.setRegion(region, animated: true)
self.location.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager, difFailWithError error: NSError)
{
print("Errors: " + error.localizedDescription)
}
}
I'm not sure why the error is occurring.
I added the CoreLocation.framework in the "Link Binary With Libraries" section. I made sure to add the "NSLocationWhenInUseUsageDescription" to the Info.plist.
I connected the Map View Kit to the code itself at 2:26 in the video.
Other ways to go about doing this is also of interest. I'm solely trying to get a basic location services clearance to run in a program.
Version: Xcode Version 7.3.1
In swift
I was missing importing CoreLocation. I have added following line on top of my file and it worked.
import CoreLocation
I made a new project and followed the steps in the video again and the project now works appropriately. The only thing that may have caused the issue was I checked off Core Data when creating the project. I do not believe this is exactly why it was throwing an error, but if you run into a similar situation make sure this box is left empty.

How to use Longitude and Latitude values from locationManager function elsewhere in my app in Swift

I am using the CoreLocation framework to get the user's location when they open up my app. I use this function:
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
var locValue:CLLocationCoordinate2D = manager.location!.coordinate
print("locations = \(locValue.latitude) \(locValue.longitude)")
}
to get the user's longitude and latitude position, and I can see them by printing them to the logs. This works fine.
elsewhere in my app (but in the same viewController.swift file) I have code that uses the OpenWeatherMap API, and I have a string that contains the url for this, which return JSON.
In my viewDidLoad, I use:
getWeatherData("http://api.openweathermap.org/data/2.5/weather?lat=XXXXXX&lon=XXXXXX&appid=(MY-APP-ID)")
I need to place the Long and Lat values that I've acquired in the locationManager function, into this string, which I know I can do by "\()" within the url string.
My problem is, I can currently only use these values inside the locationManager function. How can I store them in a value outside of this function, so I can add them into my URL string?
Thanks
Hope this answers your question.
import UIKit
import MapKit
class myClass {
var userLocation: CLLocationCoordinate2D? // The user location as an Optional stored as a var in class "myClass".
// !!! This can be accessed everywhere within the class "myClass" (and deeper)
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let locValue:CLLocationCoordinate2D = manager.location!.coordinate // Change it from var to let since it's only read not writen
// !!! This can be accessed everywhere within the func "locationManager" (and deeper)
userLocation = locValue // !!! Store it if necessary
// Why would you call this in viewDidLoad? I doubt the user location will be available at this point (but it might). You can move this anywhere if you want
// note the "\(name)" this will turn the var name into a string
// if != nil not necessary here since it cannot be nil but still added it regardless
// Maybe you want to add a check so this only gets called on the first location update. It depends on what you need it for.
if userLocation != nil {
getWeatherData("http://api.openweathermap.org/data/2.5/weather?lat=\(userLocation!.latitude)&lon=\(userLocation!.latitude)&appid=(MY-APP-ID)") // Why would you call this in viewDidLoad? I doubt user doubt the user location will be available at this point (but it might)
}
else {
print("Error: User not Located (yet)")
}
}
}

Delegate Call back method returns variable slow which causes variable nil while accessing from another class?

In my app delegate class, i am trying to retrieve user Current Location from another class using delegate. This retreived User Curren location will be used in many parts of my application.So ,i have set it here in AppDelegate Class
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var helperLocation:HelperLocationManager?
var currentLocation:CLLocation?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
//get the current location and set it so all other screens can use it
self.helperLocation = HelperLocationManager()
self.helperLocation?.delegate = self
return true
}
}
extension AppDelegate: SendLocationDelegate{
func sendCoOrdinates(loccoordinate:CLLocation, placemark:CLPlacemark){
currentLocation = loccoordinate
}
}
And this is what seems to be my HelperLocationManager Class
protocol SendLocationDelegate{
func sendCoOrdinates(coordinates:CLLocation,placemark:CLPlacemark)
}
class HelperLocationManager: NSObject {
var locationManager = CLLocationManager()
var delegate:SendLocationDelegate?
override init() {
super.init()
var code = CLLocationManager.authorizationStatus()
if code == CLAuthorizationStatus.NotDetermined {
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
}
locationManager.delegate = self
}
}
extension HelperLocationManager: CLLocationManagerDelegate{
func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case CLAuthorizationStatus.Restricted:
println( "Restricted Access to location")
case CLAuthorizationStatus.Denied:
println( "User denied access to location please turn on the location")
// UIApplication.sharedApplication().openURL(NSURL(string: UIApplicationOpenSettingsURLString)!)
//may be open here settings page and say to turn on the setting location services
default:
locationManager.startUpdatingLocation()
}
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
var locValue = locations.last as! CLLocation
locationManager.stopUpdatingLocation()
CLGeocoder().reverseGeocodeLocation(manager.location, completionHandler: {(placemarks,error)-> Void in
if (error != nil) {
println("Reverse geocoder failed with error" + error.localizedDescription)
return
}
if placemarks.count > 0 {
let pm = placemarks[0] as! CLPlacemark
self.delegate?.sendCoOrdinates(locValue,placemark: pm)
} else {
println("Problem with the data received from geocoder")
}
})
}
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) {
println("Your error is ", error.localizedDescription)
}
}
I made my call back method to trigger if there is any change in the user location...
Everything is fine. HelperLocationManager class sends the current location to the method sendCoOrdinatesthat is implemented in AppDelegate And I have set the current location and now i am accessing these location from presentedViewController as
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
pickUpDistanceLocation = appDelegate.currentLocation
My problem is when i try to access the value very fast enough in another class during the time interval the delegate call back method doesnot send me my current Location.I get nil in this case.but if i wait 2-3 sec and go to another class i get the value from delegate.
Can anyone explain me what am i doing wrong?
This is an architectural issue - you say:
when i try to access the value very fast enough in another class during the time interval the delegate call back method does not send me my current Location
You have to invert that - rather than checking the current location, with the risk of not having one because it's not been obtained yet, you should let the HelperLocationManager notify when it has a location (the hollywood principle: don't call me, I'll call you).
This can be done in different ways:
using the delegation pattern
using an event bus (which can be implemented with NSNotificationCenter)
using callbacks
There are of course many other ways to achieve the same result.
The delegation pattern is probably not the best solution when there are more than one observer.
I would use the callback way, with a subscriber registering to location updates by providing a closure to HelperLocationManager.
HelperLocationManager can store all callbacks into an array, and invoke each of them when a location update is available. Optionally, it can invoke a closures right after registration, if a location is already available.
Last, the subscriber must be able to unsubscribe, so HelperLocationManager should expose a method which removes a callback from its internal list.
This is just an idea - as said, it can be done in several different ways, the common rule is to just invert how the location is passed.
Note: I would make HelperLocationManager a singleton, and remove it from AppDelegate. If I want to use HelperLocationManager, I should contact it directly, instead of having to access through a 3rd party (the app delegate).

Resources