I'm trying to create a notification with two possible action buttons like "Quick reply" and "Cancel", but I can't find any code examples for this. Please could someone explain how to do this in Swift 3.
It's difficult to give a precise example of what you are looking for. Here's a quick UNUserNotificationCenter implementation with an action.
import UserNotifications
Define a category ID constant
private let categoryID = "Category"
Setup and register UNUserNotificationCenter
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
// Configure User Notification Center
UNUserNotificationCenter.current().delegate = self
// Define Actions
let actionShowSomething = UNNotificationAction(identifier: "ShowSomething", title: "Show Something", options: [])
// Define Category
let category = UNNotificationCategory(identifier: categoryID, actions: [actionShowSomething], intentIdentifiers: [], options: [])
// Register Category
UNUserNotificationCenter.current().setNotificationCategories([category])
}
Example event for triggering the notification
// MARK: - Actions
#IBAction func sheduleNotification(sender: UIButton) {
// 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()
})
case .authorized:
// Schedule Local Notification
self.scheduleLocalNotification()
case .denied:
print("Application Not Allowed to Display Notifications")
}
}
}
sheduleNotification implementation
// MARK: - Methods
private func scheduleLocalNotification() {
// Create Notification Content
let notificationContent = UNMutableNotificationContent()
// Configure Notification Content
notificationContent.title = "Title"
notificationContent.subtitle = "Subtitle"
notificationContent.body = "Body"
// Set Category Identifier
notificationContent.categoryIdentifier = categoryID
// Add Trigger
let notificationTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 10.0, repeats: false)
// Create Notification Request
let notificationRequest = UNNotificationRequest(identifier: "cocoacasts_local_notification", content: notificationContent, trigger: notificationTrigger)
// Add Request to User Notification Center
UNUserNotificationCenter.current().add(notificationRequest) { (error) in
if let error = error {
print("Unable to Add Notification Request (\(error), \(error.localizedDescription))")
}
}
}
Handle user notification authorization
private func requestAuthorization(completionHandler: #escaping (_ success: Bool) -> ()) {
// Request Authorization
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { (success, error) in
if let error = error {
print("Request Authorization Failed (\(error), \(error.localizedDescription))")
}
completionHandler(success)
}
}
UnUserNotificationDelegate implementation
extension ViewController: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.alert])
}
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse, withCompletionHandler
completionHandler: #escaping () -> Void) {
completionHandler()
}
}
Result
You can achieve this by UIMutableUserNotificationAction
for more information look here:-
1) https://nrj.io/simple-interactive-notifications-in-ios-8/
2) http://www.imore.com/interactive-notifications-ios-8-explained
Related
I want to make an actionable notification. It appears only if my app is closed. When I press action button nothing happening
My class of notification
class Notfication : NSObject, UNUserNotificationCenterDelegate {
func request(answer : rope, RopesBas : RopesBase){
let taskCategory = UNNotificationCategory(identifier: "task", actions: [UNNotificationAction(identifier: "done", title: "Done")], intentIdentifiers: [])
UNUserNotificationCenter.current().setNotificationCategories([taskCategory])
print("0 stage")
let content = UNMutableNotificationContent()
content.title = answer.name
content.sound =
UNNotificationSound.default
content.categoryIdentifier = "task"
content.userInfo=["name" : answer.name]
print("1 stage")
// show this notification five seconds from now
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
print("2 stage")
//UNUserNotificationCenter.current().delegate = self
// choose a random identifier
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
print("3 stage")
// add our notification request
UNUserNotificationCenter.current().add(request){ (error : Error?) in
if let theError = error {
print(theError.localizedDescription)
}
}
print("4 stage")
print(UNUserNotificationCenter.current().delegate)
print("ok")
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
print("5 stage")
let userInfo = response.notification.request.content.userInfo
let id = "dad"
print("6 stage")
print(response.actionIdentifier)
switch response.actionIdentifier {
case "done":
base.ropes.removeAll(where: {$0.name == id})
break
case UNNotificationDefaultActionIdentifier,
UNNotificationDismissActionIdentifier:
break
default :
break
}
print(base.ropes.count)
completionHandler()
}
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
print("q")
completionHandler([.badge,.banner,.sound,.list])
}
}
I’m declaring this class at sheet view
struct Adding: View {
private let publisher = Notfication()
And using it when I press the button in a sheet view
Button(action: {
RopesDB.ropes.append(rope(name: answer.answer))
publisher.request(answer: RopesDB.ropes.last!, RopesBas: RopesDB)
dismiss()
}, label: {
HStack{
Spacer()
Text(answer.answer)
Spacer()
}
I’ve tried to put delegate into
class AppDelegate: NSObject, UIApplicationDelegate{
internal func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
let center = UNUserNotificationCenter.current()
let options : UNAuthorizationOptions = [.alert, .badge, .sound]
center.requestAuthorization(options: options) { granted, error in
if granted{
UNUserNotificationCenter.current().delegate = Notfication()
}
if let error = error {
print(error.localizedDescription)
}
}
return true
}
//
}
And into the class where it comment
What my mistake?
IDE Swiftplaygrounds(iPad 9, iOS 15.5)
Apple states in its documentation that notifications are only shown when the app is in the background. When the app already is in the foreground, the notification will be directly passed into your app:
https://developer.apple.com/documentation/usernotifications/handling_notifications_and_notification-related_actions
You will need to receive the notification in your delegate's userNotificationCenter(_:willPresent:withCompletionHandler:) method and there your app can decide what to actually do with it. E.g. throw the user to a certain view, show a modal etc.
I found out that this thing can’t work on Playgrounds, I suppose
I have an iOS app with widget iOS 14 where I send UserNotifications from both sides. On main app side all UNUserNotificationCenterDelegate is working fine. On widget side when I fetched events from calendar then UserNotifications are triggered. When I'm received notification in main-app side and click on it, then didReceive response: UNNotificationResponse delegate or other delegates did not called and also did not called in widget side.
here is my code in Widgets
class NotificationCenter: NSObject, ObservableObject {
#Published var dumbData: UNNotificationResponse?
override init() {
super.init()
UNUserNotificationCenter.current().delegate = self
}
}
extension NotificationCenter: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.alert, .sound, .badge])
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
completionHandler()
}
func userNotificationCenter(_ center: UNUserNotificationCenter, openSettingsFor notification: UNNotification?) {
}
func scheduleNotification(at date: Date, identifierUnic : String, body: String, titles:String) {
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
let triggerWeekly = Calendar.current.dateComponents([.day, .month, .hour,.minute, .year], from: date)
let trigger = UNCalendarNotificationTrigger(dateMatching: triggerWeekly, repeats: true)
let content = UNMutableNotificationContent()
content.title = titles
content.body = body
content.sound = UNNotificationSound.default
content.categoryIdentifier = "todoList2"
let request = UNNotificationRequest(identifier: identifierUnic, content: content, trigger: trigger)
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().add(request) {(error) in
if let error = error {
print(" We had an error: \(error)")
}}
}
}
//and fire in
#ObservedObject var notificationCenter: NotificationCenter
func getTimeline(in context: Context, completion: #escaping (Timeline<Entry>) -> ()) {
eventManager.checkPermission { (eventArray) in
print(eventArray)
if eventArray.count > 0{
for item in eventArray{
notificationCenter.scheduleNotification(at: item.startDate ?? Date(), identifierUnic: item.eventIdentifier ?? "", body: item.title, titles: item.title)
}
}
}
var entries: [SimpleEntry] = []
// Generate a timeline consisting of five entries an hour apart, starting from the current date.
let currentDate = Date()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .second, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
On Main App Side
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
print("userInfo \(userInfo)")
}
What is missing in it?
Is it right way to handle UserNotification on two Targets?
Any other way to do this?
Please let me know?
Thank you
I have a similar situation that I still can't figure out how to do... I have a widget that will display calendar events. How can I refresh the widget when calendar changed? I tried to use notificationCenter but don't know where to put it. What I want to do is that when there is a calendar change, my widget should be reloaded (refresh timeline.)
Thank you for your help.
I am trying to send a user to a DetailView once the notification is tapped. I've followed multiple instructions such as this one. However, when I test it out, tapping on the notification still brings me to the wrong view (the settingsView).
In App Delegate, I currently have this setup (for updated code scroll below):
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
registerBackgroundTasks()
FirebaseApp.configure()
UNUserNotificationCenter.current().delegate = self
return true
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
if response.actionIdentifier == "open" {
NotificationCenter.default.post(name: NSNotification.Name("DetailViewNew"), object: nil)
}
completionHandler()
}
In SettingsDetailView (where the notification is triggered and setup)
func configureNewAlert() {
let center = UNUserNotificationCenter.current()
center.removeAllDeliveredNotifications()
center.removeAllPendingNotificationRequests()
let content = UNMutableNotificationContent()
content.title = "Header"
content.body = "Test"
content.sound = UNNotificationSound.default
if let selectedHour = Calendar.current.dateComponents([.hour], from: userData.wakeUpTime).hour, let selectedMinute = Calendar.current.dateComponents([.minute], from: userData.wakeUpTime).minute{
var dateComponents = DateComponents()
dateComponents.hour = selectedHour
dateComponents.minute = selectedMinute
let open = UNNotificationAction(identifier: "open", title: "Open ", options: .foreground)
let cancel = UNNotificationAction(identifier: "cancel", title: "Close", options: .destructive)
let categories = UNNotificationCategory(identifier: "action", actions: [open,cancel], intentIdentifiers: [])
UNUserNotificationCenter.current().setNotificationCategories([categories])
content.categoryIdentifier = "action"
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let randomIdentifier = UUID().uuidString
let request = UNNotificationRequest(identifier: randomIdentifier, content: content, trigger: trigger)
center.add(request) { error in
if let error = error {
print(error.localizedDescription)
}
}
}
}
And in my DetailView (where I want the user to land when tapping the notification)
struct DetailViewNew: View {
let poemPublisher = NotificationCenter.default.publisher(for: NSNotification.Name("DetailViewNew"))
#State private var showScreen = false
var body: some View {
ZStack{
Text("Text")
}
.onReceive(poemPublisher) { notification in
self.showScreen = true
}
}
}
EDIT
I removed the categories and action, as I just want to have the user either tap the notification or dismiss it (not custom actions required).
When I do so, the completion handler if response.actionIdentifier == UNNotificationDefaultActionIdentifier does indeed get called. Now the question is how do I route it so it opens the DetailView when tapped?
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
if response.actionIdentifier == UNNotificationDismissActionIdentifier {
print ("Message Closed")
}
else if response.actionIdentifier == UNNotificationDefaultActionIdentifier {
print ("App is Open")
NotificationCenter.default.post(name: NSNotification.Name("DetailViewNew"), object: nil)
}
completionHandler()
}
Any suggestions? Thanks.
I am creating Notification Service extension for local notification with UNNotificationAction but on tap of it delegate not getting called.
userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void)
here are my 3 actions
// Define Actions
let actionReadLater = UNNotificationAction(identifier: Notification.Action.readLater, title: "Read Later", options: [])
let actionShowDetails = UNNotificationAction(identifier: Notification.Action.showDetails, title: "Show Details", options: [.foreground])
let actionUnsubscribe = UNNotificationAction(identifier: Notification.Action.unsubscribe, title: "Unsubscribe", options: [.destructive, .authenticationRequired])
// Define Category
let tutorialCategory = UNNotificationCategory(identifier: Notification.Category.tutorial, actions: [actionReadLater, actionShowDetails, actionUnsubscribe], intentIdentifiers: [], options: [])
// Register Category
UNUserNotificationCenter.current().setNotificationCategories([tutorialCategory])
to schedule notification I am using
private func scheduleLocalNotification() {
// Create Notification Content
let notificationContent = UNMutableNotificationContent()
// Configure Notification Content
notificationContent.title = "Cocoacasts"
notificationContent.subtitle = "Local Notifications"
notificationContent.body = "In this tutorial, you learn how to schedule local notifications with the User Notifications framework."
notificationContent.userInfo = ["customNumber": 100]
// Set Category Identifier
notificationContent.categoryIdentifier = Notification.Category.tutorial
// Add Trigger
let notificationTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 10.0, repeats: false)
// Create Notification Request
let notificationRequest = UNNotificationRequest(identifier: "exampleNotification", content: notificationContent, trigger: notificationTrigger)
// Add Request to User Notification Center
UNUserNotificationCenter.current().add(notificationRequest) { (error) in
if let error = error {
print("Unable to Add Notification Request (\(error), \(error.localizedDescription))")
}
}
}
My extension is getting displayed but actions are not getting triggered & without extension actions are working fine.
Try something like that:
class NotificationViewController: UIViewController, UNNotificationContentExtension {
#IBOutlet var label: UILabel?
override func viewDidLoad() {
super.viewDidLoad()
// Do any required interface initialization here.
preferredContentSize = CGSize.init(width: self.view.bounds.width / 2, height: self.view.bounds.height / 5)
}
func didReceive(_ notification: UNNotification) {
self.label?.text = "Extension"
}
// Implement this method
func didReceive(_ response: UNNotificationResponse, completionHandler completion: #escaping (UNNotificationContentExtensionResponseOption) -> Void) {
if response.notification.request.content.categoryIdentifier == "yourCategory" {
switch response.actionIdentifier {
case "first":
// Do something
completion(.dismissAndForwardAction)
case "second":
// Do something
completion(.dismissAndForwardAction)
default:
break;
}
}
}
}
You should implement the UNNotificationContentExtension's method:
func didReceive(_ response: UNNotificationResponse, completionHandler completion: #escaping (UNNotificationContentExtensionResponseOption) -> Void) {}
And inside of this block you can check your actions. Hope it helps.
I created a notification with two actions. One of my actions is called "Cancel", while the other is called "Call". How do I make the "Call" action run a URL that is in a comment that I added to my code. Here is my code:
func notificationFires(){
/**
URL Code:
if let url = URL(string: "tel://") {
UIApplication.shared.open(url, options: [:])
}
**/
let call = UNNotificationAction(identifier:"call", title:"Call", options:[.foreground])
let cancel = UNNotificationAction(identifier: "cancel", title: "Cancel", options: [.destructive ])
let category = UNNotificationCategory(identifier: "category", actions: [call, cancel], intentIdentifiers: [], options: [])
UNUserNotificationCenter.current().setNotificationCategories([category])
let notification = UILocalNotification()
notification.category = "category"
// 2
notification.soundName = UILocalNotificationDefaultSoundName
notification.fireDate = datePicker.date
// 3
if textField.text == "" {
notification.alertBody = "You have a call right now!"
}
else{
notification.alertBody = self.textField.text
}
// 4
notification.timeZone = NSTimeZone.default
// 5
// 6
notification.applicationIconBadgeNumber = 1
// 7
func application(application: UIApplication!, handleActionWithIdentifier identifier:String!, forLocalNotification notification:UILocalNotification!, completionHandler: (() -> Void)!){
if (identifier == "call"){
if let url = URL(string: "tel://2162964785") {
UIApplication.shared.open(url, options: [:])
}
}else if (identifier == "cancel"){
}
}
UIApplication.shared.scheduleLocalNotification(notification)
func application(application: UIApplication, didReceiveLocalNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
print("Recieved: notification")
let center = UNUserNotificationCenter.current()
center.removeDeliveredNotifications(withIdentifiers: ["notification"])
}
}
Assuming your notification is working correctly, you can conform to UNUserNotificationCenterDelegate to handle the "call" action.
Something like:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
if response.actionIdentifier == "call" {
let body = response.notification.request.content.body
if let url = URL(string: "tel://\(body)") {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
}
The body constant would be set to the phone number that you want to "open".
Also, its important to note that this has to be tested on an actual device. Opening a tel scheme does nothing in the simulator.
UNUserNotificationCenterDelegate API Reference: https://developer.apple.com/reference/usernotifications/unusernotificationcenterdelegate
EDIT:
You do not call the delegate method. Instead you implement it. The delegate method is called by the UNUserNotificationCenter.
To get this working, its important to ensure that you set the UNUserNotificationCenter.current() delegate property to a class that will conform to the UNUserNotificationCenterDelegate protocol.
For example, if you are handling your notification in your AppDelegate, you may have something like the following method:
func callNotification() {
let center = UNUserNotificationCenter.center()
// TODO: - Create your actions, category, content, trigger and request...
center.delegate = self // Important!
center.removeAllPendingNotificationRequests()
center.add(request, withCompletionHandler: nil)
}
The method above would be responsible for defining your notification and scheduling it. For brevity, I have left out all of the code that would define the notification since you indicated that this is working. Instead you should note that the delegate property is being set to self.
Then in an extension, you would make the AppDelegate conform to the UNUserNotificationCenterDelegate and implement the required methods.
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
if response.actionIdentifier == "call" {
let body = response.notification.request.content.body
if let url = URL(string: "tel://\(body)") {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
}
}
Now because your AppDelegate conforms to the UNUserNotificationCenterDelegate protocol and you set self (AppDelegate) as the UNUserNotificationCenter's delegate, your implementation of the userNotification(_:didReceive:withCompletionHandler:) method will be called.