Crash when trying to access Contacts Framework in iOS 10 - ios

I am trying to access the Contacts framework so I can save a new contact into the users Addressbook. I have the following code ...
import Contacts
import ContactsUI
at the head of the file and include a method ...
func requestForAccess(completion: (granted: Bool) -> ()) {
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_BACKGROUND.rawValue), 0), {
let authStatus = CNContactStore.authorizationStatusForEntityType(.Contacts)
switch authStatus {
case .Authorized:
completion(granted: true)
case .Denied, .NotDetermined:
// CRASH HERE before the completion block is called, after calling
// requestAccessForEntityType
self.contactStore.requestAccessForEntityType(.Contacts, completionHandler: { (access, accessError) -> () in
if access {
completion(granted: access)
} else {
if authStatus == .Denied {
dispatch_async(dispatch_get_main_queue(), { () -> () in
let msg = "\(accessError!.localizedDescription)\n\nPlease allow the app to access your contacts through the Settings."
//self.alertService.showAlert(msg)
})
}
}
})
default:
completion(granted: false)
}
})
}
At the line marked, there is a SIABRT crash. Winding down the stack and running po $arg1 throws up the following ...
error: Couldn't materialize: couldn't read the value of register x0
Errored out in Execute, couldn't PrepareToExecuteJITExpression
I have the necessary line in Info.plist in the root dict
<key>NSContactsUsageDescription</key>
<string>We need to access Contacts to import entries direct from the app.</string>
I have also included Contacts.framework and ContactsUI.framework in 'Linked Frameworks and Libraries' in the target properties (General)

I have added plist entry for contact description and modified your code a little bit to use in my swift 3.0 project, following code is working like a charm on my iOS 10.0.2
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated);
self.requestForAccess { (gotAccess) in
print(gotAccess)
}
}
func requestForAccess(_ completion: #escaping (_ granted: Bool) -> ()) {
DispatchQueue.global(qos: DispatchQoS.QoSClass.background).async {
let authStatus = CNContactStore.authorizationStatus(for: .contacts)
switch authStatus {
case .authorized:
completion(true)
case .denied, .notDetermined:
self.contactStore.requestAccess(for: .contacts, completionHandler: { (access, accessError) -> () in
if access {
completion(access)
} else {
if authStatus == .denied {
DispatchQueue.main.async {
let msg = "\(accessError!.localizedDescription)\n\nPlease allow the app to access your contacts through the Settings."
print(msg)
}
}
}
})
default:
completion(false)
}
}
}

Ok so I figured it out - it wasn't anything obvious I'm afraid.
For some reason XCode had replicated my Info.plist and was parsing and loading a copy rather than the Info.plist in my root directory. Although I added the NSContactsUsageDescription to one file, it was reading the other, hence the crash.

Related

Update Storyboard UI After Retrieving Contacts

I am working on an app that will start out by requesting access to the users contacts and then display information about the first contact in the UI.
func requestContactsAccess() {
let store = CNContactStore()
store.requestAccess(for: .contacts, completionHandler: {_,_ in })
DispatchQueue.main.async {
let authorizationStatus = CNContactStore.authorizationStatus(for: .contacts)
if authorizationStatus == .authorized {
self.retrieveContacts()
self.updateScreen()
} else {
self.alert()
}
}
The problem I am running into is that the UI tries to update before the app gets user permission to access the contacts. I tried to get around this by using a completion handler to call the function to update the UI after fetching the contacts but this causes the error when updating my label "UILabel.text must be used from main thread only"
TLDR: How can I ensure I have retrieved the device contacts before updating the UI?
#RickyMo 's answer was what fixed it for me.
Solution:
func requestContactsAccess() {
let store = CNContactStore()
store.requestAccess(for: .contacts, completionHandler: {_,_ in
DispatchQueue.main.async {
let authorizationStatus = CNContactStore.authorizationStatus(for: .contacts)
if authorizationStatus == .authorized {
self.retrieveContacts()
self.updateScreen()
} else {
self.alert()
}
}
})
}

Contacts and Microphone request access does not work on iOS 9

I've been stuck on a bug since last Monday, so I'm asking for help now ..
Contacts and Micriohpone request access does not work on iOS 9. I use this piece of code in order to request access to contacts :
let contactsStore = CNContactStore()
func requestAccess(completionHandler: #escaping (Permission) -> ()) {
self.contactsStore.requestAccess(for: .contacts, completionHandler: { (granted, error) in
if granted {
completionHandler(.granted)
} else {
completionHandler(.denied)
}
})
}
This function is called, no problem with that, the problem is it always return .denied and an error set with "Access denied", even though no alert has been shown to the user. The same with microphone.
The key 'Privacy - Contacts Usage Description' is present in my Info.plist
EDIT :
I also know that when the user denied once the usage it is not shown anymore, but the other problem is that there is not "switch" in the settings section of the app. I tried to restore the device (Working on simulator as I don't have a real iOS 9 device), but still the same behaviour.
This code works perfeclty on iOS 10 and iOS 11. But no chance on iOS 9
If you could help me on this issue that would be awesome.
Thanks !
I tried this on 9.3 in the simplest way imaginable, and I did get a prompt:
import UIKit
import Contacts
class ViewController: UIViewController {
let contactsStore = CNContactStore()
override func viewDidAppear(_ animated: Bool) {
DispatchQueue.main.async {
self.requestAccess(completionHandler: { (permission) in
print("The user said \(permission)")
})
}
}
func requestAccess(completionHandler: #escaping (Permission) -> ()) {
self.contactsStore.requestAccess(for: .contacts, completionHandler: { (granted, error) in
if granted {
completionHandler(.granted)
} else {
completionHandler(.denied)
}
})
}
}
enum Permission {
case granted
case denied
}
This works fine. I think the issue is that you already denied it.
The only solutions are:
Change the bundle id, which will make your app act as a different one
Reset your device/simulator (easier if a simulator of course)
Change the privacy setting from Off to On
For end users, I've seen the UI prompt the user to change the setting if they see "denied".
You can do that like this:
self.requestAccess(completionHandler: { (permission) in
print("The user said \(permission)")
if ( permission == .denied ) {
let urlStr = UIApplicationOpenSettingsURLString
if let url = URL(string:urlStr) {
UIApplication.shared.openURL(url)
}
}
})

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)!)

Get notified when the user makes selection for allowing access to Camera in iOS

When the app tries to access the Camera API's in iOS than an OS level alertview is shown.
The user here has to allow access to camera or disable the access.
My question is how can I get notified of the selection made by the user..?
Say he selected don't allow access than is there any notification raised which I can use in my app..?
Any help is appreciated.
Instead of letting the OS show the alert view when the camera appears, you can check for the current authorization status, and request for the authorization manually. That way, you get a callback when the user accepts/rejects your request.
In swift:
let status = AVCaptureDevice.authorizationStatusForMediaType(AVMediaTypeVideo)
if status == AVAuthorizationStatus.Authorized {
// Show camera
} else if status == AVAuthorizationStatus.NotDetermined {
// Request permission
AVCaptureDevice.requestAccessForMediaType(AVMediaTypeVideo, completionHandler: { (granted) -> Void in
if granted {
// Show camera
}
})
} else {
// User rejected permission. Ask user to switch it on in the Settings app manually
}
If the user has previously rejected the request, calling requestAccessForMediaType will not show the alert and will execute the completion block immediately. In this case, you can choose to show your custom alert and link the user to the settings page. More info on this here.
Taken from Kens answer, I've created this Swift 3 protocol to handle permission access:
import AVFoundation
protocol PermissionHandler {
func handleCameraPermissions(completion: #escaping ((_ error: Error?) -> Void))
}
extension PermissionHandler {
func handleCameraPermissions(completion: #escaping ((_ error: Error?) -> Void)) {
let status = AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeVideo)
switch status {
case .authorized:
completion(nil)
case .restricted:
completion(ClientError.noAccess)
case .notDetermined:
AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo) { granted in
if granted {
completion(nil)
} else {
completion(ClientError.noAccess)
}
}
case .denied:
completion(ClientError.noAccess)
}
}
}
You can then conform to this protocol and call it in your class like so:
handleCameraPermissions() { error in
if let error = error {
//Denied, handle error here
return
}
//Allowed! As you were

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