Search code examples
iosswiftlocalnotificationtimedelayuncalendarnotificationtrigger

UNCalendarNotificationTrigger not initiating


Okay - I am totally frustrated with this piece of code right now and ready to give up! Basically when simulating to either Simulator or actual device I get the requestAuthorisation to work no problem but the trigger does not initiate ever. I have followed several guys online and their code worked with ease! When I use a button to initiate a UNTimeIntervalNotificationTrigger it works but that is not what I want. Currently testing in iOS 14.3 as target for build. Rest of the App builds no problem. What am I doing wrong?! Cannot help but think that somewhere along the line of trying to get it to work I might have damaged something in info.plist or similar?! I have tested to repeat the trigger and not to repeat but neither works.

    override func viewDidLoad() {
        super.viewDidLoad()
        
        //NOTIFICATIONS
        // Step 1 - Ask the use for permission to notify
        let randVerseCenter = UNUserNotificationCenter.current()
        randVerseCenter.requestAuthorization(options: [.alert, .sound]){ (granted, error) in
            if granted {
                print("Yay - request authorisation worked!")
            } else {
                print ("D'oH - request Authorisation did not work!")
            }
        }
        // Step 2 - Create the Notification Content
        let randVerseContent = UNMutableNotificationContent()
        randVerseContent.title = "Random Reference"
        randVerseContent.body = "Random Verse"
        randVerseContent.sound = UNNotificationSound.default
        // Step 3 - Create the trigger for the notification by delay
        let randVerseDate = Date().addingTimeInterval(30)
        let randVerseDateComponents = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute, .second], from: randVerseDate)
        let randVerseTrigger = UNCalendarNotificationTrigger(dateMatching: randVerseDateComponents, repeats: true)
        // Step 4 - Creating the request
        let randVerseUUIDString = UUID().uuidString
        let randVerseRequest = UNNotificationRequest(identifier: randVerseUUIDString, content: randVerseContent, trigger: randVerseTrigger)
        // Step 5 - Register the request
        randVerseCenter.add(randVerseRequest) { (error) in
            if let error = error{
                print (error.localizedDescription)
            }
            //Check the error parameter and handle any errors
        }
    }

Solution

  • After getting more details, I guess I know why you still don't see the notifications being delivered. I'm making it in another answer to not have it too long, but I'll keep my previous answer for reference.
    Maybe you were waiting for the notification with the application in foreground? I'll refer to another part of the documentation:

    Scheduling and Handling Local Notifications
    On the section about Handling Notifications When Your App Is in the Foreground:

    If a notification arrives while your app is in the foreground, you can silence that notification or tell the system to continue to display the notification interface. The system silences notifications for foreground apps by default, delivering the notification’s data directly to your app...

    So, if that's the case, you must implement a delegate for UNUserNotificationCenter.
    I suggest you something like this, where on AppDelegate you assign the delegate for UNUserNotificationCenter since documentation says it must be done before application finishes launching:

    // AppDelegate.swift
    @main
    class AppDelegate: UIResponder, UIApplicationDelegate {
    
    
        func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
            UNUserNotificationCenter.current().delegate = self
            return true
        }
    
        // Rest of your code on AppDelegate...
    }
    
    extension AppDelegate: UNUserNotificationCenterDelegate {
        func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
            // Here we actually handle the notification
            print("Notification received with identifier \(notification.request.identifier)")
            // So we call the completionHandler telling that the notification should display a banner and play the notification sound - this will happen while the app is in foreground
            completionHandler([.banner, .sound])
        }
    }
    

    On the view controller you have handling the notification authorization and request registration, you could do it like this:

    class NotificationsViewController: UIViewController {
        
        static let notificationAuthorizedNotification = NSNotification.Name(rawValue: "NotificationAuthorizedNotification")
        let randVerseCenter = UNUserNotificationCenter.current()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            // We call this method when we know that the user granted permission, so we know we can then make notification requests
            NotificationCenter.default.addObserver(self, selector: #selector(handleNotificationAuthorization), name: NotificationsViewController.notificationAuthorizedNotification, object: nil)
            
            randVerseCenter.getNotificationSettings { [weak self] settings in
                // We check current settings and asks for permission if not granted before
                if settings.authorizationStatus == .notDetermined {
                    // Step 1 - Ask the use for permission to notify
                    self?.randVerseCenter.requestAuthorization(options: [.alert, .sound]){ (granted, error) in
                        if granted {
                            NotificationCenter.default.post(name: NotificationsViewController.notificationAuthorizedNotification, object: nil)
                            print("Yay - request authorisation worked!")
                        } else {
                            print ("D'oH - request Authorisation did not work!")
                        }
                    }
                }
            }
        }
        
        override func viewDidDisappear(_ animated: Bool) {
            super.viewDidDisappear(animated)
            // We stop listening to those notifications here
            NotificationCenter.default.removeObserver(self)
        }
        
        @objc
        func handleNotificationAuthorization() {
            // Step 2 - Create the Notification Content
            let randVerseContent = UNMutableNotificationContent()
            randVerseContent.title = "Random Reference"
            randVerseContent.body = "Random Verse"
            randVerseContent.sound = UNNotificationSound.default
            // Step 3 - Create the trigger for the notification by delay
            let randVerseDate = Date().addingTimeInterval(30)
            let randVerseDateComponents = Calendar.current.dateComponents([.second], from: randVerseDate)
            let randVerseTrigger = UNCalendarNotificationTrigger(dateMatching: randVerseDateComponents, repeats: true)
            // Step 4 - Creating the request
            let randVerseUUIDString = UUID().uuidString
            let randVerseRequest = UNNotificationRequest(identifier: randVerseUUIDString, content: randVerseContent, trigger: randVerseTrigger)
            // Step 5 - Register the request
            randVerseCenter.add(randVerseRequest) { (error) in
                if let error = error{
                    print (error.localizedDescription)
                } else {
                    print("Successfully registered notification with id \(randVerseUUIDString) at every second \(randVerseDateComponents.second!) of a minute")
                }
            }
        }
    }
    

    You might still have older notifications scheduled since your code was requesting them at the viewDidLoad and maybe you didn't remove them or delete the app.
    You can check the pending notifications using this on your viewDidLoad for example:

            randVerseCenter.getPendingNotificationRequests() { requests in
                for request in requests {
                    guard let trigger = request.trigger as? UNCalendarNotificationTrigger else { return }
                    print("Notification registered with id \(request.identifier) is schedulled for \(trigger.nextTriggerDate()?.description ?? "(not schedulled)")")
                }
            }
    

    And use randVerseCenter to remove them by their identifiers or remove all of them.