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.
Related
Im having a trouble to add locationManager.requestWhenInUseAuthorization()
What i expect : popup asking for location permission to user
What heppend : nothing show
Setting : location setting in iphone : never
Plist:
LocationAlwaysAndWhenInuseUsageDescription
LocationAlwaysUsageDescription
LocationWhenInUseUsageDescription
import Foundation
import UIKit
import CoreLocation
class NoobNoobVC: BaseVC, CLLocationManagerDelegate {
var locationManager: CLLocationManager!
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.distanceFilter = 5
locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
//locationManager.startUpdatingHeading()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
locationManager.requestWhenInUseAuthorization()
}
}
Documentation says:
You may call requestWhenInUseAuthorization() whenever the current authorization status is not determined (CLAuthorizationStatus.notDetermined).
&
If the initial authorization status is anything other than CLAuthorizationStatus.notDetermined, this method does nothing and doesn't call the locationManager(_:didChangeAuthorization:) method.
You have to use authorizationStatus() to check the authorization status and then take a proper action like - inform the user what's wrong, ... and what can be done about it. You can call requestWhenInUseAuthorization() only if it's .notDetermined. This class function is marked as deprecated and if you're targeting iOS >= 14.0 you should use the instance variant.
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
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.
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)
{
}
}
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.