Amogam
Amogam

Reputation: 437

Why local notification is not firing for UNCalendarNotificationTrigger

The local notification is supposed to fire at 25-9-2021. Here is the print object of fire time

▿ year: 2021 month: 9 day: 26 isLeapMonth: false

  • year : 2021
  • month : 9
  • day : 25
  • isLeapMonth : false
let content = UNMutableNotificationContent()
content.title = "Test title"
content.body = "sample test body"
var trigger:UNCalendarNotificationTrigger
let n = 1
let nextTriggerDate = Calendar.current.date(byAdding: .day, value: n, to: Date())!
let comps = Calendar.current.dateComponents([.year, .month, .day], from: nextTriggerDate)
trigger = UNCalendarNotificationTrigger(dateMatching: comps, repeats: false)
content.subtitle = "Sub title-Date-NonRepeat"
let request = UNNotificationRequest(identifier: "test.my.example", content: content, trigger: trigger)    
UNUserNotificationCenter.current().add(request) { Error in                        
    if let err = Error {
        print("Notification Error:\(String(describing: err))")
    }
}

Again I added time with Date with below code changes

var comps = Calendar.current.dateComponents([.year, .month, .day], from: nextTriggerDate)
comps.day = 25
comps.hour = 12
comps.minute = 17
comps.second = 10

Here is PO of comps variable

var comps = Calendar.current.dateComponents([.year, .month, .day], from: nextTriggerDate) comps.day = 25 comps.hour = 12 comps.minute = 17 comps.second = 10

I have given just date to see if it fires and I have given just date and time to see if it fires and I'm doing the whole thing in main thread and it's not working

Can someone make me understand what am I doing wrong here

Upvotes: 1

Views: 1443

Answers (2)

Amu
Amu

Reputation: 15

I have gone through your code , It's very clear you are adding calendar trigger properly and you have mentioned in one of the comments that you have got necessary permission as well while adding.

To me it feels like some datetime issue. Check if the datetime to datetime components converstion takes place properly.

If you are testing on simulator things can get crazy when you change the dates in the system back and forth.

I'd suggest you to test with real device.

This is how you get permission

  UNUserNotificationCenter.current().requestAuthorization(options: [.alert,.announcement,.sound]) { [self]
                    (granted, error) in
                    if granted {
                 print("granted")
                    } else {
                 print("denied or error")
                    }
                }

You can query list of available notifications using below code just to if it's scheduled properly at the expected datetime.

var text:NSMutableAttributedString? = NSMutableAttributedString(string: "List of notification requests and it's time\n")

UNUserNotificationCenter.current().getPendingNotificationRequests() { [weak self] requests in
                DispatchQueue.main.async {
                for request in requests {
                            guard let trigger = request.trigger as? UNCalendarNotificationTrigger else { return }
                            text?.append(NSAttributedString(string: "\nTrigger Date:\(trigger.nextTriggerDate()?.description) \nDateTime Component:\(trigger.dateComponents.description)\n"))
                        }
                print(text)
                    }

Update ( Credit(lorem ipsum) for the below is from this answer in the question)

func getFireDatetime(apiDate: Date, numberOfDaysBeforeApiDate: Int,repeat:Bool) -> DateComponents? {
    if let fireDate: Date = apiDate.dateByAdding(days: -numberOfDaysBeforeApiDate) {
        if repeat {
            return Calendar.current.dateComponents([.day, .hour, .minute, .second], from: fireDate)
        }
        return Calendar.current.dateComponents([.year, .month, .day, .hour, .minute, .second], from: fireDate)
    }
return nil
}

Invoke getFireDatetime method and that should fix all your problem I guess

P.S

  • Date() it gives you time back in universal time whereas Datetime components will give the Datetime with current calendar settings of your device.

  • As you are using Datetime components Timezone change and day light savings should be handled automatically

Upvotes: 0

lorem ipsum
lorem ipsum

Reputation: 29261

You likely aren't requesting permission first

    import SwiftUI
//struct and class should start with an uppercase
struct NotificationView: View {
    //Central location for Notification code including the delegate
    // A call to the notificationManager just like the one below has to be included in
    // application(_:willFinishLaunchingWithOptions:) or
    // application(_:didFinishLaunchingWithOptions:)
    //https://developer.apple.com/documentation/usernotifications/unusernotificationcenterdelegate
    //https://www.hackingwithswift.com/quick-start/swiftui/how-to-add-an-appdelegate-to-a-swiftui-app
    let notificationManager: NotificationManager = NotificationManager.shared
    var body: some View {
        VStack {
            VStack {
                Button("Request Permission") {
                    //Call a func here don't define it
                    notificationManager.requestAuthorization()
                }
                .frame(width: 200, height: 60, alignment: .center)
                .foregroundColor(.black)
                .background(Color.blue)
                .cornerRadius(10.0)
                .padding()

                Button("Add custom trigger") {
                    let apiDate = DateComponents(year: 2021, month: 11, day: 10, hour: 16, minute: 0, second: 0)
                    notificationManager.scheduleBasedOnDaysBeforeDate(title: "test", body: "test", baseDate: Calendar.current.date(from: apiDate)!, xDaysBefore: 10, count: 12, identifier: UUID().uuidString)
                }
                .padding()

                Button("Print Notifications") {
                    //Reusable method
                    self.notificationManager.printNotifications()
                }
                Button("Print Delivered Notifications") {
                    //Reusable method
                    self.notificationManager.printDeliveredNotifications()
                }
                .foregroundColor(.blue)
                .padding()
                Button("Delete Notifications") {
                    //Reusable method
                    self.notificationManager.deleteNotifications()
                }
                .foregroundColor(.blue)
                .padding()
            }
        }
    }
}
class NotificationManager: NSObject, UNUserNotificationCenterDelegate{
    //Singleton is requierd because of delegate
    static let shared: NotificationManager = NotificationManager()
    let notificationCenter = UNUserNotificationCenter.current()
    
    private override init(){
        super.init()
        //This assigns the delegate
        notificationCenter.delegate = self
    }
    func scheduleUNCalendarNotificationTrigger(title: String, body: String, dateComponents: DateComponents, identifier: String, repeats: Bool = false){
        print(#function)
        let content = UNMutableNotificationContent()
        content.title = title
        content.body = body
        content.sound = .default
        
        let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: repeats)
        let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
        notificationCenter.add(request) { (error) in
            if error != nil {
                print(error!)
            }
        }
    }
    func scheduleUNTimeIntervalNotificationTrigger(title: String, body: String, timeInterval: TimeInterval, identifier: String, repeats: Bool = false){
        print(#function)
        let content = UNMutableNotificationContent()
        content.title = title
        content.body = body
        content.sound = .default

        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval, repeats: repeats)
        let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
        notificationCenter.add(request) { (error) in
            if error != nil {
                print(error!)
            }
        }
    }
    ///Schedules `count` number of monthly notifications that occur `xDaysBefore` the `baseDate`
    func scheduleBasedOnDaysBeforeDate(title: String, body: String, baseDate: Date, xDaysBefore: Int, count: Int, identifier: String){
        print(#function)
        var nextBaseDate: Date = baseDate
        
        for n in 1...count{
            
            guard let triggerDate: Date = Calendar.current.date(byAdding: .day, value: -xDaysBefore, to: nextBaseDate) else{
                return
            }
            let components: DateComponents = Calendar.current.dateComponents([.month,.day, .hour,.minute,.second], from: triggerDate)
            let id = identifier.appending(" \(n)")
            scheduleUNCalendarNotificationTrigger(title: title, body: body, dateComponents: components, identifier: id)
            //OR if you want specific seconds 
            //let interval = Calendar.current.dateComponents([.second], from: Date(), to: triggerDate).second ?? 1
            
            //scheduleUNTimeIntervalNotificationTrigger(title: title, body: body, timeInterval: TimeInterval(interval), identifier: id)
            
            let next = Calendar.current.date(byAdding: .month, value: 1, to: nextBaseDate)
            
            if next != nil{
                
                nextBaseDate = next!
            }else{
                print("next == nil")
                return
            }
        }
        self.printNotifications()
        
    }
    func requestAuthorization() {
        print(#function)
        notificationCenter.requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
            if granted {
                print("Access Granted!")
            } else {
                print("Access Not Granted")
            }
        }
    }
    
    func deleteNotifications(){
        print(#function)
        notificationCenter.removeAllPendingNotificationRequests()
    }
    
    ///Prints to console schduled notifications
    func printNotifications(){
        print(#function)
        notificationCenter.getPendingNotificationRequests { request in
            print("UNTimeIntervalNotificationTrigger Pending Notification")
            for req in request{
                if req.trigger is UNTimeIntervalNotificationTrigger{
                    print((req.trigger as! UNTimeIntervalNotificationTrigger).nextTriggerDate()?.description ?? "invalid next trigger date")
                }
            }
            print("UNCalendarNotificationTrigger Pending Notification")
            for req in request{
                if req.trigger is UNCalendarNotificationTrigger{
                    print((req.trigger as! UNCalendarNotificationTrigger).nextTriggerDate()?.description ?? "invalid next trigger date")
                }
            }
        }
    }
    ///Prints to console delivered notifications
    func printDeliveredNotifications(){
        print(#function)
        notificationCenter.getDeliveredNotifications { request in
            for req in request{
                print(req)
            }
        }
    }
    //MARK: UNUserNotificationCenterDelegate
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        
        completionHandler(.banner)
    }
}
struct NotificationView_Previews: PreviewProvider {
    static var previews: some View {
        NotificationView()
    }
}

Run this code. Click on "Request Permission" first then click on "Add custom trigger" you should see your notification in the console when you click on "print"

Upvotes: 2

Related Questions