Swift: ViewDidLoad does not continue after having accepted notifications permissions request - ios

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.

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()
}
}
})
}

How can I check if a specific type of local notification is enabled?

If I request authorization for local notifications for alerts and sounds using the code below, the user could go into settings after granting authorization turn off banners and sounds. Authorization would still exist, but there would be no methods for sending notifications. How can I check if specific types of notifications (e.g. alert) is enabled?
#objc func registerLocal() {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in
if granted {
self.notificationsAuth = true
} else {
self.notificationsAuth = false
}
self.notificationAuthUndertermined = false
}
}
For iOS 10.0 and later:
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
// also available for badgeSetting, soundSetting, authorization status and ...
switch settings.alertSetting {
case .notSupported:
// Do stuff
break
case .disabled:
// Do stuff
break
case .enabled:
// Do stuff
break
}
}
We can verify alertSetting, soundSetting, badgeSetting is enabled or not using below code,
func getNotificationSettings() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
print("Notification settings: \(settings)")
let isAlertEnabled = settings.alertSetting
let isSoundEnable = settings.soundSetting
let isbadgeEnabled = settings.badgeSetting
if(isAlertEnabled == .enabled && isSoundEnable == .enabled && isbadgeEnabled == .enabled){
print("Alert, Sound and Badges are enabled.")
}else{
//Different action
}
}
}

Active my app when user walked 1 km and then 5km and so on. by using healthkit in ios swift when app is not running or in background

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")
}
}

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