I'm learning iOS development and faced strange problem. To get user permission for using location in viewController I'm defining location manager and requesting requestWhenInUseAuthorization().
Like this:
class ViewController: UIViewController, CLLocationManagerDelegate {
let lm = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
lm.requestWhenInUseAuthorization()
}
}
Then, in plist I'm setting proper text for permission dialog, like this:
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>We need your location</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>We need your location</string>
<key>NSLocationUsageDescription</key>
<string>We need your location</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>We need your location</string>
Everything works fine till I'm trying to change text for permission dialog. After changing it in plist I'm recompiling it but nothing changes in the app, I'm receiving dialog with old text. So my question - how can I change it?
P.S. Xcode 10.3 & iOS 12.4
Go to the ViewController.swift file and add the following line to import the CoreLocation framework.
import CoreLocation
Conform the ViewController class to the CLLocationManagerDelegate protocol. Change the class declaration line to
class ViewController: UIViewController, CLLocationManagerDelegate {
Add the following property
let locationMgr = CLLocationManager()
The CLLocationManager is the object that will give you the GPS coordinates. Next, implement the getLocation(_:) method.
#IBAction func getLocation(_ sender: Any) {
// 1
let status = CLLocationManager.authorizationStatus()
switch status {
// 1
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
return
// 2
case .denied, .restricted:
let alert = UIAlertController(title: "Location Services disabled", message: "Please enable Location Services in Settings", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(okAction)
present(alert, animated: true, completion: nil)
return
case .authorizedAlways, .authorizedWhenInUse:
break
}
// 4
locationManager.delegate = self
locationManager.startUpdatingLocation()
}
The authorizationStatus returns the current authorisation status.
The when in use authorisation get location updates while the app is in the foreground
When location services is disabled, the user will be shown an alert
Send location updates to the delegate, which is the View Controller.
Next, implement the CLLocationManager delegate methods.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let currentLocation = locations.last {
print("Current location: \(currentLocation)")
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error)
}
The current location is printed to the console.
An Error is generated and displayed when the location can't get updated.
To enable the permissions for location updates while the app is running a special key is needed. Open info.plist. Right-click and select Add Row. Enter the following Values.
In Info.plist
<key>NSLocationWhenInUse</key>
<string>This app needs access to your Location</string>
Build and Run the project, When the app ask for permission
Looks like I've found where problem was. But firs things first. If you are trying to localize your app - read carefully what xcode asks you. In my case it was deleting Info.plist file from root directory when I tried to localize it, then (obviously) xcode informed me that it can't make a build because of it can't find Info.plist file (which was deleted from root directory).
Solution
Move Info.plist from Base.lproj directory;
Make as written in this article;
Keys in strings file should be exactly as they are named in system. This won't work:
"SomKey" = "We need you location";
But this would:
"NSLocationWhenInUseUsageDescription" = "We need you location";
Related
My App relies on checking user location upon start.
On iOS 16, when the user enables the Developer Mode on Privacy Settings they can simulate any location on the device using a GPX file.
The device actually changes the location of the device and most apps that I use actually think my location is the one on the GPX file.
Is there a way to make sure the location is the actual user location or a simulated one ?
Is there a public API to check if Developer Mode is enabled ?
Is there a way to tell the location comes from the GPX file ?
Even if the Developer mode is turned off it takes restarting the device to pick up the actual location again , so not sure if there is a different and better solution to this.
Thank you.
Apple provides sourceInformation on each CLLocation object returned. Checking the isSimulatedBySoftware parameter will give a boolean response.
Core Location sets isSimulatedBySoftware to true if the system generated the location using on-device software simulation. You can simulate locations by loading GPX files using the Xcode debugger. The default value is false.
The code below is a full functioning example that will turn the screen red if a simulated location is detected. I have confirmed that toggling simulated locations on/off in Xcode will make the screen turn from green to red and back again.
import UIKit
import CoreLocation
class ViewController: UIViewController {
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
// setup location monitoring
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
}
}
extension ViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// list of locations that are simulated
let simulatedLocations = locations.filter { $0.sourceInformation?.isSimulatedBySoftware == true }
view.backgroundColor = simulatedLocations.count > 0 ? .red : .green
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
guard manager.authorizationStatus == .authorizedAlways || manager.authorizationStatus == .authorizedWhenInUse else { return }
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
}
Fist time when I install my iOS app on a device via xcode and location service is off, requestAlwaysAuthorization() shows "Turn on location services" that push user to device Settings to turn on location service, then user comes back to the app and would see "permission" alert(with three options always, while using and never). If user taps on always option then closes the app completely and turns location service off then opens app again, "Turn on location services" alert is not displayed. This is my code:
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
NotificationCenter.default.addObserver(self, selector: #selector(checkLocationService), name: Notification.Name.UIApplicationWillEnterForeground, object: nil)
}
#objc func checkLocationService() {
if CLLocationManager.locationServicesEnabled() {
switch CLLocationManager.authorizationStatus() {
case .denied, .notDetermined, .restricted:
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
case .authorizedWhenInUse, .authorizedAlways:
...
}
} else {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
}
}
I've added all three location keys in the Info.plist:
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>My app need your location to work</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>My app need your location to work</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>My app need your location to work</string>
I am testing on iOS 11 and 12 and I have no idea what is wrong.
You only request auth once... if the user grants permission then you dont need to ask again and if they refuse you can't prompt again. you need to handle it differently if denied. you push the user to the app settings in the settings menu and the user has to enable from there
switch CLLocationManager.authorizationStatus() {
case .notDetermined, .restricted:
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
case .authorizedWhenInUse, .authorizedAlways:
...
case .denied:
// present an alert advising the user that they need to go to the settings menu to enable the permissions as they have previously denied it.
// if the user presses Open Settings on the alert....
if let url = URL(string: UIApplicationOpenSettingsURLString) {
UIApplication.shared.open(url: url)
}
}
I want to check and see if the location is on and if it's not or user didn't give permission to use location, app quits (won't run).
Is there a way to do this as people are saying using exit(0) is not recommended and will make Apple sad. :D
you could put a view onto the screen (full width and height) with a label that tells the user that it's only possible to use the app with location services enabled. of course the user should not be possible to interact with this view in any way.
here is an example helper class:
import UIKit
import CoreLocation
class LocationHelper: NSObject, CLLocationManagerDelegate {
private static let sharedInstance = LocationHelper()
private var locationManager: CLLocationManager! {
didSet {
locationManager.delegate = self
}
}
private override init() {}
class func setup() {
sharedInstance.locationManager = CLLocationManager()
}
private func informUserToEnableLocationServices() {
let infoPopup = UIAlertController(title: "Location Services", message: "Sorry, but you have to enable location services to use the app...", preferredStyle: .Alert)
let tryAgainAction = UIAlertAction(title: "Try again", style: .Default) { (action) in
if CLLocationManager.authorizationStatus() != .AuthorizedWhenInUse {
self.informUserToEnableLocationServices()
}
}
infoPopup.addAction(tryAgainAction)
let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate
let rootViewController = appDelegate?.window?.rootViewController
rootViewController?.presentViewController(infoPopup, animated: true, completion: nil)
}
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .NotDetermined:
locationManager.requestWhenInUseAuthorization()
case .AuthorizedWhenInUse:
break
default:
informUserToEnableLocationServices()
}
}
}
simply call LocationHelper.setup() after the app launched and the class should handle the rest...
Apple does not like exit(0) for a reason. I would highly recommend not terminating the app yourself. Maybe you could let the user use the app with limited features? Another option would be to make an alert with no actions, or actions that don't do anything.
I'm trying to get a fix on the user's current location tied into displaying some data on a Map. My code is as follows for my watch kit extension.
import Foundation
import CoreLocation
class InterfaceController: WKInterfaceController, CLLocationManagerDelegate {
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
dLog("Did get a location.")
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
dLog("Did fail to retrieve a location.")
}
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
print(status)
}
#IBAction func btnRequestLocation() {
let locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.requestLocation()
}
I've also made sure to include the CoreLocation framework under the WatchKit extension Target Linked Frameworks and Libraries. However, running this code on both the watch simulator and a real Apple watch paired with an iOS app, these delegate callbacks never get fired. I'm not sure if I'm missing a step somewhere that would be preventing me from getting the location. I've tried resetting the location and privacy and I even get the request that my app needs location permissions. I hit "Allow" but no location is ever sent to the Apple Watch extension. Any help would be appreciated.
EDIT:
I've also taken some additional steps for iOS 9.
Project -> iOS Target -> Background Modes
- Location Updates is checked
- iOS Target also includes the "Required Background Modes" key in the plist file.
Here is the property list for the iOS App.
UPDATE
Seems this might be a common problem isolated in watch OS2.
https://forums.developer.apple.com/thread/14828
You are requesting requestAlwaysAuthorization so you need to make sure you have enabled Locations background mode and also set allowsBackgroundLocationUpdates=TRUE (for iOS9).
However, I would recommend requesting requestWhenInUseAuthorization instead since you are just calling for a onetime location update via requestLocation().
I am trying to implement a basic map view and add a user's current location to the map as an annotation. I have added the requestwheninuse key to my info.plist and imported coreLocation.
In my view controller's did load method, I have the following:
locManager.requestWhenInUseAuthorization()
var currentLocation : CLLocation
if(CLLocationManager.authorizationStatus() == CLAuthorizationStatus.AuthorizedWhenInUse){
currentLocation = locManager.location
println("currentLocation is \(currentLocation)")
}
else{
println("not getting location")
// a default pin
}
I am getting the prompt re. permission to retrieve location. As this is happening I am getting my print saying not getting location, obviously because this runs before the user gets a chance to tap OK. If I elave the app and come back in I can retrieve the location and add it to the map. However, I want when the user taps OK the first time to be able to then grab the current location and add it to the map there and then. How can I achieve this? I have the following method for adding a pin:
func addPin(location2D: CLLocationCoordinate2D){
self.mapView.delegate = self
var newPoint = MKPointAnnotation()
newPoint.coordinate = location2D
self.mapView.addAnnotation(newPoint)
}
In order to do that, you need to implement the methoddidChangeAuthorizationStatus for your location manager delegate which is called shortly after CLLocationManager is initialized.
First, at the top of the file don't forget to add : import CoreLocation
To do that, in your class where you are using the location, add the delegate protocol. Then in the viewDidLoad method (or applicationDidFinishLaunching if you are in the AppDelegate) initialize your location manager and set its delegate property to self:
class myCoolClass: CLLocationManagerDelegate {
var locManager: CLLocationManager!
override func viewDidLoad() {
locManager = CLLocationManager()
locManager.delegate = self
}
}
Finally, implement the locationManager(_ didChangeAuthorizationStatus _) method in the body of your class that you declared previously, this method will be called when the status of the authorization is changed, so as soon as your user clicked the button. You can implement it like this:
private func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
// If status has not yet been determied, ask for authorization
manager.requestWhenInUseAuthorization()
break
case .authorizedWhenInUse:
// If authorized when in use
manager.startUpdatingLocation()
break
case .authorizedAlways:
// If always authorized
manager.startUpdatingLocation()
break
case .restricted:
// If restricted by e.g. parental controls. User can't enable Location Services
break
case .denied:
// If user denied your app access to Location Services, but can grant access from Settings.app
break
default:
break
}
}
Swift 4 - New enum syntax
For Swift 4, just switch the first letter of each enum case to lowercase (.notDetermined, .authorizedWhenInUse, .authorizedAlways, .restricted and .denied)
That way you can handle each and every case, wether the user just gave its permission or revoked it.