可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am trying to check if UserNotifications are enabled and if not I want to throw an alert. So I have a function checkAvailability
which checks multiple things, including the UserNotification authorization status.
func checkAvailabilty() -> Bool {
//
// other checking
//
var isNotificationsEnabled = false
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound], completionHandler: { (granted, error) in
if granted {
isNotificationsEnabled = true
}
else {
isNotificationsEnabled = false
}
})
}
if isNotificationsEnabled {
return true
}
else {
// Throw alert: Remind user to activate notifications
return false
}
}
But the completion handler gets called too late. The function already returned false
and after that the code in the colsure executes.
I tried to put the whole statement UNUserNotificationCenter.current().requestAuthorization()
in a synchronous dispatch queue but this didn't work.
Another approach would be to return from inside the closure but I have no idea how to accomplish that.
回答1:
Do not wait, use a completion handler, for convenience with an enum:
enum AuthResult {
case success(Bool), failure(Error)
}
func checkAvailabilty(completion: @escaping (AuthResult) -> ()) {
//
// other checking
//
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound], completionHandler: { (granted, error) in
if error != nil {
completion(.failure(error!))
} else {
completion(.success(granted))
}
})
}
And call it:
checkAvailabilty { result in
switch result {
case .success(let granted) :
if granted {
print("access is granted")
} else {
print("access is denied")
}
case .failure(let error): print(error)
}
}
回答2:
Ya so as you figured what is happening is that the function returns before the completion handler gets called. So what you want to do is pass an asynchronous callback to the checkAvailability
function so it will callback once the completion handler is fired.
func checkAvailabilty(callback: @escaping (Bool) -> Void) {
//
// other checking
//
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound], completionHandler: { (granted, error) in
if granted {
callback(true)
} else {
callback(false)
}
})
}
you would call this function like so...
checkAvailability(callback: { (isAvailable) -> Void in
if isAvailable {
// notifications are available
} else {
// present alert
}
})
Keep in mind that when you go to present the alert you may need to explicitly dispatch the call to the main thread since the completion handler may callback on a different thread. In which case this is how you would want to call the function and present the alert...
checkAvailability(callback: { (isAvailable) -> Void in
if isAvailable {
// notifications are available
} else {
DispatchQueue.main.async {
// present alert
}
}
})
回答3:
The code block
if isNotificationsEnabled {
return true
}
else {
// Throw alert: Remind user to activate notifications
return false
}
gets called immediately after the call to requestAuthorization(options:completionHandler)
.
You should instead display the alert from within the completion handler:
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound], completionHandler: { (granted, error) in
if !granted {
// Show alert
}
})
Your function checkAvailability
is no longer synchronously returning a Bool, as the call to requestAuthorization(options:completionHandler)
is asynchronous.
回答4:
Another alternative is to return two parameters in the completion handler:
func checkAvailabilty(completion: @escaping (_ granted: Bool, _ error: Error?) -> ()) {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, error in
completion(granted, error)
}
}
usage
checkAvailabilty { granted, error in
guard error == nil else {
// An Authorization error has occurred. Present an alert to the user with the error description.
DispatchQueue.main.async {
let alert = UIAlertController(title: "Alert", message: error?.localizedDescription ?? "Authorization failed. Unknown error.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alert, animated: true)
}
return
}
if granted {
print("granted") // authorization was successful
} else {
print("denied") // present alert from the main thread
DispatchQueue.main.async {
let alert = UIAlertController(title: "Attention", message: "The App needs you to turn on notifications !!!", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alert, animated: true)
}
}
}