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.
In my app I want to ask for camera access when the user pressed a camera button, which would take him to an AVFoundation based camera live preview.
That view is presented using a "present modally segue".
So in my ViewController I currently override the shouldPerformSegue() and return true if the user gives permission or has granted it already, otherwise false.
If the user didn't grant access I am showing an Alert in which he can go to settings to change the permission. That is done in showPermissionInfo().
My problem is, that AVCaptureDevice.requestAccess is called asynchronously and thus hasCameraPermission is not set to true before I'm checking for it.
Is there a way to call these restriction accesses in a blocking way?
Thank you!
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "Modally_ToCameraViewController"
{
var hasCameraPermission = false
if AVCaptureDevice.authorizationStatus(for: .video) == .authorized
{
hasCameraPermission = true
} else {
AVCaptureDevice.requestAccess(for: .video, completionHandler: { (granted: Bool) in
if granted {
hasCameraPermission = true
} else {
hasCameraPermission = false
}
})
}
if(!hasCameraPermission){
showPermissionInfo()
}
return hasCameraPermission
}
return true
}
One easy solution would be to create a semaphore and wait on it until the completion closure is called. semaphore.wait will block the current thread until semaphore.signal is called.
let semaphore = DispatchSemaphore(value: 0)
AVCaptureDevice.requestAccess(for: .video, completionHandler: { (granted: Bool) in
if granted {
hasCameraPermission = true
} else {
hasCameraPermission = false
}
semaphore.signal()
})
semaphore.wait()
How can I check if the user has enabled remote notifications on ios 9 or ios 10?
If the user has not allowed or clicked No I want to toggle a message asking if they want to enable notifications.
Apple recommends to use UserNotifications framework instead of shared instances. So, do not forget to import UserNotifications framework. As this framework is new in iOS 10 it's really only safe to use this code in apps building for iOS10+
let current = UNUserNotificationCenter.current()
current.getNotificationSettings(completionHandler: { (settings) in
if settings.authorizationStatus == .notDetermined {
// Notification permission has not been asked yet, go for it!
} else if settings.authorizationStatus == .denied {
// Notification permission was previously denied, go to settings & privacy to re-enable
} else if settings.authorizationStatus == .authorized {
// Notification permission was already granted
}
})
You may check official documentation for further information: https://developer.apple.com/documentation/usernotifications
Updated answer after iOS 10 is using UNUserNotificationCenter .
First you need to import UserNotifications then
let current = UNUserNotificationCenter.current()
current.getNotificationSettings(completionHandler: { permission in
switch permission.authorizationStatus {
case .authorized:
print("User granted permission for notification")
case .denied:
print("User denied notification permission")
case .notDetermined:
print("Notification permission haven't been asked yet")
case .provisional:
// #available(iOS 12.0, *)
print("The application is authorized to post non-interruptive user notifications.")
case .ephemeral:
// #available(iOS 14.0, *)
print("The application is temporarily authorized to post notifications. Only available to app clips.")
#unknown default:
print("Unknow Status")
}
})
this code will work till iOS 9, for iOS 10 use the above code snippet.
let isRegisteredForRemoteNotifications = UIApplication.shared.isRegisteredForRemoteNotifications
if isRegisteredForRemoteNotifications {
// User is registered for notification
} else {
// Show alert user is not registered for notification
}
I tried Rajat's solution, but it didn't work for me on iOS 10 (Swift 3). It always said that push notifications were enabled. Below is how I solved the problem. This says "not enabled" if the user has tapped "Don't Allow" or if you have not asked the user yet.
let notificationType = UIApplication.shared.currentUserNotificationSettings!.types
if notificationType == [] {
print("notifications are NOT enabled")
} else {
print("notifications are enabled")
}
PS: The method currentUserNotificationSettings was deprecated in iOS 10.0 but it's still working.
If your app supports iOS 10 and iOS 8, 9 use below code
// At the top, import UserNotifications
// to use UNUserNotificationCenter
import UserNotifications
Then,
if #available(iOS 10.0, *) {
let current = UNUserNotificationCenter.current()
current.getNotificationSettings(completionHandler: { settings in
switch settings.authorizationStatus {
case .notDetermined:
// Authorization request has not been made yet
case .denied:
// User has denied authorization.
// You could tell them to change this in Settings
case .authorized:
// User has given authorization.
}
})
} else {
// Fallback on earlier versions
if UIApplication.shared.isRegisteredForRemoteNotifications {
print("APNS-YES")
} else {
print("APNS-NO")
}
}
in iOS11, Swift 4...
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
if settings.authorizationStatus == .authorized {
// Already authorized
}
else {
// Either denied or notDetermined
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) {
(granted, error) in
// add your own
UNUserNotificationCenter.current().delegate = self
let alertController = UIAlertController(title: "Notification Alert", message: "please enable notifications", preferredStyle: .alert)
let settingsAction = UIAlertAction(title: "Settings", style: .default) { (_) -> Void in
guard let settingsUrl = URL(string: UIApplicationOpenSettingsURLString) else {
return
}
if UIApplication.shared.canOpenURL(settingsUrl) {
UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
})
}
}
let cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: nil)
alertController.addAction(cancelAction)
alertController.addAction(settingsAction)
DispatchQueue.main.async {
self.window?.rootViewController?.present(alertController, animated: true, completion: nil)
}
}
}
}
#Rajat's answer is not enough.
isRegisteredForRemoteNotifications is that your app has connected to APNS and get device token, this can be for silent push notification
currentUserNotificationSettings is for user permissions, without this, there is no alert, banner or sound push notification delivered to the app
Here is the check
static var isPushNotificationEnabled: Bool {
guard let settings = UIApplication.shared.currentUserNotificationSettings
else {
return false
}
return UIApplication.shared.isRegisteredForRemoteNotifications
&& !settings.types.isEmpty
}
For iOS 10, instead of checking for currentUserNotificationSettings, you should use UserNotifications framework
center.getNotificationSettings(completionHandler: { settings in
switch settings.authorizationStatus {
case .authorized, .provisional:
print("authorized")
case .denied:
print("denied")
case .notDetermined:
print("not determined, ask user for permission now")
}
})
Push notification can be delivered to our apps in many ways, and we can ask for that
UNUserNotificationCenter.current()
.requestAuthorization(options: [.alert, .sound, .badge])
User can go to Settings app and turn off any of those at any time, so it's best to check for that in the settings object
open class UNNotificationSettings : NSObject, NSCopying, NSSecureCoding {
open var authorizationStatus: UNAuthorizationStatus { get }
open var soundSetting: UNNotificationSetting { get }
open var badgeSetting: UNNotificationSetting { get }
open var alertSetting: UNNotificationSetting { get }
open var notificationCenterSetting: UNNotificationSetting { get }
}
for iOS12 and Swift 4 also support iOS13 and Swift5
I also created a git for this you can check here
just add this singleton file in your XCode Project
import Foundation
import UserNotifications
import UIKit
class NotificaionStatusCheck {
var window: UIWindow?
private var currentViewController : UIViewController? = nil
static let shared = NotificaionStatusCheck()
public func currentViewController(_ vc: UIViewController?) {
self.currentViewController = vc
checkNotificationsAuthorizationStatus()
}
private func checkNotificationsAuthorizationStatus() {
let userNotificationCenter = UNUserNotificationCenter.current()
userNotificationCenter.getNotificationSettings { (notificationSettings) in
switch notificationSettings.authorizationStatus {
case .authorized:
print("The app is authorized to schedule or receive notifications.")
case .denied:
print("The app isn't authorized to schedule or receive notifications.")
self.NotificationPopup()
case .notDetermined:
print("The user hasn't yet made a choice about whether the app is allowed to schedule notifications.")
self.NotificationPopup()
case .provisional:
print("The application is provisionally authorized to post noninterruptive user notifications.")
self.NotificationPopup()
}
}
}
private func NotificationPopup(){
let alertController = UIAlertController(title: "Notification Alert", message: "Please Turn on the Notification to get update every time the Show Starts", preferredStyle: .alert)
let settingsAction = UIAlertAction(title: "Settings", style: .default) { (_) -> Void in
guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
return
}
if UIApplication.shared.canOpenURL(settingsUrl) {
UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
})
}
}
let cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: nil)
alertController.addAction(cancelAction)
alertController.addAction(settingsAction)
DispatchQueue.main.async {
self.currentViewController?.present(alertController, animated: true, completion: nil)
}
}
}
to access this code on ViewController user this on viewDidLoad
NotificaionStatusCheck.shared.currentViewController(self)
Here's a solution for getting a string describing the current permission that works with iOS 9 trough iOS 11, with Swift 4. This implementation uses When for promises.
import UserNotifications
private static func getNotificationPermissionString() -> Promise<String> {
let promise = Promise<String>()
if #available(iOS 10.0, *) {
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.getNotificationSettings { (settings) in
switch settings.authorizationStatus {
case .notDetermined: promise.resolve("not_determined")
case .denied: promise.resolve("denied")
case .authorized: promise.resolve("authorized")
}
}
} else {
let status = UIApplication.shared.isRegisteredForRemoteNotifications ? "authorized" : "not_determined"
promise.resolve(status)
}
return promise
}
class func isRegisteredForRemoteNotifications() -> Bool {
if #available(iOS 10.0, *) {
var isRegistered = false
let semaphore = DispatchSemaphore(value: 0)
let current = UNUserNotificationCenter.current()
current.getNotificationSettings(completionHandler: { settings in
if settings.authorizationStatus != .authorized {
isRegistered = false
} else {
isRegistered = true
}
semaphore.signal()
})
_ = semaphore.wait(timeout: .now() + 5)
return isRegistered
} else {
return UIApplication.shared.isRegisteredForRemoteNotifications
}
}
Even though user doesn't allow the push notifications, the device token is available. So it would be also a good idea to check if it's allowed to receive the push notifications.
private func checkPushNotificationAllowed(completionHandler: #escaping (Bool) -> Void) {
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
if settings.authorizationStatus == .notDetermined || settings.authorizationStatus == .denied {
completionHandler(false)
}
else {
completionHandler(true)
}
}
}
else {
if let settings = UIApplication.shared.currentUserNotificationSettings {
if settings.types.isEmpty {
completionHandler(false)
}
else {
completionHandler(true)
}
}
else {
completionHandler(false)
}
}
}
All answers above are almost correct BUT if you have push notifications enabled and all options disabled (alertSetting, lockScreenSetting etc.), authorizationStatus will be authorized and you won't receive any push notifications.
The most appropriate way to find out if you user can receive remote notifications is to check all these setting values. You can achieve it using extensions.
Note: This solution works for iOS 10+. If you support older versions, please read previous answers.
extension UNNotificationSettings {
func isAuthorized() -> Bool {
guard authorizationStatus == .authorized else {
return false
}
return alertSetting == .enabled ||
soundSetting == .enabled ||
badgeSetting == .enabled ||
notificationCenterSetting == .enabled ||
lockScreenSetting == .enabled
}
}
extension UNUserNotificationCenter {
func checkPushNotificationStatus(onAuthorized: #escaping () -> Void, onDenied: #escaping () -> Void) {
getNotificationSettings { settings in
DispatchQueue.main.async {
guard settings.isAuthorized() {
onDenied()
return
}
onAuthorized()
}
}
}
}
The new style with async await
static func getPermissionState() async throws {
let current = UNUserNotificationCenter.current()
let result = await current.notificationSettings()
switch result.authorizationStatus {
case .notDetermined:
//
case .denied:
//
case .authorized:
//
case .provisional:
//
case .ephemeral:
//
#unknown default:
//
}
}
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.
I am using a UIImagePicker to present the users with camera to take photos which will be used in the app.
My problem is that on the first time a user opens the image picker they are presented with a prompt saying: '"my App" Would like to Access your Camera' with two options, Don't allow and OK.
My requirement is that when the user clicks Don't Allow, the Image picker gets dismissed leaving a black view. Is there a way to detect that the user has chosen Don't allow?
Here is my code to present UIImagePicker:
var PhotoPicker:UIImagePickerController = UIImagePickerController()
PhotoPicker.delegate = self
PhotoPicker.sourceType = .Camera
PhotoPicker.cameraFlashMode = .Off
PhotoPicker.showsCameraControls = false
PhotoPicker.cameraDevice = .Rear
self.presentViewController(PhotoPicker, animated: false, completion: nil)
To detect access to your library:
You need to use AssetsLibrary for that. First, import assets library framework:
import AssetsLibrary
Then, request authorization status, and if it is not determined, use blocks to catch those events, like this:
if ALAssetsLibrary.authorizationStatus() == ALAuthorizationStatus.NotDetermined {
let library = ALAssetsLibrary()
library.enumerateGroupsWithTypes(.All, usingBlock: { (group, stop) -> Void in
// User clicked ok
}, failureBlock: { (error) -> Void in
// User clicked don't allow
imagePickerController.dismissViewControllerAnimated(true, completion: nil)
})
}
To detect access to camera:
You need to use AVFoundation for that. First, import avfoundation framework:
import AVFoundation
Then, as previously, request user permission when you go to imagepicker and catch the event.
if AVCaptureDevice.authorizationStatusForMediaType(AVMediaTypeVideo) == AVAuthorizationStatus.NotDetermined {
AVCaptureDevice.requestAccessForMediaType(AVMediaTypeVideo, completionHandler: { (videoGranted: Bool) -> Void in
// User clicked ok
if (videoGranted) {
// User clicked don't allow
} else {
imagePickerController.dismissViewControllerAnimated(true, completion: nil)
}
})
}
Hope it helps!
In iOS 10, use:
import Photos
let authStatus = PHPhotoLibrary.authorizationStatus()
if authStatus == .notDetermined || authStatus == .denied {
PHPhotoLibrary.requestAuthorization({ (status) in
if status == PHAuthorizationStatus.authorized {
} else {
imagePickerController.dismissViewControllerAnimated(true, completion: nil)
}
})
}
Check out this for detecting camera permission
Presenting camera permission dialog in iOS 8
Use this when user picks Don't Allow.
PhotoPicker.dismissViewControllerAnimated(false, completion: nil)