Request remote notification outside AppDelegate - ios

I want to have an onboarding viewcontroller, where the user can click a button "allow notifications" and then the request alert for push notifications show up.
I have done so:
OnboardingViewController
onboardingViewController: UIViewController, UNUserNotificationCenterDelegate
func registerForRemoteNotification() {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.sound, .alert, .badge]) { (granted, error) in
if error == nil && granted {
print("Brugeren har accepteret notifikationer")
DispatchQueue.main.async {
UNUserNotificationCenter.current().delegate = self
UIApplication.shared.registerForRemoteNotifications()
}
}
}
}
The issue is that this function in AppDelegate is not called, when I do it like the above example:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
print("Test 1")
completionHandler()
}
The "didReceive" function will only be called, if I put the requestAuthorization code inside the AppDelegate file, inside didFinishLaunchingWithOptions.
How do you guys fix this issue?

Related

UNUserNotification not displayed when app is backgrounded

I'm trying to get a local notification to fire in the background after a specified delay, but for some reason it's not being presented when being scheduled from my app. I created a bare bones test app to test the behavior in which the same code displays the scheduled notification after the specified delay as expected so I have confirmed that my code is correct. However the same code doesn't produce the same result in my app. I have the correct permissions etc. yet the notification does not show. It will display in foreground, just not when the app is in the background. I've also checked pending notifications after scheduling to see if it's in the queue and it is indeed there. I've searched the code for any reference to UNUserNotificationCenter.current() and added breakpoints and comment code to ensure nothing else is interacting with it.
Here is the code to trigger the notification and validate that it has been added to the queue:
let content = UNMutableNotificationContent()
content.title = "Tap to trigger test"
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { (error) in
print("error", error)
UNUserNotificationCenter.current().getPendingNotificationRequests(completionHandler: { (requests) in
print("requests", requests.count)
})
}
And the following is the permissions registration and UNUserNotificationCenterDelegate implementation in my AppDelegate:
public func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler(.alert)
}
public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
print(response)
}
public func registerForPushNotifications() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { [weak self] granted, error in
print("Permission granted: \(granted)")
guard granted else { return }
self?.getNotificationSettings()
}
}
public func getNotificationSettings() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
print("Notification settings: \(settings)")
guard settings.authorizationStatus == .authorized else { return }
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
Console output:
Permission granted: true
Notification settings: <UNNotificationSettings: 0x280565260; authorizationStatus: Authorized, notificationCenterSetting: Enabled, soundSetting: Enabled, badgeSetting: Enabled, lockScreenSetting: Enabled, carPlaySetting: NotSupported, criticalAlertSetting: NotSupported, alertSetting: Enabled, alertStyle: Banner, providesAppNotificationSettings: No>
error nil
requests 1
I should also add that in testing there has been a few times it has fired randomly. Once when I set a breakpoint in the add request completion block...
Try adding a body to your content:
content.body = "Some content"
I am not sure how did the other sample app work and your main app does not with the same code. But I do not understand also how foreground notifications display for you without content.body

userInfo by clicking on notification when the app are closed

I'm doing an app that schedules local notifications and saves an userInfo. That's part its ok.
But when the app is closed, if a Notification appears and the user clicks, the method is not called and I can't handle userInfo.
I saw that there's a new way to receive a notification with UNUserNotificationCenter. But is not working too.
I've tried it that way, but I did not succeed:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
if let yourData = userInfo["yourKey"] as? String {
// Handle your data here, pass it to a view controller etc.
}
}
That's my implementation in AppDelegate:
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let lNotification = UILocalNotification()
lNotification.userInfo = response.notification.request.content.userInfo
// Handle your data here, pass it to a view controller etc.
}
Anyone, to help me? I saw all the questions related here and didn't found anything.
Have you registered for notifications?
If not, add this to AppDelegate didFinishLaunchingWithOptions:
// Register Notifications
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge], completionHandler: { granted, error in
if granted {
print("User notifications are allowed")
} else {
print("User notifications are NOT allowed")
}
})
UNUserNotificationCenter.current().delegate = self

How to detect "clear" notifications

if more than a minute pass from the time a user notification arrived to notification center, there is a "clear" option to dismiss one or more notifications at once from notification center.
How the iOS OS notify that the user tapped on "clear" to dismiss several notifications together?
Van's anwser goes straight into the right direction, but we do not need to implement the custom action to get what the question giver wanted.
If you create the category and pass it to the UNUserNotificationCenter you get a callback on the delegates didReceive function even if the user tabbed on the builtin Clear Button or the "X" Button on the content extension. The ResponeIdentifier will then be response.actionIdentifier == UNNotificationDismissActionIdentifier.
The Category must be something like that:
//Create the category...
UNNotificationCategory(identifier: "YourCustomIdentifier",
actions: [], intentIdentifiers: [], options: .customDismissAction)
//... and pass it to the UNUserNotificationCenter
UNUserNotificationCenter.current().setNotificationCategories(notificationCategories)
The category triggers the magic in the iOS framework and suddenly you get callbacks in your delegate.
The delegate function should look like:
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void) {
if response.actionIdentifier == UNNotificationDismissActionIdentifier {
// notification has been dismissed somehow
}
completionHandler()
}
its possible from iOS 10 and above with implementation of custom notification, you will need to work with UNNotificaitons
private func registerForRemoteNotificaiton(_ application: UIApplication) {
// show the dialog at a more appropriate time move this registration accordingly.
// [START register_for_notifications]
if #available(iOS 10.0, *) {
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: {(granted, error) in
if granted {
DispatchQueue.main.async(execute: {
UIApplication.shared.registerForRemoteNotifications()
})
}
})
configureUserNotification()
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.current().delegate = self
// For iOS 10 data message (sent via FCM)
Messaging.messaging().delegate = self as MessagingDelegate
} else {
let settings: UIUserNotificationSettings =
UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
application.registerUserNotificationSettings(settings)
}
// [END register_for_notifications]
}
private func configureUserNotification() {
if #available(iOS 10.0, *) {
let action = UNNotificationAction(identifier: UNNotificationDismissActionIdentifier, title: "Cancel", options: [])
//let action1 = UNNotificationAction(identifier: "dismiss", title: "OK", options: [])
let category = UNNotificationCategory(identifier: "myNotificationCategory", actions: [action], intentIdentifiers: [], options: .customDismissAction)
UNUserNotificationCenter.current().setNotificationCategories([category])
} else {
// Fallback on earlier versions
}
}
call registerForRemoteNotificaiton method from appdelegate's didFinishLaunching method.
Then you will need to implement UNUserNotificationCenterDelegate in appdelegate.
and then you will get the clear (here "Dismiss" as we added in action name)
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void) {
if response.actionIdentifier == UNNotificationDismissActionIdentifier {
//user dismissed the notifcaiton:
}
completionHandler()
}
find more information here
First you should set the UNUserNotificationCenterDelegate:
import UserNotifications
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UNUserNotificationCenter.current().delegate = self
return true
}
then user the delegate functinos
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
switch response.actionIdentifier {
case UNNotificationDismissActionIdentifier:
// Do whatever you want to do on dismiss action, when user clear the notification
break
case UNNotificationDefaultActionIdentifier:
// Do whatever you want to do on tap action, when user tap on notification and your application will open
break
default:
break
}
}
}

Launch app from local notification in iOS 10

I am trying to implement app launch (from inactive state) with an action from a local notification in iOS 10.
I have followed Launch a local notification at a specific time in iOS and the app launches fine in response to the local notification. But what I want from here is to perform an action in response to data in the notification.
In iOS 8 and 9 I had the setup in AppDelegate
func application(_ application: UIApplication, didReceive notification: UILocalNotification) {
if (application.applicationState == UIApplicationState.inactive || application.applicationState == UIApplicationState.background) {
NotificationCenter.default.post(name: Notification.Name(rawValue: "noteName", object: notification.alertBody)
and the observer catching it in ViewController
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.txtFromNotifier), name: NSNotification.Name(rawValue: "noteName", object: nil)
and in iOS 10 now in AppDelegate:
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
// Determine the user action
switch response.actionIdentifier {
case UNNotificationDismissActionIdentifier:
print("Dismiss Action")
case UNNotificationDefaultActionIdentifier:
print("Default")
// do something here
I haven't been able to find how to get from the UNNotification Default action ("Default" gets printed in the console after launch) to passing the parameters and executing the txtFromNotifier function in ViewController. Trying to use the NotificationCenter post / addObserver combination works when the app is in the background but doesn't get there when the app is inactive.
Any ideas?
I found a solution. I wrapped a delay around the notification broadcast. My AppDelegate now looks something like this. For selecting the notification centre in didFinishLaunchingWithOptions:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if #available(iOS 10.0, *) {
let center = UNUserNotificationCenter.current()
center.delegate = self
let options: UNAuthorizationOptions = [.alert, .badge, .sound]
center.requestAuthorization(options: options) {
(granted, error) in
if !granted {
print("Something went wrong")
}
}
} else {
application.registerUserNotificationSettings(UIUserNotificationSettings(types: [.alert , .badge , .sound], categories: nil))
}
return true
}
In didReceiveNotification I select out iOS 10...
func application(_ application: UIApplication, didReceive notification: UILocalNotification) {
if #available(iOS 10.0, *) {
// different notification centre for iOS 10, see #available below
} else {
if (application.applicationState == UIApplicationState.inactive || application.applicationState == UIApplicationState.background) {
runAfterDelay(2.0) { // 2 second delay, not sure if needed but doesn't seem to hurt - runAfterDelay function is below
NotificationCenter.default.post(name: Notification.Name(rawValue: NSLocalizedString("ticker_notification_name", comment: "")), object: notification.alertBody)
}
} /*else {
// handle the local notification when the app is open, if needed
} */
}
}
and use #available option to select for iOS 10:
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
// Determine the user action
switch response.actionIdentifier {
case UNNotificationDismissActionIdentifier:
print("Dismiss Action")
case UNNotificationDefaultActionIdentifier:
print("Default")
runAfterDelay(3.0) { // 3 second delay to give observer time to load. 3 seconds is arbitrary. runAfterDelay function is below
NotificationCenter.default.post(name: Notification.Name(rawValue: NSLocalizedString("ticker_notification_name", comment: "")), object: response) // .body
}
case "Snooze":
print("Snooze")
case "Delete":
print("Delete")
default:
print("Unknown action")
}
completionHandler()
}
and the runAfterDelay function that gives the observer time to put its socks on:
func runAfterDelay(_ delay: Double, closure:#escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
I think that I did not have to make any changes with the observer - I don't see any changes in my revision history, it looks the same as the addObserver method in the original question.

Getting local notifications to show while app is in foreground Swift 3

Apparently this is now possible with ios10 :
optional func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: (UNNotificationPresentationOptions) -> Void)
This answer basically says the tools needed to do it:
Displaying a stock iOS notification banner when your app is open and in the foreground?
I'm just not really understanding how to put it all together.
I dont know how important this is, but I'm not able to keep the optional func and xcode wants me to switch it to private.
I'm trying to show the badge, and the docs provide
static var badge: UNNotificationPresentationOptions { get }
Little lost here.
And then I'm assuming if I want to exclude a certain view controller from getting these badges and I'm not using a navigation controller this code I found would work? :
var window:UIWindow?
if let viewControllers = window?.rootViewController?.childViewControllers {
for viewController in viewControllers {
if viewController.isKindOfClass(MyViewControllerClass) {
print("Found it!!!")
}
}
}
There is a delegate method to display the notification when the app is open in iOS 10. You have to implement this in order to get the rich notifications working when the app is open.
extension ViewController: UNUserNotificationCenterDelegate {
//for displaying notification when app is in foreground
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
//If you don't want to show notification when app is open, do something here else and make a return here.
//Even you you don't implement this delegate method, you will not see the notification on the specified controller. So, you have to implement this delegate and make sure the below line execute. i.e. completionHandler.
completionHandler([.alert, .badge, .sound])
}
// For handling tap and user actions
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
switch response.actionIdentifier {
case "action1":
print("Action First Tapped")
case "action2":
print("Action Second Tapped")
default:
break
}
completionHandler()
}
}
In order to schedule a notification in iOS 10 and providing a badge
override func viewDidLoad() {
super.viewDidLoad()
// set UNUserNotificationCenter delegate to self
UNUserNotificationCenter.current().delegate = self
scheduleNotifications()
}
func scheduleNotifications() {
let content = UNMutableNotificationContent()
let requestIdentifier = "rajanNotification"
content.badge = 1
content.title = "This is a rich notification"
content.subtitle = "Hello there, I am Rajan Maheshwari"
content.body = "Hello body"
content.categoryIdentifier = "actionCategory"
content.sound = UNNotificationSound.default
// If you want to attach any image to show in local notification
let url = Bundle.main.url(forResource: "notificationImage", withExtension: ".jpg")
do {
let attachment = try? UNNotificationAttachment(identifier: requestIdentifier, url: url!, options: nil)
content.attachments = [attachment!]
}
let trigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 3.0, repeats: false)
let request = UNNotificationRequest(identifier: requestIdentifier, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { (error:Error?) in
if error != nil {
print(error?.localizedDescription ?? "some unknown error")
}
print("Notification Register Success")
}
}
In order to register in AppDelegate we have to write this piece of code in didFinishLaunchingWithOptions
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
registerForRichNotifications()
return true
}
I have defined actions also here. You may skip them
func registerForRichNotifications() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert,.badge,.sound]) { (granted:Bool, error:Error?) in
if error != nil {
print(error?.localizedDescription)
}
if granted {
print("Permission granted")
} else {
print("Permission not granted")
}
}
//actions defination
let action1 = UNNotificationAction(identifier: "action1", title: "Action First", options: [.foreground])
let action2 = UNNotificationAction(identifier: "action2", title: "Action Second", options: [.foreground])
let category = UNNotificationCategory(identifier: "actionCategory", actions: [action1,action2], intentIdentifiers: [], options: [])
UNUserNotificationCenter.current().setNotificationCategories([category])
}
If you want that your notification banner should be shown everywhere in the entire application, then you can write the delegate of UNUserNotificationDelegate in AppDelegate and make the UNUserNotificationCenter current delegate to AppDelegate
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
print(response.notification.request.content.userInfo)
completionHandler()
}
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.alert, .badge, .sound])
}
}
Check this link for more details
https://www.youtube.com/watch?v=Svul_gCtzck
Github Sample
https://github.com/kenechilearnscode/UserNotificationsTutorial
Here is the output
Swift 3 | iOS 10+
Assuming you know how to schedule a local notification:
func scheduleLocalNotification(forDate notificationDate: Date) {
let calendar = Calendar.init(identifier: .gregorian)
let requestId: String = "123"
let title: String = "Notification Title"
let body: String = "Notification Body"
// construct notification content
let content = UNMutableNotificationContent()
content.title = NSString.localizedUserNotificationString(forKey: title, arguments: nil)
content.body = NSString.localizedUserNotificationString(forKey: body, arguments: nil)
content.sound = UNNotificationSound.default()
content.badge = 1
content.userInfo = [
"key1": "value1"
]
// configure trigger
let calendarComponents: [Calendar.Component] = [.year, .month, .day, .hour, .minute]
let dateComponents = calendar.dateComponents(calendarComponents, from: notificationDate)
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
// create the request
let request = UNNotificationRequest.init(identifier: requestId, content: content, trigger: trigger)
// schedule notification
UNUserNotificationCenter.current().add(request) { (error: Error?) in
if let error = error {
// handle error
}
}
}
You need to make your AppDelegate implement the UNUserNotificationCenterDelegate protocol, and set it as the notification center's delegate with UNUserNotificationCenter.current().delegate = self.
// AppDelegate.swift
import UIKit
import UserNotifications
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions
launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// set app delegate as notification center delegate
UNUserNotificationCenter.current().delegate = self
}
}
extension AppDelegate: UNUserNotificationCenterDelegate {
// called when user interacts with notification (app not running in foreground)
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse, withCompletionHandler
completionHandler: #escaping () -> Void) {
// do something with the notification
print(response.notification.request.content.userInfo)
// the docs say you should execute this asap
return completionHandler()
}
// called if app is running in foreground
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent
notification: UNNotification, withCompletionHandler completionHandler:
#escaping (UNNotificationPresentationOptions) -> Void) {
// show alert while app is running in foreground
return completionHandler(UNNotificationPresentationOptions.alert)
}
}
Now your local notifications will appear when your app is in the foreground.
See the UNUserNotificationCenterDelegate docs for reference.
Key to getting your notifications to show up while your app is in the foreground is also setting:
content.setValue(true, forKey: "shouldAlwaysAlertWhileAppIsForeground")
in your UNNotificationRequest. As for the rest, see the excellent answer by Rajan Maheshwari.
When your app is open in the foreground userNotificationCenter method call
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void)
{
completionHandler(.alert)
}
None of these answers are good with recent IOS versions
shouldAlwaysAlertWhileAppIsForeground will crash on >= IOS 12
assigning UNUserNotificationCenter.current().delegate changes the behavior of background push notifications. UIApplicationDelegate.didReceiveRemoteNotification() is no longer called, when push notification is received and app is on background (until user clicks the notification).

Resources