Google places API doesn't work as expected - ios

I am following Google Places API for IOS tutorial to view the user current place.
I used the same code in the tutorial as follow:
var placesClient: GMSPlacesClient!
// Add a pair of UILabels in Interface Builder, and connect the outlets to these variables.
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var addressLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
placesClient = GMSPlacesClient.shared()
}
// Add a UIButton in Interface Builder, and connect the action to this function.
#IBAction func getCurrentPlace(_ sender: UIButton) {
placesClient.currentPlace(callback: { (placeLikelihoodList, error) -> Void in
if let error = error {
print("Pick Place error: \(error.localizedDescription)")
return
}
self.nameLabel.text = "No current place"
self.addressLabel.text = ""
if let placeLikelihoodList = placeLikelihoodList {
let place = placeLikelihoodList.likelihoods.first?.place
if let place = place {
self.nameLabel.text = place.name
self.addressLabel.text = place.formattedAddress?.components(separatedBy: ", ")
.joined(separator: "\n")
}
}
})
}
But I get the following error in the console:
Pick Place error: The operation couldn’t be completed. The Places API
could not find the user's location. This may be because the user has
not allowed the application to access location information.
NOTE: I have set the NSLocationWhenInUseUsageDescription key (Privacy - Location When In Use Usage Description) in info.plist file.
It's confusing because I followed the tutorial step by step. And am testing the application using physical device with "Locations Services" enabled .
Any idea what I might be doing wrong?
Or is it because the documentation is not up-to-date?

This may be because the user has not allowed the application to access location information.
This points you towards your answer. For Google Places to work you need to request to use location services by calling requestWhenInUseAuthorization(). This will prompts the user to grant permission to the app to use location services.
Please refer to the Apple Docs for more info.
EDIT
You should keep a strong reference to the CLLocationManager that you create so it does not get deallocated when your function exits.
"Create an instance of the CLLocationManager class and store a strong reference to it somewhere in your app.
Keeping a strong reference to the location manager object is required until all tasks involving that object are complete. Because most location manager tasks run asynchronously, storing your location manager in a local variable is insufficient."
Taken from the CLLocationManager Docs
EXAMPLE
class LocationViewController: UIViewController, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
override func viewDidLoad()
{
super.viewDidLoad()
locationManager.delegate = self
if CLLocationManager.authorizationStatus() == .notDetermined
{
locationManager.requestWhenInUseAuthorization()
}
}
}

1. Request user for Location Usage Authorization
requestWhenInUseAuthorization() OR requestAlwaysAuthorization() according to your requirement.
2. In Info.plist, add the following keys:
a. NSLocationAlwaysUsageDescription OR NSLocationWhenInUseUsageDescription
b. NSLocationAlwaysAndWhenInUseUsageDescription
Example:
class ViewController: UIViewController, CLLocationManagerDelegate
{
let locationManager = CLLocationManager()
override func viewDidLoad()
{
super.viewDidLoad()
locationManager.requestWhenInUseAuthorization()
locationManager.delegate = self
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus)
{
}
}

Related

Request User Location Permission In SwiftUI

How do you get user location permissions in SwiftUI?
I tried asking for user location permissions after a button tap, but the dialogue box disappears after about a second. Even if you do end up clicking it in time, permission is still denied.
import CoreLocation
.
.
.
Button(action: {
let locationManager = CLLocationManager()
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
}) {
Image("button_image")
}
Things like location manager should be in your model, not your view.
You can then invoke a function on your model to request location permission.
The problem with what you are doing now is that your CLLocationManager gets released as soon as the closure is done. The permission request methods execute asynchronously so the closure ends very quickly.
When the location manager instance is released the permission dialog disappears.
A location model could look something like this:
class LocationModel: NSObject, ObservableObject {
private let locationManager = CLLocationManager()
#Published var authorisationStatus: CLAuthorizationStatus = .notDetermined
override init() {
super.init()
self.locationManager.delegate = self
}
public func requestAuthorisation(always: Bool = false) {
if always {
self.locationManager.requestAlwaysAuthorization()
} else {
self.locationManager.requestWhenInUseAuthorization()
}
}
}
extension LocationModel: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
self.authorisationStatus = status
}
}
You would probably also want functions to start & stop location updates and an #Published CLLocation property

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.

Getting user's current location but getting back "Optional" California.. etc

I'm trying to get the user's current location using Swift. Here is what I am currently using:
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
let locationManager = CLLocationManager();
//Info about user
#IBOutlet weak var userTF: UITextField!
#IBOutlet weak var BarbCustTF: UITextField!
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();
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// GPS STUFF
// UPDATE LOCATION
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
CLGeocoder().reverseGeocodeLocation(manager.location!) { (placemarks, ErrorType) -> Void in
if(ErrorType != nil)
{
print("Error: " + ErrorType!.localizedDescription);
return;
}
if(placemarks?.count > 0)
{
let pm = placemarks![0] ;
self.displayLocationInfo(pm);
}
}
}
// STOP UPDATING LOCATION
func displayLocationInfo(placemark: CLPlacemark)
{
self.locationManager.stopUpdatingLocation();
print(placemark.locality);
print(placemark.postalCode);
print(placemark.administrativeArea);
print(placemark.country);
}
// PRINT OUT ANY ERROR WITH LOCATION MANAGER
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("Error: " + error.localizedDescription);
}
Everything seems to work fine, but the output I'm getting is like really weird and says Optional in front of it, and is definitely (unfortunately) not my current location.
This is the output I'm getting when I print it to the console
Optional("Cupertino")
Optional("95014")
Optional("CA")
Optional("United States")
Things I've tried:
1) In my info.plist I have : NSLocationWhenInUseUsageDescription
2) I've also heard weird stuff happens and I tried going to Debug>>Location>> and changing it to in city and all sorts of things (didn't help)
I think that the problem is something in my function LocationManager that has to do with like "wrapping" or something ? I'm not sure, this is my first day messing with iOS programming with Swift and I don't really know what wrapping is but I think that may be what's going on from what I've seen on the internet... Basically I dont understand why i'm printing out some default apple locations (California.. blah blah) I don't live in Cali (unfortunately).
Instead of this
print(placemark.locality);
do this
if let locality = placemark.locality {
print(locality)
}
The if let pattern here is a way of only printing locality if it's not nil. This is the way to do it in this case.
If you were sure that locality was never nil, you could do
print(placemark.locality!)
but if locality happened to be nil, your app would crash on that line.

parse geoPointForCurrentLocationInBackground Swift sample

Found multiple posts on this, but still can't quite piece it together.
I'm using Parse to retrieve the user's current location. Documentation makes it seem very easy, but several things appear to be missing. https://parse.com/docs/ios/guide#geopoints-getting-the-user-39-s-current-location
First, my code:
class TableViewController: UITableViewController, CLLocationManagerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
print("Get GPS")
PFGeoPoint.geoPointForCurrentLocationInBackground { (geoPoint, error ) -> Void in
if error == nil {
print("Got geoPoint") //Never reaches this
print(geoPoint)
} else {
print(error ) //No error either
}
}
I've updated the NSLocationWhenInUseUsageDescription in my info.plist
Tried simulator and two real devices.
Added CLLocationManagerDelegate
I'm trying to avoid making this more complex than it needs to be.
I've also experimented with CLLocationManager samples and it doesn't seem to be working either.
I'm using most recent versions of everything... started with Parse yesterday!
I've never been challenged for authorization to use my location. Tried that with CLLocationManager examples!
Would greatly appreciate some guidance / support.
You are right about missing a few things before you can handle the location update from Parse.
First, add a location manager property to your class.
class TableViewController: UITableViewController, CLLocationManagerDelegate {
var locationManager = CLLocationManager()
Set its delegate and use it to request authorization from the user:
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
print("Get GPS")
PFGeoPoint.geoPointForCurrentLocationInBackground { (geoPoint, error ) -> Void in
if error == nil {
print("Got geoPoint") //Never reaches this
print(geoPoint)
} else {
print(error ) //No error either
}
}
}
In your Info.plist, you need to add a key NSLocationWhenInUseUsageDescription and give it a string value for a message to the user during the authorization request.
The authorization request and the location update should work after the above changes.

I can get Lat and Long but I can't access the GPS altitude info in SWIFT

I've been trying to get the altitude from a CLLocation that i get from from the CoreLocation framework using the following code:
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
/*
Note: This needs to be added to the info.plist file for this to work:
<key>NSLocationUsageDescription</key> <string>Your message</string> <key>NSLocationAlwaysUsageDescription</key> <string>Your message</string> <key>NSLocationWhenInUsageDescription</key>
<string>Your message</string>
*/
#IBOutlet weak var gpsResult: UILabel!
#IBOutlet weak var altitudeLabel: UILabel!
var manager:CLLocationManager!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
manager = CLLocationManager()
manager.delegate = self
manager.distanceFilter = kCLDistanceFilterNone
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestAlwaysAuthorization()
manager.startUpdatingLocation()
}
func locationManager(manager:CLLocationManager!, didUpdateLocations myLocations:CLLocation) {
if manager != nil {
var alt:CLLocationDistance = myLocations.altitude
gpsResult.text = "locations = \(myLocations)"
altitudeLabel.text = "GPS Altitude: \(Double(alt))"
// manager.stopUpdatingLocation()
}
}
}
So if I only request the location I'm able to get the gpsResult.text value and it works correctly but when I try to access the altitude I get an error:
'NSInvalidArgumentException', reason: '-[__NSArrayM altitude]: unrecognized selector sent to instance 0x17404dcb0'
The thing is as per apple's reference that selector should exist.
I went through the posts here and the net and I tried their code but all of them failed.
Does anyone haven an idea what's goig on?
Thanks.
According to Apple's documentation CLLocationManagerDelegate the locations parameter of didUpdateLocations gives:
An array of CLLocation objects containing the location data. This
array always contains at least one object representing the current
location. If updates were deferred or if multiple locations arrived
before they could be delivered, the array may contain additional
entries. The objects in the array are organized in the order in which
they occurred. Therefore, the most recent location update is at the
end of the array.
So you can access the most recent location via the last element in the array:
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
let location = locations.last
gpsResult.text = "locations = \(location)"
altitudeLabel.text = "GPS Altitude: \(location.altitude)"
// manager.stopUpdatingLocation()
}

Resources