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()
}
}
})
}
Related
I use new API for requesting of access for an assets library of a user:
PHPhotoLibrary.requestAuthorization(for: .readWrite) {
[weak self] (status:PHAuthorizationStatus) in
print("called");
}
iOS displays alert with access levels:
If the user selects Select Photos..., iOS will show a picker:
If user swipes down on a title of the picker, the photos library will not call the handler block.
Implementation of the photoLibraryDidChange
func photoLibraryDidChange(_ changeInstance: (PHChange)) {
guard let fetchResult = self.fetchResult else {
return
}
if changeInstance.changeDetails(for: fetchResult) != nil {
DispatchQueue.main.async {
self.delegate?.photosLibraryChanged(self)
}
}
}
Does someone faced with this situation? And how I can fix it?
Example: https://github.com/K-Be/NewInPhotoKitInIOS14
I'm creating an iOS application in Swift and in the AppDelegate I insert the following code in the didFinishLaunchingWithOptions to ask the notifications permissions
let center = UNUserNotificationCenter.current()
center.delegate = self
// set the type as sound or badge
center.requestAuthorization(options: [.sound,.alert,.badge]) { (granted, error) in
guard granted else { return }
DispatchQueue.main.async(execute: {
application.registerForRemoteNotifications()
})
}
Meanwhile, in the ViewDidLoad, I ask the user the microphone permissions in this way:
func checkPermission()
{
switch AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeAudio)
{
case .authorized:
print("ok2")
self.addButton()
self.voice()
case .notDetermined:
AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeAudio, completionHandler: { granted in
if granted {
print("ok1")
self.addButton()
self.voice()
else {
print("ko")
}
})
case .denied:
print("ko")
case .restricted:
return
}
}
The problem is:
After accepting the microphone permissions, I accept the notification permissions, but, after that, the code in the ViewController does not proceed (methods addButton and voice are not executed).
Could you help me?
Thank you so much in advance
Maybe the problem is caused by threading issues: The completion handler in AVCaptureDevice.requestAccess is executed in an arbitrary worker thread. If you do UI stuff here (like adding a button), this has to be done in the main thread, e.g.
AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeAudio) { granted in
if granted {
print("ok1")
DispatchQueue.main.async { [unowned self] in
self.addButton()
self.voice()
}
else {
print ("ko")
}
}
But I wonder why UIKit does not crash (or at least complain) in your case.
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)
}
}
})
I have followed the answers of this question -
Healthkit background delivery when app is not running
on stackoverflow but not achieved my goal.
I want to notify user using local notification when he/she walked 1 km exact, when app is in background and not running state.
I am still not able to read the distance from Healthkit in background as well.
But when app is in foreground then only my code works and the code is as blow
let healthKitStore = HKHealthStore()
let distanceType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.distanceWalkingRunning)!
let query:HKObserverQuery = HKObserverQuery(sampleType: distanceType, predicate: nil) { (query, HKObserverQueryCompletionHandler, error) in
//here I schedule my local notification
// I want to read the distance here and also want to notify user.
self.callLocalNotification(1)
HKObserverQueryCompletionHandler()
}
healthKitStore?.execute(query)
healthKitStore?.enableBackgroundDelivery(for: distanceType, frequency: .immediate, withCompletion: { (succeeded, error) in
if succeeded{
print("Enabled background delivery of Distance changes")
} else {
if let theError = error{
print("Failed to enable background delivery of Distance changes.")
print("Error = \(theError)")
}
}
})
func callLocalNotification(_ distance:Int)
{
// Request Notification Settings
UNUserNotificationCenter.current().getNotificationSettings { (notificationSettings) in
switch notificationSettings.authorizationStatus {
case .notDetermined:
self.requestAuthorization(completionHandler: { (success) in
guard success else { return }
// Schedule Local Notification
self.scheduleLocalNotification(distance)
})
case .authorized:
// Schedule Local Notification
self.scheduleLocalNotification(distance)
case .denied:
print("Application Not Allowed to Display Notifications")
}
}
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