I have set remote notifications for my app and it works as follows.
If the user taps the notification's body, it calls a function (which takes the user to a specific ViewController).
However when the user taps one of the action buttons, the button's action is performed AS WELL AS the body tap action. How can I have the action button perform its action only, without the body performing its action as well.
Here's my code sample:
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.alert,.sound,.badge])
}
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo as! [String:AnyHashable]
// parse userInfo and execute a function in SWReveal to show the appropriate ViewController
let action = response.actionIdentifier
if action == "acceptFriendRequest" {
print("Friend Request Accepted")
}
}
func setCategories() {
if #available(iOS 10.0, *) {
let acceptFriendRequest = UNNotificationAction(
identifier: "acceptFriendRequest",
title: "Accept",
options: [])
let rejectFriendRequest = UNNotificationAction(identifier: "rejectFriendRequest", title: "Reject", options: [.destructive])
let FrReq = UNNotificationCategory(
identifier: "FrReq",
actions: [acceptFriendRequest, rejectFriendRequest],
intentIdentifiers: [],
options: [.customDismissAction])}
I don't think there's a way of completely avoiding the method being called as it's a delegate method. However, a simple guard statement in the beginning of the function should do the same trick:
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
guard response.actionIdentifier != "acceptFriendRequest" && response.actionIdentifier != "rejectFriendRequest" else { return }
let userInfo = response.notification.request.content.userInfo as! [String:AnyHashable]
// parse userInfo and execute a function in SWReveal to show the appropriate ViewController
}
This way if the action identifier matches either of the actions i.e. the action buttons have been pressed, then the rest of the function won't be executed.
Related
Here I use UNUserNotificationCenter to trigger notifications repeatedly. I had a problem when the notification was triggered I wanted to call the function without action. can someone tell me how to do that?
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
print(" Foreground Notification IS CALLED ")
completionHandler([.badge, .banner, .sound])
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
print(" Nikan Did recieve calling ")
if response.actionIdentifier == "Okay" {
print(" Notification Clickced ")
}
completionHandler()
}
func createNotification() {
let content = UNMutableNotificationContent()
content.title = "Notification"
content.subtitle = "Wow, Notification"
content.categoryIdentifier = "Actions"
content.sound = UNNotificationSound.defaultRingtone
// show this notification five seconds from now
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 60, repeats: true)
// choose a random identifier
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
// notification actions
let okay = UNNotificationAction(identifier: "Okay", title: "Okay", options: .destructive)
let category = UNNotificationCategory(identifier: "Actions", actions: [okay], intentIdentifiers: [], options: [])
UNUserNotificationCenter.current().setNotificationCategories([category])
// UNUserNotificationCenter.current().add(request)
UNUserNotificationCenter.current().add(request, withCompletionHandler: { error in
if let error = error {
// Something went wrong
print(error)
}
})
}
I want an answer to call a function when UNUserNotificationCenter's notification is triggered.
Question: How to call a function when a notification is received?
Answer:
When you receive notification (:didReceive), post NSNotification and observe it in your UIViewController.
Call the function in observer's #selecteor method.
DidReceive triggers when tap on push notification, in your case app is is in background and if the app is opening by clicking on push notification, then DidReveive will be triggered.
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let pushInfo = response.notification.request.content.userInfo
// post NSNotification here
// You also have data fetched from push notification (if any) in pushInfo variable
}
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
I am using the UNUserNotificationCenterDelegate (> ios 10) and one of the delegate methods where I can check the response from the notification has always actionIdentifier equal "com.apple.UNNotificationDefaultActionIdentifier" no matter what I do. The "response.notification.request.content.categoryIdentifier" comes right, with the expected value, but the request.actionIdentifier never comes correctly ("mycustomactionidentifier" in the example below). Does anyone know if I'm missing something?
extension NotificationManager: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Swift.Void) {
completionHandler([.alert,.sound])
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Swift.Void) {
if response.notification.request.content.categoryIdentifier == "TEST" {
if response.actionIdentifier == "mycustomactionidentifier" {
NSLog("it finally works dude!")
}
}
completionHandler()
}
}
I added the action and category to the Notification center:
let uploadAction = UNNotificationAction(identifier: "mycustomactionidentifier", title: "Uploaded", options: [])
let category = UNNotificationCategory(identifier: "TEST", actions: [uploadAction], intentIdentifiers: [])
center.setNotificationCategories([category])
and am sending the request putting the correct identifier:
let uploadContent = UNMutableNotificationContent()
uploadContent.title = String(number) + " asset(s) added"
uploadContent.body = "Check your inventory to manage your assets!"
uploadContent.categoryIdentifier = "TEST"
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 6, repeats: false)
let uploadRequestIdentifier = "mycustomactionidentifier"
let uploadRequest = UNNotificationRequest(identifier: uploadRequestIdentifier, content: uploadContent, trigger: trigger)
UNUserNotificationCenter.current().add(uploadRequest, withCompletionHandler: nil)
Firstly: Register your custom actions:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { (granted, error) in
if granted {
// Access granted
} else {
// Access denied
}
}
self.registerNotificationAction()
return true
}
func registerNotificationAction() {
let first = UNNotificationAction.init(identifier: "first", title: "Action", options: [])
let category = UNNotificationCategory.init(identifier: "categoryIdentifier", actions: [first], intentIdentifiers: [], options: [])
UNUserNotificationCenter.current().setNotificationCategories([category])
}
And create a content with a unique identifier:
func scheduleNotification() {
// Create a content
let content = UNMutableNotificationContent.init()
content.title = NSString.localizedUserNotificationString(forKey: "Some title", arguments: nil)
content.body = NSString.localizedUserNotificationString(forKey: "Body of notification", arguments: nil)
content.sound = UNNotificationSound.default()
content.categoryIdentifier = "categoryIdentifier"
// Create a unique identifier for each notification
let identifier = UUID.init().uuidString
// Notification trigger
let trigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 5, repeats: false)
// Notification request
let request = UNNotificationRequest.init(identifier: identifier, content: content, trigger: trigger)
// Add request
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}
Lastly: Handle the notification with their default and custom actions.
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
if response.notification.request.content.categoryIdentifier == "categoryIdentifier" {
switch response.actionIdentifier {
case UNNotificationDefaultActionIdentifier:
print(response.actionIdentifier)
completionHandler()
case "first":
print(response.actionIdentifier)
completionHandler()
default:
break;
}
}
}
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.alert, .sound])
}
}
Hope it helps!
Second Edition
Here's the results: This is going to be our UNNotificationDefaultActionIdentifier:
And this one is expanded version of the notification, we could handle both actions:
As Mannopson said, you can register a default action identifier.
However I though what you need is another thing :
response.notification.request.identifier
From Apple's actionIdentifier description said This parameter may contain one the identifier of one of your UNNotificationAction objects or it may contain a system-defined identifier. which means you need to register one, hope I am right(as I am a newbie to swift)
In my app, I want a URL to run when my notification action is tapped on. The problem is that when I run the app it puts me into the foreground but doesn't run the URL (I added .foreground to the notification action). Here is my code:
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
if response.actionIdentifier == "call" {
if let urled = URL(string: "tel://1234567891") {
UIApplication.shared.open(urled, options: [:])
}
}
}
}
Anyones help would be great:) Thanks for any answers!
For iOS 10 :
Add UserNotifications.framework to your app.
Please check that you have set delegate for push notification.
if you have already set it then try following method for iOS 10.
import UserNotifications
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate,UNUserNotificationCenterDelegate {
}
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (_ options: UNNotificationPresentationOptions) -> Void) {
print("Handle push from foreground")
// custom code to handle push while app is in the foreground
print("\(notification.request.content.userInfo)")
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
print("Handle push from background or closed")
// if you set a member variable in didReceiveRemoteNotification, you will know if this is from closed or background
print("\(response.notification.request.content.userInfo)")
}
Try this:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
if response.actionIdentifier == "call" {
if let urled = URL(string: "tel://\(1234567891)") {
if UIApplication.shared.canOpenURL(urled) {
UIApplication.shared.open(urled, options: [:], completionHandler: { (completed) in
// completionHandler block
})
}
}
}
completionHandler()}
I tried the same thing and I am facing the same issue.
You can try an alternative solution if you are using Notification Content Extension.
Since we can handle the notification actions in both Extension as well as Containing App, you can try handling it in Extension using extensionContext instead of Containing App's UIApplication object.
Try this:
func didReceive(_ response: UNNotificationResponse, completionHandler completion: #escaping (UNNotificationContentExtensionResponseOption) -> Void)
{
if response.actionIdentifier == "call"
{
self.extensionContext?.open(URL(string: "tel:9555221836")!, completionHandler: { (done) in
completion(.dismiss)
})
}
//Handling other actions...
}
So I am able to schedule notifications like so;
//iOS 10 Notification
if #available(iOS 10.0, *) {
var displayDate: String {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = DateFormatter.Style.full
return dateFormatter.string(from: datePicker.date as Date)
}
let notif = UNMutableNotificationContent()
notif.title = "I am a Reminder"
notif.subtitle = "\(displayDate)"
notif.body = "Here's the body of the notification"
notif.sound = UNNotificationSound.default()
notif.categoryIdentifier = "reminderNotification"
let today = NSDate()
let interval = datePicker.date.timeIntervalSince(today as Date)
let notifTrigger = UNTimeIntervalNotificationTrigger(timeInterval: interval, repeats: false)
let request = UNNotificationRequest(identifier: "reminderNotif", content: notif, trigger: notifTrigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: { error in
if error != nil {
print(error)
// completion(Success: false)
} else {
//completion(Sucess: true)
}
})
}
I have asked for permissions in the appDelegate and the notifications appear fine with my custom view using the notification extension.
I have added notification actions in the appDelegate for the notification category; these also appear.
//Notifications Actions
private func configureUserNotifications() {
if #available(iOS 10.0, *) {
let tomorrowAction = UNNotificationAction(identifier: "tomorrowReminder", title: "Remind Me Tomorrow", options: [])
let dismissAction = UNNotificationAction(identifier: "dismissReminder", title: "Dismiss", options: [])
let category = UNNotificationCategory(identifier: "reminderNotification", actions: [tomorrowAction, dismissAction], intentIdentifiers: [], options: [.customDismissAction])
UNUserNotificationCenter.current().setNotificationCategories([category])
} else {
// Fallback on earlier versions
}
}
I have the same category set in the notification extension .plist file. And in the notification extension I have the following to change the text when the user taps on a action.
//Handle Notification Actions And Update Notification Window
private func didReceive(_ response: UNNotificationResponse, completionHandler done: (UNNotificationContentExtensionResponseOption) -> Void) {
if response.actionIdentifier == "tomorrowReminder" {
print("Tomrrow Button Pressed")
subLabel.text = "Reminder For Tomorrow"
subLabel.textColor = UIColor.blue
done(.dismissAndForwardAction)
}
if response.actionIdentifier == "dismissReminder" {
print("Dismiss Button Pressed")
done(.dismiss)
} else {
print("Else response")
done(.dismissAndForwardAction)
}
}
However the text does not change and none of the statements are called;
Over in the appDelegate I have the following;
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self
configureUserNotifications()
}
}
extension AppDelegate: UNUserNotificationCenterDelegate {
#available(iOS 10.0, *)
private func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: (UNNotificationPresentationOptions) -> Void) {
completionHandler([.alert, .sound])
}
#available(iOS 10.0, *)
private func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: () -> Void) {
print("Recieved Action For \(response.actionIdentifier)")
if response.actionIdentifier == "tomorrowReminder" {
print("Tomorrow Reminder")
//Set new reminder for tomorrow using the notification content title
completionHandler()
}
if response.actionIdentifier == "dismissReminder" {
print("Dismiss Reminder...")
completionHandler()
}
}
}
Neither of these functions are actually called in the appDelegate either. I am not sure if the problem with updating the extension view is related to the app delegate. I don't think so, I have followed Apple's WWDC video as well as other tutorials and look at the document API and can't figure out;
Why is the Notification Extension Text Labels not updating ?
Why are the functions in the appDelegate not getting called ?
How can I using the notification content in the app delegate to use
for the action ?
PS: I have spent the past few weeks researching and trying to figure this out, it seemed fairly straight forward and I am unsure what I am missing. I know I am not the only one having these issues.
I haven't checked whole code of yours, but at least, these function headers need to be changed as follows:
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void) {
func didReceive(_ response: UNNotificationResponse,
completionHandler done: #escaping (UNNotificationContentExtensionResponseOption) -> Void) {
Simple rule:
Remove private, add #escaping.
You may have received wrong suggestions from Xcode, but with making them private, Objective-C entry points are not generated. iOS runtime uses Objective-C selectors internally, so it cannot find your methods, thus, they are not executed.