I'm trying to set up an on boarding where I'm asking the user for a few permissions including: Location, Notifications and Camera. I have 3 different View Controllers set up, each asking for one of the permissions and explaining why. On each of the view controllers I have a button at the bottom that says "Grant Permission".
When the user clicks the button I want the permission dialogue to pop up, and once the user clicks allow I want to transition to the next view controller.
Here is what I have right now:
class OnboardingStep2:UIViewController{
override func viewDidLoad() {
self.view.backgroundColor = StyleKit.orangeWhite()
}
#IBAction func getPermission(sender: AnyObject) {
dispatch_sync(dispatch_get_main_queue()) {
let locManager = CLLocationManager()
locManager.requestAlwaysAuthorization()
}
if (CLLocationManager.authorizationStatus() == CLAuthorizationStatus.Authorized) {
self.performSegueWithIdentifier("goToStep3", sender: self)
}
}
}
I've tried using dispatch to queue up the tasks, but when using async the permission dialogue pops up and then immediately it closes because the authorization check is run (I'm assuming). Using dispatch_sync, the dialogue is never shown.
What is the best way to do this, I want the permission dialogue to pop up first and once the user clicks allow i want to segue.
Conform to the CLLocationManagerDelegate
Then call this:
Swift 3.0
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
manager.requestLocation()
case .authorizedAlways, .authorizedWhenInUse:
// Do your thing here
default:
// Permission denied, do something else
}
}
Swift 2.2
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .NotDetermined:
manager.requestLocation()
case .AuthorizedAlways, .AuthorizedWhenInUse:
// Do your thing here
default:
// Permission denied, do something else
}
}
Swift 5
Implement CLLocationManagerDelegate
and this function:
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch CLLocationManager.authorizationStatus() {
case .notDetermined:
// User has not yet made a choice
case .denied:
// User has explicitly denied authorization
case .restricted:
// This application is not authorized to use location services.
case .authorized, .authorizedAlways, .authorizedWhenInUse:
// User has granted authorization
default:
// Other
}
}
Related
I don't know can I use this functionality in my UI tests on iOS, but I try it, an have problem with this.
In my UI tests I can choose Allow tracking for my app or I can decline tracking, but after all these actions, I want checkout status IDFA via ATTrackingManager.AuthorizationStatus, but this method always returns notDetermined. If I go to Settings > Privacy > Tracking, here I see that settings applied correctly (switch Allow App To Request To Track is on and switch for my app in right state (on or off)).
I don't have any idea why I recieve wrong AuthorizationStatus.
Here is my code in my XCTestCase:
import AppTrackingTransparency
enum TrackingStatus {
case authorized
case denied
case notDetermined
}
func map(_ status: ATTrackingManager.AuthorizationStatus) -> TrackingStatus {
switch ATTrackingManager.trackingAuthorizationStatus {
case .notDetermined:
return .notDetermined
case .authorized:
return .authorized
default:
return .denied
}
}
func advertisingTrackingStatusCheckout(status: TrackingStatus) {
print("IDFA status: \(ATTrackingManager.trackingAuthorizationStatus)")
var currentTrackingStatus: TrackingStatus {
return map(ATTrackingManager.trackingAuthorizationStatus)
}
guard currentTrackingStatus == status else {
XCTFail("IDFA status: \(currentTrackingStatus), expected: \(status)")
return
}
}
After settings IDFA status in my UI test, i call this method, ex. advertisingTrackingStatusCheckout(status: TrackingStatus.denied)
But it always returns notDetermined.
It behaviors have only one exception: If I manually set switch Allow App To Request To Track to off-state, calling the ATTrackingManager.trackingAuthorizationStatus will returns denied.
Delete the App, And call your function in sceneDidBecomeActive with delay. So once your app become active then it will shown. Am facing the same issue now its resolved. Like this
func sceneDidBecomeActive(_ scene: UIScene) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.requestPermission()
}
}
func requestPermission() {
if #available(iOS 14, *) {
ATTrackingManager.requestTrackingAuthorization { status in
switch status {
case .authorized:
// Tracking authorization dialog was shown
// and we are authorized
print("Authorized Tracking Permission")
// Now that we are authorized we can get the IDFA
case .denied:
// Tracking authorization dialog was
// shown and permission is denied
print("Denied Tracking Permission")
case .notDetermined:
// Tracking authorization dialog has not been shown
print("Not Determined Tracking Permission")
case .restricted:
print("Restricted Tracking Permission")
#unknown default:
print("Unknown Tracking Permission")
}
}
} else {
// Fallback on earlier versions
}
}
I'm using the MapKit in my Swift iOS app.
The thing is that I'm requesting the permission to access the user location when the app is in use, but the first time I run the app in my iPhone, it stays frozen in the splash screen, because the permission request don't popup, but then, if I press the home button, the popup appears to ask for permission. And if I accept then, the next run the app works properly, but it shouldn't work like this.
So in the code, the debugger crashes here because he cannot get the permission:
let initialLocation:CLLocation = CLLocation(latitude: (locationManager.location?.coordinate.latitude)!, longitude: (locationManager.location?.coordinate.longitude)!)
Indicating the next issue: Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1000b5d00)
So, I'm already asking the permissions in the viewWillAppear method:
let locationManager = CLLocationManager()
// Ask for Authorisation from the User.
// locationManager.requestAlwaysAuthorization()
// For use in foreground
locationManager.requestWhenInUseAuthorization()
//locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
//locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
mapView.showsUserLocation = true
}
And I also have the entry in the Info.plist: Privacy - Location When In Use Usage Description.
Why is the popup not showing in the foreground but in the background?
Thanks in advance for your help.
Cheers
EDIT:
I have an splash screen with the logo before the map view. Can this be the problem?
EDIT 2 in answer to #Dan Clark
Ok, I've added this check in the viewDidLoad as below:
EDIT 3
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
print("viewdidload")
if CLLocationManager.authorizationStatus() != .AuthorizedWhenInUse // Check authorization for location tracking
{
print("requestingautorization")
locationManager.requestWhenInUseAuthorization()
print("afterrequestingauthorization")
// LocationManager will callbackdidChange... once user responds
} else {
print("startupdatinglocation")
addPins(locationManager)
}
}
But the popup requesting the authorization is not appearing :( I've got both prints before and after but the popup is not showing.
I also added the function you wrote me, in the same class.
#nonobjc func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
print("instatuscheck")
switch status
{
case .AuthorizedWhenInUse:
print("statusauthorized")
addPins(manager)
default:
print("statusdefault")
manager.requestWhenInUseAuthorization()
// User denied access, handle as appropriate
}
}
But I don't have it clear... this function will be called automatically when the authorization status changes?
Thanks again for your help :)
The problem is that it can take a while for you to get authorized by LocationManager after you make the request. Therefore, on your first try you don't have authorization before reaching the closure after your request. I've addressed this by testing for authorization and, if I don't have it, putting in the request and then waiting for the callback to didChangeAuthorizationStatus before starting location updates. If I already do have authorization, I immediately start location updates.
By the second time you run the app, you have the authorization so the delay doesn't occur and you're OK to go.
To try this approach, include this section in your ViewDidLoad (I'm assuming that you don't need to run this whenever your view appears, but only when it first starts):
if CLLocationManager.authorizationStatus() != .authorizedAlways // Check authorization for location tracking
{
locationManager.requestAlwaysAuthorization() // LocationManager will callbackdidChange... once user responds
} else {
locationManager.startUpdatingLocation()
}
And add this delegate function to your class to be called by LocationManager once you're authorized:
// If we've been authorized to use location, start the processes, otherwise abort the operation
// since we can't proceed without locations
#nonobjc func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status
{
case .authorizedAlways:
locationManager.startUpdatingLocation()
default:
// User denied access, handle as appropriate
}
}
Here's the code I use to instantiate / configure the locationManager:
lazy var locationManager: CLLocationManager = {
[unowned self] in
var _locationManager = CLLocationManager()
_locationManager.delegate = self
_locationManager.desiredAccuracy = [a user setting in my app]
_locationManager.allowsBackgroundLocationUpdates = true
_locationManager.pausesLocationUpdatesAutomatically = false // So doesn't shut off if user stops to rest
_locationManager.activityType = .fitness
_locationManager.distanceFilter = Double([a user setting in my app])
return _locationManager
}()
This has been working for me so hopefully it will help.
It is my general ViewController class , I research the this question iOS app doesn't ask for location permission , I already have NSLocationAlwaysUsageDescription and NSLocationWhenInUseUsageDescription
#IBOutlet weak var ownMapView: MKMapView!
let locationManager : CLLocationManager = CLLocationManager();
override func viewDidLoad() {
super.viewDidLoad();
self.locationManager.delegate = self;
self.locationManager.requestWhenInUseAuthorization();
self.locationManager.startUpdatingLocation();
}
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
guard status == .AuthorizedWhenInUse else
{
print("Location not using");
return;
}
print("Location using.");
ownMapView.showsUserLocation = true;
}
Even I added NSLocationAlwaysUsageDescription and NSLocationWhenInUseUsageDescription this is not working for me . It prints only "Location not using"
How do I request correctly ?
To understand this correctly check the CLLAuthorizationStatus doc:
https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/c/tdef/CLAuthorizationStatus
CLAuthorizationStatus has several possible values:
NotDetermined - User has not yet made a decision to allow/deny to use location
Restricted - App is restricted to use location
Denied - User has denied to use location.
AuthorizedAlways - This app is authorized to start location services at any time.
AuthorizedWhenInUse - App is authorized to start most location services while running in the foreground
You are checking only "AuthorizedWhenInUse" status but you launch first its status is NotDetermined.
You can further check the status like below:
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if status == .NotDetermined
{
print ("Loction use not determined")
return
}
if status == .Denied
{
print ("Location determined")
return
}
guard status == .AuthorizedWhenInUse else
{
print("Location not using");
return;
}
print("Location using.");
ownMapView.showsUserLocation = true;
}
Have us added NSLocationAlwaysUsageDescription & NSLocationWhenInUseUsageDescription in plist? . Add these and it will work .
My app is a geolocation based app. I've implemented to pop up a UIAlertview as soon as some users press "Don't allow location service" button to guide them to settings again to turn on the service.
The problem I'm facing is that when user finally turns on the button, the data are not being loaded to the tableview since my data calling functions are in viewdidload and viewdidappear. Is there a way to call those functions again?
I did something like below and it totally crashes my app:
extension ExploreViewController: CLLocationManagerDelegate {
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .Denied:
// Changed status to denied
self.locationAlert = UIAlertView(title: "Location Services Permission Needed", message: "Location service needs to be turned on to use Peek! Please press setting button below and turn the service on!", delegate: self, cancelButtonTitle: "Settings")
locationAlert.show()
break
case .AuthorizedWhenInUse:
self.viewDidLoad()
self.viewDidAppear(true)
break
default
break
}
When I was doing this way, it was kept calling viewdidload like million times before it crashed the app. Any advices are appreciated
Simply move your data calling functions outside of viewDidLoad and viewDidAppear:
Instead of
override func viewDidLoad() {
// do some stuff
}
write
override func viewDidLoad() {
super.viewDidLoad()
doSomeStuff()
}
func doSomeStuff() {
// do some stuff
}
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
// do your current logic
doSomeStuff()
}
Never never never never call viewDidLoad or viewDidAppear (except, in the latter case, to call super). They are messages sent by the runtime to you, to report stages in the life of the view controller.
func myCodeToRun() {
//put all the code you want to run here
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
myCodeToRun()
}
extension ExploreViewController: CLLocationManagerDelegate {
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .Denied:
// Changed status to denied
self.locationAlert = UIAlertView(title: "Location Services Permission Needed", message: "Location service needs to be turned on to use Peek! Please press setting button below and turn the service on!", delegate: self, cancelButtonTitle: "Settings")
locationAlert.show()
break
case .AuthorizedWhenInUse:
myCodeToRun()
break
default
break
}
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.