Why is 'CLLocationManager.locationServicesEnabled()' true by default? - ios

I have noticed the following with a plain, completely new project in Xcode.
If, in the ViewController.swift file I import CoreLocation, and then in the viewDidLoad method I add...
print(CLLocationManager.locationServicesEnabled())
..., when the app runs in simulator Xcode prints out true. I would have thought that location services would be disabled by default, but as you can see for yourself, the opposite is true. If I wanted I could add some more code to gather location information about the user, and all this without ever having to ask for permission.
Can anybody explain why this is?

As far as I know, CLLocationManager.locationServicesEnabled() will return whether location Services are enabled on the device, not just for that one app. So even if location Services are disabled for that app, if they are enabled for the device, I think that will still return true, as per the documentation: https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/#//apple_ref/occ/clm/CLLocationManager/locationServicesEnabled
In my app, I set it up like this:
//check if location services are enabled at all
if CLLocationManager.locationServicesEnabled() {
switch(CLLocationManager.authorizationStatus()) {
//check if services disallowed for this app particularly
case .Restricted, .Denied:
print("No access")
var accessAlert = UIAlertController(title: "Location Services Disabled", message: "You need to enable location services in settings.", preferredStyle: UIAlertControllerStyle.Alert)
accessAlert.addAction(UIAlertAction(title: "Okay!", style: .Default, handler: { (action: UIAlertAction!) in UIApplication.sharedApplication().openURL(NSURL(string:UIApplicationOpenSettingsURLString)!)
}))
presentViewController(accessAlert, animated: true, completion: nil)
//check if services are allowed for this app
case .AuthorizedAlways, .AuthorizedWhenInUse:
print("Access! We're good to go!")
//check if we need to ask for access
case .NotDetermined:
print("asking for access...")
manager.requestAlwaysAuthorization()
}
//location services are disabled on the device entirely!
} else {
print("Location services are not enabled")
}
Good luck!

Swift 3.1 function to return status and error message
func isLocationEnabled() -> (status: Bool, message: String) {
if CLLocationManager.locationServicesEnabled() {
switch(CLLocationManager.authorizationStatus()) {
case .notDetermined, .restricted, .denied:
return (false,"No access")
case .authorizedAlways, .authorizedWhenInUse:
return(true,"Access")
}
} else {
return(false,"Turn On Location Services to Allow App to Determine Your Location")
}
}

Related

SwiftyStoreKit IAP Purchasing but not remembering it's settings after launch

I am trying to integrate a very basic single IAP in my game, here I am calling from my GameScene
let alert = UIAlertController(title: "Upgrade", message: "Would you like to remove ads?", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Remove Ads", style: .default, handler: { action in
print("Pressed Remove Ads")
GameViewController().buytheIAP()
}))
alert.addAction(UIAlertAction(title: "Restore Purchases", style: .default, handler: { action in
print("Pressed Restore")
GameViewController().restoretheIAP()
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { action in
print("Pressed Cancel")
}))
view?.window?.rootViewController?.present(alert, animated: true, completion: nil)
}
Those methods are called correctly, and refer to these inside GameViewController.swift;
func buytheIAP(){
iAPHelper.purchase()
print("OK Lets upgrade")
}
func restoretheIAP(){
iAPHelper.restorePurchase()
print("OK Lets restore")
}
func restoreDidSucceed() {
UserDefaults.setValue(true, forKey: iAPHelper.productID)
//this should have something like hide banner etc.
bannerView.isHidden = true
}
func purchaseDidSucceed() {
UserDefaults.setValue(true, forKey: iAPHelper.productID)
//as above this should have something like hide banner etc.
bannerView.isHidden = true
print("Purchased upgrade ENJOYYYYYYYY")
}
func nothingToRestore() {
}
func paymentCancelled() {
}
The test IAP goes through, it gets the correct information from the app store, and I use my sandbox details to purchase, it goes through correctly with a successful purchase message. However, the bannerView does not hide, and more importantly, upon restarting the game again, everything is forgotten and the game thinks nothing has been purchased. I am guessing it has to be some sort of check that is missing.
I have this in my viewDidLoad
if userDefaults.bool(forKey: iAPHelper.productID) {
bannerView.isHidden = true
print("It is purchased, so DO NOT show the ads")
} else{
bannerView.adSize = getAdaptiveSize()
bannerView.adUnitID = "ca-app-pub-3940256099942544/2934735716"
bannerView.delegate = self
bannerView.rootViewController = self
bannerView.load(GADRequest())
addBannerViewToView(bannerView)
print("Not purchased, so show the ads")
}
And it ALWAYS shows print("Not purchased, so show the ads")
The IAPHelper file, for purchasing is;
func purchase() {
SwiftyStoreKit.purchaseProduct(productID, quantity: 1, atomically: true) { [self] result in
switch result {
case .success:
delegate?.purchaseDidSucceed()
print("OK It's purchased")
case .error(let error):
switch error.code {
case .unknown: print("Unknown error. Please contact support")
case .clientInvalid: print("Not allowed to make the payment")
case .paymentCancelled:
delegate?.paymentCancelled()
case .paymentInvalid: print("The purchase identifier was invalid")
case .paymentNotAllowed: print("The device is not allowed to make the payment")
case .storeProductNotAvailable: print("The product is not available in the current storefront")
case .cloudServicePermissionDenied: print("Access to cloud service information is not allowed")
case .cloudServiceNetworkConnectionFailed: print("Could not connect to the network")
case .cloudServiceRevoked: print("User has revoked permission to use this cloud service")
default: print((error as NSError).localizedDescription)
}
}
}
}
}
And the log DOES show print("OK It's purchased") after the initial purchase - so I am struggling to see what is going wrong.
The IAP delegate functions are not (guaranteed to be) called on the UI/main thread, that's why your view doesn't hide.
Don't you see some iOS warning saying that you try to set a UIView property on non-main thread?
The fact that your custom purchase info was not saved in UserDefaults could be caused by killing the app prematurely from Xcode.
(UserDefaults does take of saving what's being set during normal app flow.)
OK, so I figured the answer, there was a missing line in my AppDelegate.swift section didFinishLaunchingWithOptions
UserDefaults.standard.register(defaults: ["adRemove" : false])
I renamed userDefaults.bool(forKey: iAPHelper.productID) to make it easier to use / understand. So in my original post that has been replaced by UserDefaults.standard.register(defaults: ["adRemove" : false]) instead.
You can then use this anywhere to check, such as;
if !userDefaults.bool(forKey: "adRemove") {
// Do something here
}
Hope this helps anyone in future with the same question!

Denied Contact access (Contacts framework, iOS)

I am using Contacts framework. I am having trouble enabling contacts if the user has switched it to off in the settings screen: Settings app -> App name -> Turn Contacts off
If a user turns off the app Contact permissions in the settings screen, is it possible to present a dialog to allow them to turn Contacts on? It seems as if access = always false. Are we not allowed access once the user turns off Contacts via settings?
Here is my relevant code:
func requestForAccess(completionHandler: #escaping (_ accessGranted: Bool) -> Void) {
let authorizationStatus = CNContactStore.authorizationStatus(for: CNEntityType.contacts)
switch authorizationStatus {
case .authorized:
completionHandler(true)
case .denied, .notDetermined:
self.contactStore.requestAccess(for: CNEntityType.contacts, completionHandler: { (access, accessError) -> Void in
if access {
completionHandler(access)
}
else {
//Access is always false
if authorizationStatus == CNAuthorizationStatus.denied {
completionHandler(false)
}
}
})
default:
completionHandler(false)
}
}
No, once the user has denied access to Contacts, you'll need to direct them to the Settings app.
You can do this by displaying a UIAlertViewController, and opening the settings app via an alertAction
UIApplication.shared.open(URL(string: UIApplicationOpenSettingsURLString)!)

Alert Box Asking User to Grant Location Privileges is not showing

I am just getting started with SWIFT programming on iOS. However I have good experience in other languages such as JAVA, C# and PHP. That said, I am running into a little bit of a problem. I am creating a simple application that will show a user information based on their geographical location. I am following the tutorial listed here.
However, since a lot of things have changed between Swift 2.x and 3.x I have had to modify his code. Not too hard and so far I have gotten it to compile. However, I am never asked by the application to allow me to use the device's location.
I have followed the directions Apple listed here about putting "NSLocationAlwaysUsageDescription" in the Info.plist. However I am never prompted. The relevant piece of code that should be showing this alert is listed below.
Any help would be greatly appreciated. Thanks.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// 1. status is not determined
if CLLocationManager.authorizationStatus() == .notDetermined {
locationManager.requestAlwaysAuthorization()
}
// 2. authorization were denied
else if CLLocationManager.authorizationStatus() == .denied {
let controller = UIAlertController(title: "Error!", message: "Location services were previously denied. Please enable location services for this app in Settings.", preferredStyle: .alert)
let alertAction = UIAlertAction(title: "Dismiss", style: .destructive) { (action) in
print("Dismiss button tapped!")
}
controller.addAction(alertAction)
}
// 3. we do have authorization
else if CLLocationManager.authorizationStatus() == .authorizedAlways {
locationManager.startUpdatingLocation()
}
}
Your code is working fine you just need to present your alert:
self.present(controller, animated: true, completion: nil)
And also take a look at my answer here how you can use switch for this types of code.

Microphone Access Prompt Not Being Presented

In my audio recording application I'm trying to ask the user for access to the microphone. However, I keep getting the .NotDetermined case and no prompt. The documentation states that the Not Determined case will prompt the user but that doesn't seem to be the case.
#IBAction func recordTapped(recordButton: UIButton!){
let microPhoneStatus = AVCaptureDevice.authorizationStatusForMediaType(AVMediaTypeAudio)
switch microPhoneStatus {
case .Authorized:
// Has access
println("access")
vibrate()
recordNow()
case .Denied:
// No access granted
println("denied")
case .Restricted:
// Microphone disabled in settings
println("mic disabled in settings")
case .NotDetermined:
// Didn't request access yet
AVCaptureDevice.requestAccessForMediaType(AVMediaTypeAudio, completionHandler: nil)
println("Not determined")
}
}

Detect permission of camera in iOS

I am developing a very simple video app. I use the official control: UIImagePickerController.
Here is the problem. When presenting the UIImagePickerController for the first time, the iOS will ask for the permission. The user can click yes or no. If the user clicks no, the control is not dismissed. Instead, if the user keeps clicking the start button, the timers go on while the screen is always black, and the user can't stop the timers or go back. The only thing the user can do is to kill the app. The next time the UIImagePickerController is presented, it is still a black screen and the user can't go back if clicking start.
I was wondering if it's a bug. Is there any way we can detect the permission of the camera so that we can decide to show the UIImagePickerController or not?
Check the AVAuthorizationStatus and handle the cases properly.
NSString *mediaType = AVMediaTypeVideo;
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType];
if(authStatus == AVAuthorizationStatusAuthorized) {
// do your logic
} else if(authStatus == AVAuthorizationStatusDenied){
// denied
} else if(authStatus == AVAuthorizationStatusRestricted){
// restricted, normally won't happen
} else if(authStatus == AVAuthorizationStatusNotDetermined){
// not determined?!
[AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) {
if(granted){
NSLog(#"Granted access to %#", mediaType);
} else {
NSLog(#"Not granted access to %#", mediaType);
}
}];
} else {
// impossible, unknown authorization status
}
Swift 4 and newer
Make sure to:
import AVFoundation
The code below checks for all possible permission states:
let cameraMediaType = AVMediaType.video
let cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: cameraMediaType)
switch cameraAuthorizationStatus {
case .denied: break
case .authorized: break
case .restricted: break
case .notDetermined:
// Prompting user for the permission to use the camera.
AVCaptureDevice.requestAccess(for: cameraMediaType) { granted in
if granted {
print("Granted access to \(cameraMediaType)")
} else {
print("Denied access to \(cameraMediaType)")
}
}
}
Since iOS 10 you need to specify
NSCameraUsageDescription key in your Info.plist to be able ask for camera access, otherwise your app will crash at runtime. See APIs Requiring Usage Descriptions.
An interesting sidenote from Apple Developer forum:
The system actually kills your app if the user toggles your app's
access to camera in Settings. The same applies to any protected
dataclass in the Settings→Privacy section.
Swift Solution
extension AVCaptureDevice {
enum AuthorizationStatus {
case justDenied
case alreadyDenied
case restricted
case justAuthorized
case alreadyAuthorized
case unknown
}
class func authorizeVideo(completion: ((AuthorizationStatus) -> Void)?) {
AVCaptureDevice.authorize(mediaType: AVMediaType.video, completion: completion)
}
class func authorizeAudio(completion: ((AuthorizationStatus) -> Void)?) {
AVCaptureDevice.authorize(mediaType: AVMediaType.audio, completion: completion)
}
private class func authorize(mediaType: AVMediaType, completion: ((AuthorizationStatus) -> Void)?) {
let status = AVCaptureDevice.authorizationStatus(for: mediaType)
switch status {
case .authorized:
completion?(.alreadyAuthorized)
case .denied:
completion?(.alreadyDenied)
case .restricted:
completion?(.restricted)
case .notDetermined:
AVCaptureDevice.requestAccess(for: mediaType, completionHandler: { (granted) in
DispatchQueue.main.async {
if granted {
completion?(.justAuthorized)
} else {
completion?(.justDenied)
}
}
})
#unknown default:
completion?(.unknown)
}
}
}
And then in order to use it you do
AVCaptureDevice.authorizeVideo(completion: { (status) in
//Your work here
})
As an addition to the answer from #Raptor the following should be mentioned. You may receive the following error starting with iOS 10: This application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread. This can lead to engine corruption and weird crashes.
To fix this, make sure you handle the results from the main thread as follows (Swift 3):
private func showCameraPermissionPopup() {
let cameraMediaType = AVMediaTypeVideo
let cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(forMediaType: cameraMediaType)
switch cameraAuthorizationStatus {
case .denied:
NSLog("cameraAuthorizationStatus=denied")
break
case .authorized:
NSLog("cameraAuthorizationStatus=authorized")
break
case .restricted:
NSLog("cameraAuthorizationStatus=restricted")
break
case .notDetermined:
NSLog("cameraAuthorizationStatus=notDetermined")
// Prompting user for the permission to use the camera.
AVCaptureDevice.requestAccess(forMediaType: cameraMediaType) { granted in
DispatchQueue.main.sync {
if granted {
// do something
} else {
// do something else
}
}
}
}
}
Specify NSCameraUsageDescription key in Info.plist first.
Then check AVAuthorizationStatus if Authorised then present the UIImagePickerController. It will work.
Swift: Using AVFoundation
Add AVFoundation to Target -> Build Phases -> Link Binary with Libraries.
import AVFoundation on ViewController.
On Info.plist, Add the following:
On View Controller:
#IBAction func cameraButtonClicked(sender: AnyObject) {
let authorizationStatus = AVCaptureDevice.authorizationStatusForMediaType(AVMediaTypeVideo)
print(authorizationStatus.rawValue)
if AVCaptureDevice.authorizationStatusForMediaType(AVMediaTypeVideo) == AVAuthorizationStatus.Authorized{
self.openCameraAfterAccessGrantedByUser()
}
else
{
print("No Access")
dispatch_async(dispatch_get_main_queue()) { [unowned self] in
AVCaptureDevice.requestAccessForMediaType(AVMediaTypeVideo, completionHandler: { (granted :Bool) -> Void in
if granted == true
{
// User granted
self.openCameraAfterAccessGrantedByUser()
}
else
{
// User Rejected
alertToEncourageCameraAccessWhenApplicationStarts()
}
});
}
}
//Open camera
func openCameraAfterAccessGrantedByUser()
{
if(UIImagePickerController .isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera)){
self.cameraAndGalleryPicker!.sourceType = UIImagePickerControllerSourceType.Camera
cameraAndGalleryPicker?.delegate = self
cameraAndGalleryPicker?.allowsEditing = false
cameraAndGalleryPicker!.cameraCaptureMode = .Photo
cameraAndGalleryPicker!.modalPresentationStyle = .FullScreen
presentViewController(self.cameraAndGalleryPicker!, animated: true, completion: nil)
}
else
{
}
}
//Show Camera Unavailable Alert
func alertToEncourageCameraAccessWhenApplicationStarts()
{
//Camera not available - Alert
let cameraUnavailableAlertController = UIAlertController (title: "Camera Unavailable", message: "Please check to see if it is disconnected or in use by another application", preferredStyle: .Alert)
let settingsAction = UIAlertAction(title: "Settings", style: .Destructive) { (_) -> Void in
let settingsUrl = NSURL(string:UIApplicationOpenSettingsURLString)
if let url = settingsUrl {
dispatch_async(dispatch_get_main_queue()) {
UIApplication.sharedApplication().openURL(url)
}
}
}
let cancelAction = UIAlertAction(title: "Okay", style: .Default, handler: nil)
cameraUnavailableAlertController .addAction(settingsAction)
cameraUnavailableAlertController .addAction(cancelAction)
self.window?.rootViewController!.presentViewController(cameraUnavailableAlertController , animated: true, completion: nil)
}

Resources