2

I'm learning the new notification system but I'm having trouble with actions. The notification works, but I do't get an action with it. I'm following a tutorial on https://www.appcoda.com/ios10-user-notifications-guide/ but I don't seem to get actions to appear with notification.

As you can see the commented out part in func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil)is where I cannot seem to get where it goes, can anyone see where I'm mistaking?

import UIKit
import UserNotifications

@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?

    func scheduleNotification(at date: Date) {
        UNUserNotificationCenter.current().delegate = self

        //compone a new Date components because components directly from Date don't work
        let calendar = Calendar(identifier: .gregorian)
        let components = calendar.dateComponents(in: .current, from: date)
        let newComponents = DateComponents(calendar: calendar, timeZone: .current, month: components.month, day: components.day, hour: components.hour, minute: components.minute)
        //create the trigger with above new date components, with no repetitions
        let trigger = UNCalendarNotificationTrigger(dateMatching: newComponents, repeats: false)

        //create the content for the notification
        let content = UNMutableNotificationContent()
        content.title = "Tutorial Reminder"
        content.body = "Just a reminder to read your tutorial over at appcoda.com!"
        content.sound = UNNotificationSound.default()
        // notification action category


        //add an image to notification
          //convert logo to url
        if let path = Bundle.main.path(forResource: "logo", ofType: "png") {
            let url = URL(fileURLWithPath: path)


            do {     // because UNNotificationAttachment is mark as throwing we need an attach block for handling errors
                let attachment = try UNNotificationAttachment(identifier: "logo", url: url, options: nil)
                content.attachments = [attachment]
                content.categoryIdentifier = "myCategory"
            } catch {
                print("The attachment was not loaded.")
            }
        }


        //create the request for notification with desired parameters
        let request = UNNotificationRequest(identifier: "textNotification", content: content, trigger: trigger)

        //add the request to notification center after removing all notifications
        UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
        UNUserNotificationCenter.current().add(request) {(error) in
            if let error = error {
                print("Uh oh! We had an error: \(error)")
            }
        }


    }

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
//        let action = UNNotificationAction(identifier: "remindLater", title: "Remind me later", options: [])
//        let category = UNNotificationCategory(identifier: "myCategory", actions: [action], intentIdentifiers: [], options: [])
//        UNUserNotificationCenter.current().setNotificationCategories([category])

        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) {(accepted, error) in
            if !accepted {
//                print("Notification access denied.")
//                let action = UNNotificationAction(identifier: "remindLater", title: "Remind me later", options: [])
//                let category = UNNotificationCategory(identifier: "myCategory", actions: [action], intentIdentifiers: [], options: [])
//                UNUserNotificationCenter.current().setNotificationCategories([category])
            }
        }
        let action = UNNotificationAction(identifier: "remindLater", title: "Remind me later", options: [])
        let category = UNNotificationCategory(identifier: "myCategory", actions: [action], intentIdentifiers: [], options: [])
        UNUserNotificationCenter.current().setNotificationCategories([category])


        return true
    }

}
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {

    if response.actionIdentifier == "remindLater" {
        let newDate = Date(timeInterval: 5, since: Date())
        scheduleNotification(at: newDate)
    }

//    UNUserNotificationCenter.current().delegate = self

   }
}

EDIT:

after @VIP-DEV answer I changed the code, to add the reminder in it's own action, into:

import UIKit
import UserNotifications

@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?



    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate

        configureCategory()
        triggerNotification(at: Date())
        requestAuth()

        return true
    }


    private let category = "Notification.Category.Read"

    private let readActionIdentifier = "Read"
    private let waitActionIdentifier = "Wait"

    private func requestAuth() {
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { (success, error) in
            if let error = error {
                print("Request Authorization Failed (\(error), \(error.localizedDescription))")
            }
        }
    }

    func triggerNotification(at date: Date) {
        // Create Notification Content
        let notificationContent = UNMutableNotificationContent()

        //compone a new Date components because components directly from Date don't work
        let calendar = Calendar(identifier: .gregorian)
        let components = calendar.dateComponents(in: .current, from: date)
        let newComponents = DateComponents(calendar: calendar, timeZone: .current, month: components.month, day: components.day, hour: components.hour, minute: components.minute)
        //create the trigger with above new date components, with no repetitions





        // Configure Notification Content
        notificationContent.title = "Hello"
        notificationContent.body = "Kindly read this message."

        // Set Category Identifier
        notificationContent.categoryIdentifier = category

        // Add Trigger
//        let notificationTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 10.0, repeats: false)
        let trigger = UNCalendarNotificationTrigger(dateMatching: newComponents, repeats: false)
//        // Create Notification Request
        let notificationRequest = UNNotificationRequest(identifier: "test_local_notification", content: notificationContent, trigger: trigger)
//
        // 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))")
            }
        }
    }

    private func configureCategory() {
        // Define Actions
        let read = UNNotificationAction(identifier: readActionIdentifier, title: "Read", options: [])
        let wait = UNNotificationAction(identifier: waitActionIdentifier, title : "Wait", options: [])
        // Define Category
        let readCategory = UNNotificationCategory(identifier: category, actions: [read, wait], intentIdentifiers: [], options: [])

        // Register Category
        UNUserNotificationCenter.current().setNotificationCategories([readCategory])
    }

    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) {
        switch response.actionIdentifier {
        case readActionIdentifier:
            print("Read tapped")
        case waitActionIdentifier:
            let NewDate = Date(timeInterval: 20, since: Date())
            triggerNotification(at: NewDate)
            print("Wait tapped")
        default:
            print("Other Action")
        }

        completionHandler()
    }


}

and

class ViewController: UIViewController {
    @IBAction func datePickerDidSelectNewDate(_ sender: UIDatePicker) {

        let selectedDate = sender.date
        let delegate = UIApplication.shared.delegate as? AppDelegate
//        delegate?.scheduleNotification(at: selectedDate)
        delegate?.triggerNotification(at: selectedDate)
    }
}

Now I don't get to set new notification when tap on wait button. where do I have to set let NewDate = Date(timeInterval: 20, since: Date()) triggerNotification(at: NewDate)? I thought it went in case waitActionIdentifier but I don't even get the print to console.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Vincenzo
  • 5,304
  • 5
  • 38
  • 96
  • You are not setting the categoryIdentifier for content i.e let content = UNMutableNotificationContent(); content.categoryIdentifier = "myCategory"; – VIP-DEV Sep 01 '18 at 17:14
  • @VIP-DEV. I see I did add it in the wrong place. I now added it after `// notification action category line` but nothing changed, no actions with the notifications. I might have messed up something more. I don't understand where to declare this ` let action = UNNotificationAction(identifier: "remindLater", title: "Remind me later", options: [])` , `let category = UNNotificationCategory(identifier: "myCategory", actions: [action], intentIdentifiers: [], options: [])`, `UNUserNotificationCenter.current().setNotificationCategories([category])` in `didFinishLaunchingWithOptions` function – Vincenzo Sep 01 '18 at 18:46
  • I have found other two tutorials, and they come with a project sample, with the full code. None of them shows me actions with the notification in either simulator or iPhone 6. Is there anything that I should check, like developer permission for notifications or else? This is the other tutorial with the sample project: https://medium.freecodecamp.org/ios-10-notifications-inshorts-all-in-one-ad727e03983a – Vincenzo Sep 02 '18 at 07:13
  • Can it be that I'm not enrolled in the Apple Developer Program and that actions are featured only for paying developer? If so, how can I learn and test my progresses as a developer in that ? Would be a very big deception ... – Vincenzo Sep 02 '18 at 07:39
  • I see you are triggering local notification, so you don't need Apple Developer Program – VIP-DEV Sep 02 '18 at 07:55
  • @VIP-DEV. Ok, good to know it's as I knew, so why is not showing me buttons with the notification? I'm following another tutorial, with sample project for the local notifications , but still..nothing..no actions.. https://medium.freecodecamp.org/ios-10-notifications-inshorts-all-in-one-ad727e03983a can you try and see if it works on your simulator please? is there something I need to abilitate? Ain't working in neither the simulator or the iPhone 6. – Vincenzo Sep 02 '18 at 08:04
  • I have posted an answer below. Kindly check it – VIP-DEV Sep 02 '18 at 08:09
  • Are you calling [`setNotificationCategories‍‍‍`](https://www.google.com/search?q=setNotificationCategories&oq=setNotificationCategories&aqs=chrome..69i57j69i60j0j69i60j0l2.417j0j7&sourceid=chrome&ie=UTF-8) once across your entire app? When ever you call it, it **replaces** previous categories. So upon registration you should have ALL your categories ready – mfaani Sep 06 '18 at 22:03
  • @Honey . I call set categories inside configureCategory() so its set on time at app start an thst's it. Do you think it's a wrong approach? Should i set it differently? I send you the project in chat.. – Vincenzo Sep 08 '18 at 04:48
  • @Honey. This is a solved question already anyways. There is the other one, the one we starded the chat from .. but it's solved as well. – Vincenzo Sep 08 '18 at 04:50

2 Answers2

1
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.
    UNUserNotificationCenter.current().delegate = self

    configureCategory()
    triggerNotification()
    requestAuth()

    return true
}


private let category = "Notification.Category.Read"

private let actionIdentifier = "Read"

private func requestAuth() {
    UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { (success, error) in
        if let error = error {
            print("Request Authorization Failed (\(error), \(error.localizedDescription))")
        }
    }
}

private func triggerNotification() {
    // Create Notification Content
    let notificationContent = UNMutableNotificationContent()

    // Configure Notification Content
    notificationContent.title = "Hello"
    notificationContent.body = "Kindly read this message."

    // Set Category Identifier
    notificationContent.categoryIdentifier = category

    // Add Trigger
    let notificationTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 3.0, repeats: false)

    // Create Notification Request
    let notificationRequest = UNNotificationRequest(identifier: "test_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))")
        }
    }
}

private func configureCategory() {
    // Define Actions
    let read = UNNotificationAction(identifier: actionIdentifier, title: "Read", options: [])

    // Define Category
    let readCategory = UNNotificationCategory(identifier: category, actions: [read], intentIdentifiers: [], options: [])

    // Register Category
    UNUserNotificationCenter.current().setNotificationCategories([readCategory])
}

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) {
    switch response.actionIdentifier {
    case actionIdentifier:
        print("Read tapped")
    default:
        print("Other Action")
    }

    completionHandler()
}

enter image description here

VIP-DEV
  • 221
  • 1
  • 6
  • Sorry, I didn't refresh and could't see your answer. Thanks. Let me try it. – Vincenzo Sep 02 '18 at 08:21
  • Hi, your code only creates a delayed notification but no actions. What I'm trying to accomplish is to add actions to a time picker scheduled notification. I've been trying to adapt your answer but with no luck. I see you se a new content in your answer. can you try to use the code I supplied in the question? I won't understand where is the error otherwise. Many thanks. – Vincenzo Sep 02 '18 at 11:09
  • It creates a notification with action, you have to pull drag notification to see action. You check above the screenshot attached – VIP-DEV Sep 02 '18 at 11:18
  • I adapted your code to use the timer for a wait action, as my notification gets requested using a date piker, but I don't get the notification anymore. can you see why? see edit. – Vincenzo Sep 02 '18 at 12:16
  • stupidly I forgot to set trigger, notification request and add the request to Notification Center . Now I do get notification with 2 buttons: Read and Wait. Wait is supposed to set a 20 sec timer and triggers the `triggerNotification(at date: Date())`with ne new date set but it doesn't as I don't get prints in console – Vincenzo Sep 02 '18 at 14:08
  • Super you found it – VIP-DEV Sep 02 '18 at 14:32
  • Yes.. I think I understood the new system of notification, but still I cannot perform anything , is it correct how I declared the new timer in wait action? Because it's not showing a new notification after I select the wait button. I'm very close to it but still..not solved .. – Vincenzo Sep 02 '18 at 19:12
  • Can you please check my `didReceiveResponse` method please? I don't seem to get a response.. – Vincenzo Sep 02 '18 at 20:24
  • I don't see your app delegate is conforming to UNUserNotificationCenterDelegate. Replicate exactly what I have done – VIP-DEV Sep 03 '18 at 04:10
  • How can it be? I can't find what's missing from your code.. `UNUserNotificationCenter.current().delegate = self` I have to change into `UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate`because it was throwing me an error: `Cannot assign value of type 'AppDelegate' to type 'UNUserNotificationCenterDelegate?' Insert ' as! UNUserNotificationCenterDelegate'`so I did fix it.. but the it made me change `as! `into `as?`. Can this be the problem with my code? I my AppDelegate wasn't conforming to the UNUserNotificationCenterDelegate wouldn't work at all right? – Vincenzo Sep 03 '18 at 05:56
  • Added `UNUserNotificationCenterDelegate`to class declaration, so `UNUserNotificationCenter.current().delegate = self`now is not throwing errors, but still, no prints when I tap a button. Anyways I see in the mentioned tutorial that AppDelegate has the `didReceiveResponse`methon set in an extension. can it be the problem? I remember reading somewhere in apple documentation something about this being a must. – Vincenzo Sep 03 '18 at 06:10
  • Partially solved. The category identifier `private let category = "Notification.Category.Read"`was declared as private. Now I do get prints when I tap on action buttons. Still it's not rescheduling a new notification wit the timer settings. – Vincenzo Sep 03 '18 at 12:37
  • I think I found where the problem is. The new code in `case:wait`is: `let actualDate = Date(), let newDate = Date(timeInterval: 10, since: actualDate), self.triggerNotification(at: newDate)` but 'self.triggerNotification(at: newDate)`doesn't get called at `newDate` which is correct as the prints confirm. Now, when I trigger it the first time with `let selectedDate: Date = sender.date`, `delegate?.triggerNotification(at: selectedDate)` I get the date from Date Picker, why it doesn't get triggered again with newDate? My logic is not enough here. Thanks again. – Vincenzo Sep 03 '18 at 18:21
  • @VIP-DEV.Thanks for all the help, gave your answer a +1, dough that private declaration mislead me quite a bit, it was very helpful for learning the UNUserNotification framework. As for the problem in my previous comment I asked a new question as this was pretty much solved, and didn't wan to make a thousand comments. – Vincenzo Sep 03 '18 at 20:16
  • Super you found it – VIP-DEV Sep 04 '18 at 01:02
  • Can you check the new question please? Another user is reporting a weird behaviour. In her system uncommenting out `notificationTrigger`is the only way to make everything work..which makes no sense right? that is the trigger from you original code and is not useful in mine. Or am I missing something? Thank you again. New question: https://stackoverflow.com/questions/52155717/reschedule-notification-on-notification-action-doesnt-work-swift – Vincenzo Sep 04 '18 at 05:55
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/179405/discussion-between-vincenzo-and-vip-dev). – Vincenzo Sep 04 '18 at 17:36
0

The declaration of the category identifier was declared as private. Now the Notification actions work as expected. Thanks to VIP-DEV's answer I could find how to correct my code.

Vincenzo
  • 5,304
  • 5
  • 38
  • 96