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.
Related
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)
{
}
}
I'm starting using ReactiveCocoa and I'm still struggling with some basic concepts:
My app starts listening for geolocation data (init in my view model)
My app emits a signal with my current location (didFindCurrentPosition is called)
My view controller showing a map loads (viewDidLoad in my view controller)
My view controller starts observing the current location signal (still viewDidLoad)
My problem is: after step 2 is achieved, if no other event is sent on the signal, my view controller doesn't get notified.
How can my view controller get access to the last value from the signal? (ie how to get access at step 3 to a value emitted at step 2?)
Thanks for your help.
PS: ReactiveCocoa looks like a great library but I'm puzzled by the state of the documentation. IMHO, it is not very clear and lacks some clear guides on how to use it.
The Code
The view model:
class MyViewModel: LocationManagerDelegate {
let locationManager: LocationManager
let geolocationDataProperty = MutableProperty<Geolocation?>(nil)
let geolocationData: Signal<Geolocation?, NoError>
init() {
geolocationData = geolocationDataProperty.signal
// Location Management
locationManager = LocationManager()
locationManager.delegate = self
locationManager.requestLocation()
}
// MARK: - LocationManagerDelegate
func didFindCurrentPosition(location: CLLocation) {
geolocationDataProperty.value = Geolocation(location: location)
}
}
The view controller:
class MyViewController: UIViewController {
let viewModel = MyViewModel()
init() {
super.init(nibName: nil, bundle: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
viewModel.geolocationData
.observe(on: UIScheduler())
.observeValues { geolocation in
debugPrint("GOT GEOLOCATION")
}
}
}
You already have a Property that holds the latest value emitted. Instead of using the property's signal use the producer. That way when you start the producer you will get the current value first (in none was sent you will get nil).
Cf. the documentation:
The current value of a property can be obtained from the value getter. The producer getter returns a signal producer that will send the property’s current value, followed by all changes over time. The signal getter returns a signal that will send all changes over time, but not the initial value.
So, regarding the code in the question, the viewDidLoad method should do something like the following:
viewModel.geolocationDataProperty
.producer
.start(on: UIScheduler())
.startWithValues { geolocation in
debugPrint("GOT GEOLOCATION")
}
Bindings from any kind of streams of values can be crated using the <~ operator but you can start to modify your code in the following way and see it is working fine, it is easier to debug :-)
class MyViewModel: LocationManagerDelegate {
let locationManager: LocationManager
let geolocationDataProperty = MutableProperty<Geolocation?>(nil)
init() {
geolocationDataProperty.signal.observeValues { value in
//use the value for updating the UI
}
// Location Management
locationManager = LocationManager()
locationManager.delegate = self
locationManager.requestLocation()
}
// MARK: - LocationManagerDelegate
func didFindCurrentPosition(location: CLLocation) {
geolocationDataProperty.value = Geolocation(location: location)
}
}
then we can try to use the binary operator for implementing the MVVM pattern, since having direct reference to the UI element inside the modelview is definitely a bad idea :-)
Take a look at this article, it is really interesting.
I am not sure if your map component is directly supporting the reactive framework.
see it has the ".reactive" extension and if it can used for updating the current position property
if it is present then the <~ operator can be used writing something similar to
map.reactive.position <~ viewModel.geolocationDataProperty
if it does not have the reactive extension then you can simply move this code in your viewcontroller
viewModel.geolocationDataProperty.signal.observeValues { value in
//use the value for updating the UI
}
If your problem is to have old value when it gets modified .You can try with Key-Value Observing . Add your property for value observation like
geolocationDataProperty.addObserver(self, forKeyPath: "value", options: [.new, .old], context: &observerContext)
Whenever your geolocationDataProperty gets modified ,you will receive that value in delegate method
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
let newValue = change?[.newKey] as? NSObject
let oldValue = change?[.oldKey] as? NSObject
//Add your code
}
I am working on an app which will display points of interest on a map around the users location. The problem is, when the app loads, the permission dialog disappears instantly before the user can allow or deny permissions.
My code looks like this:
override func viewDidLoad() {
super.viewDidLoad()
logo.animation = "zoomIn"
logo.duration = 1
logo.delay = 0.5
logo.animate()
formatView()
let locationManager = CLLocationManager()
let authStatus: CLAuthorizationStatus = CLLocationManager.authorizationStatus()
if authStatus == .notDetermined {
locationManager.requestWhenInUseAuthorization()
}
if authStatus == .denied || authStatus == .restricted {
showLocationServicesDeniedAlert()
return
}
}
But I have no idea what I'm doing wrong. Ive tried to follow other answers on here for similar problems but it seems like there are a few reasons this can happen and I have no idea which one my problem is I'm tearing hairs out!
You are assigning locationManager to a local variable of viewDidLoad which gets released immediately when viewDidLoad finishes. Instead, make locationManager a property of your ViewController:
var locationManager: CLLocationManager?
override func viewDidLoad() {
...
locationManager = CLLocationManager()
...
}
Try moving the the code in viewWillAppear as requestWhenInUseAuthorization needs to be called when view controller has appeared. And you could study the View Controller Life Cycle here to know more about their appearance, loading and everything.
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.
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).