1

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

Amogam
  • 321
  • 5
  • 20

2 Answers2

2

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"

lorem ipsum
  • 21,175
  • 5
  • 24
  • 48
0

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

Amu
  • 15
  • 5
  • I'm giving proper datetime and I'm just updating the date and time in components.. but when I change the date and time in system and rerun the project into simulator even simulator date and time changes... but I'm wondering why is it not firing. I don't have the liberty of using actual device but pushing notification only can't be tested however local notification should be able to be tested in simulator.. . – Amogam Sep 26 '21 at 20:58
  • Strange , if simulator time is updated with new changed datetime of system .. when time coarse through , the notification centre and when the datetime matches , notification centre will fire the local notificaiton – Amu Sep 26 '21 at 21:02
  • That's what am so much confused about – Amogam Sep 26 '21 at 21:31
  • 1
    Try this option in simulator after changing the datetime in system regardless of whether new changed system date time is reflecting in simulator or not **Device -> Restart** , it might help – Amu Sep 26 '21 at 21:40
  • 1
    Thanks , I think restart did the magic :) If you could help me with the others concerns, it'd be great – Amogam Sep 27 '21 at 03:50
  • Please find my updated answer , it should solve your problem – Amu Sep 28 '21 at 12:05
  • 1
    @Amu copying pieces of another answer and posting them as your own is pretty shady. You should upvote mine vs just half copying. – lorem ipsum Sep 28 '21 at 15:40
  • @loremipsum - sorry mate , didn't mean to commit any plagiarism. Just I took some ideas from your comments and made the answer bit simpler the code that follows below the statement update is inspired from your wisdom :) ofcourse credit goes to you. I have upvoted your answer and will leave your name on credit – Amu Sep 28 '21 at 15:48
  • 1
    @Amu Let's say I'm keeping the repeat on from 31st of this month. Will it get missed for next month as it'd be having one 30th and what happens if the next month is February – Amogam Oct 02 '21 at 19:30
  • 1
    If you use the above code snippet , it will fire the notification on the ending day of next month if 31st is not available. It's iOS behaviour. – Amu Oct 03 '21 at 16:30
  • 1
    That solves my confusion – Amogam Oct 03 '21 at 17:04
  • I'm glad my answer helps you but half of my answer's idea comes from @loremipsum , I'd recommend to accept his post as answer – Amu Oct 03 '21 at 20:15