1

I know there are plenty of similar questions and answers, and I reviewed them all, but still cannot find the solution. Despite of removing a scheduled notification from UNUserNotificationCenter it still gets triggered.

I create local notification as follows:

let aDF = DateFormatter()
aDF.dateFormat = "yyyy-MM-dd HH:mm:ss"
var identifierST = ""
    if update == true {
        identifierST = myCoreDataEntity.notificationUID!
    } else {
        identifierST = aDF.string(from: Date())
    }

let notif = UNMutableNotificationContent()
notif.title = "some string"
notif.body = "some string"
var dateComp: DateComponents
switch myCoreDataEntity.schedule {
case 1: //daily
    dateComp = Calendar.current.dateComponents([.hour, .minute], from: myCoreDataEntity.date)

case 2: //weekly
    dateComp = Calendar.current.dateComponents([.weekday, .hour, .minute], from: myCoreDataEntity.date)

case 3: //monthly
    dateComp = Calendar.current.dateComponents([.day, .hour, .minute], from: myCoreDataEntity.date)

case 4: //quartely - this is actually not quarterly, dont know how to set quarterly, setting monthly
    dateComp = Calendar.current.dateComponents([.day, .hour, .minute], from: myCoreDataEntity.date)

case 5: //halfyearly - this is actually not halfyearly, dont know how to set halfyearly, setting monthly
    dateComp = Calendar.current.dateComponents([.day, .hour, .minute], from: myCoreDataEntity.date)

case 6: //yearly
    dateComp = Calendar.current.dateComponents([.month, .day, .hour, .minute], from: myCoreDataEntity.date)

default: //monthly
    dateComp = Calendar.current.dateComponents([.day, .hour, .minute], from: myCoreDataEntity.date)

}
dateComp.hour = 10
dateComp.minute = 0

let notificationTrigger = UNCalendarNotificationTrigger(dateMatching: dateComp, repeats: true)
        let request = UNNotificationRequest.init(identifier: timeStampST, content: notif, trigger: notificationTrigger)
        UNUserNotificationCenter.current().add(request) { (error) in
            if (error != nil) {
                 print (error) //notify user that reminder was not saved
            } else {
                 myCoreDataEntity.notificationUID = identifierST
            }
        }

notificationUID is a String Attribute on a CoreData Entity where I store created notification identifier, so I could retrieve it later.

The above works correctly, notification is scheduled and delivered on defined date and time, so no problem here.

Whenever I need to remove a particular notification, I retrieve saved notification identifier (notificationUID) and pass it to the following function:

func removeExisting(identifierST: String) {
     UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [identifierST])
}

When trying to find the issue, I've retrieved all pending notifications and compared their identifiers with the identifierST I am passing to removePendingNotificationRequests and they match

UNUserNotificationCenter.current().getPendingNotificationRequests(completionHandler: { requests in
        for request in requests {
            print("==============================================")
            print(request.identifier)
            print(request.content.title)
            print(request.content.subtitle)
            print(request.content.body)
        }
})

But those cancelled notifications are still being triggered, am I missing something obvious here?

Edit 1

To provide a bit more details on app logic and scenarios:

  • App creates some events recurring daily, weekly, monthly, etc

  • For each event a notification is created

  • Notification is sent on the date of event

  • The user can see upcoming events and can chose to skip one cycle => this is were the problem is -> when user choses to skip, I run the func removeExisting(identifierST: String) to remove it, but when the date of that skipped event comes, the notification is still being sent.

Edit 2

Re: the point on possible typo or logic mistake while removing the notification - the reason I am sure there is no mistake there, is because it works when I am not skipping or editing the event, but I am deleting it, i.e. let's say event date is tomorrow and notification is scheduled to be delivered tomorrow at 10:00. Today user decides that he does not want this even at all and deletes it, so I run func removeExisting(identifierST: String) and it works -> no notification of that event is generated tomorrow.

But, if the user decides not to delete completely, but just skip 1 day tomorrow, and continue with it the day after tomorrow (in cases when schedule is daily) this is where I get the problem. I've tried to address this scenario in 3 approaches:

Approach 1 - Delete existing notification and create a new one with new identifier and new trigger date - the day after tomorrow

Approach 2 - Create notification with the same identifier (assuming that this will not create a new one but will modify the existing one) but new trigger date - the day after tomorrow

Approach 3 - Delete existing notification and create a new one with the same identifier and new trigger date - the day after tomorrow

None of this works.

grep
  • 566
  • 5
  • 20

3 Answers3

1

First, let me explain why what you're doing is not working.

Let's say you have many different dates, for example: April 27th, May 4th, May 11th, May 18th etc..

You're assigning dateComp as

dateComp = Calendar.current.dateComponents([.weekday, .hour, .minute], from: myCoreDataEntity.date)

so for each of those mentioned dates your dateComp will look like:

dateComp = [Monday, 10, 30] // April 27th
dateComp = [Monday, 10, 30] // May 4th
dateComp = [Monday, 10, 30] // May 11th
dateComp = [Monday, 10, 30] // May 18th

and next you put that dateComp in your trigger, do you see where i'm going with this? Trigger doesn't have anything to do with your date except those three parameters, by changing date week later, you're actually using exactly the same trigger.

The moment you add your request to notification center your notification is set up to show on NEXT AVAILABLE Monday at 10:30, NOT on your date Monday 10:30.

I had the same problem and posted similar question here. From all my research and what others said it is not possible to set up repeated notification from specific future date in current UNNotificationCenter, i will be more than happy if someone could prove me wrong about this.

You could set up whole dateComp from your date, for example [.month, .day, .hour, .minute] but then if you set repeat to true it will repeat only on the same [.month, .day, .hour, .minute] which will end up repeating yearly.

Solution can be either to set up non-repeating notification for each specific date (there is limit of 64 notifications you can have in the same time) or like in my case set up seven repeating notifications for each weekday and then just remove the one you don't want at a time and add it again some other day but NOT the same day yet because it will be triggered again.

You can check out code i used in my app to do that here:

How to set up daily local notification for tasks but don't show it when user completes the task before

dakota
  • 293
  • 3
  • 13
  • Hey dakota, thank you for looking at this. Please see Edit 2 in response to your answer. – grep Apr 27 '20 at 16:52
  • Hey @grep, please check my answer again, i completely re-edited it for clearer explanation – dakota Apr 27 '20 at 22:12
  • Thank you @dakota, I've modified my code accordingly, tested and it works. I am accepting your answer. – grep Apr 28 '20 at 07:01
0

You mean, you call getPendingNotificationRequests and no longer see the one you removed? Then, are you sure the code to add is not being called by accident after that? (so it could be being added again?)

One feature of UNUserNotificationCenter.current().add() is that, if a notification with the desired ID is already scheduled, it will not schedule another notification at the same time, but it will also not give an error. You can think of it as that it over-writes the existing one. So, if your code is somehow scheduling the same notifications again and again, you would probably not notice. Until you try removing notifications, and then they get rescheduled.

auspicious99
  • 3,902
  • 1
  • 44
  • 58
  • Please see Update 1 for more details, and to answer your questions, yes I call getPendingNotificationRequests and do not see that removed one there, but when the date comes, it is still triggered. – grep Apr 25 '20 at 15:35
  • Thanks for the update. Are you sure it is not calling the `add()` again by mistake? (some time between when it was removed and when the notification is supposed to fire?) Maybe add some logging where the `add()` is called? – auspicious99 Apr 25 '20 at 15:40
  • After removal, It does add a new one again, but with a different trigger date, i.e. if event was weekly each Monday, user skips 1 cycle, I remove the one for coming Monday and create a new one for next Monday, but I still get notification for the first Monday. Does this make sense? – grep Apr 25 '20 at 15:46
  • And you checked that this new one is actually scheduled on the coming Monday, using getPendingNotificationRequests? – auspicious99 Apr 25 '20 at 16:06
  • Yes, I can see the new one scheduled for the new date – grep Apr 25 '20 at 16:18
  • I suspect the issue might be in the way I am setting up date components for every cycle (daily, weekly, monthly, etc) is this something you could have a look and advise? – grep Apr 25 '20 at 17:22
  • You could include that code for the date components set up, yes. – auspicious99 Apr 25 '20 at 17:35
  • I've updated the question and added date components related code – grep Apr 25 '20 at 17:46
  • When you asked if I can see the new one is actually scheduled on the coming Monday, I said yes, but I was wrong, I still see the old one, with old trigger date, and I do not see the new one. The most confusing part is that when I completely delete it, it disappears from getPendingNotificationRequests, but when I delete and add again, it stays there with an old trigger date. Maybe I need to add some time delay after deletion and before adding the new one? – grep Apr 25 '20 at 18:29
  • Thanks for the update, good to know. I am wondering, if you log the updated date between the lines `dateComp.minute = 0` and `let notificationTrigger =`, would it be the correct updated date at that time? Or even after `let request = UNNotificationRequest.init(identifier: timeStampST, content: notif, trigger: notificationTrigger)`, would it be the correct date in the `request`? – auspicious99 Apr 26 '20 at 02:35
  • When I initially create an event the date of the event is correct, and notification date equals to event date, just the time of notification is 10. When I change the date of event to become +1 week (or day, or month, etc) and then add notification with the same identifier but the new trigger date based on new event date, and log this, it shows correct updated event date (+1 week), while notification trigger date stays the original one (1 week earlier) – grep Apr 26 '20 at 04:40
0

I've now watched again related WWDC video - https://developer.apple.com/videos/play/wwdc2016/707 - and changed the logic a bit.

Original approach

When user is skipping a cycle, I am removing scheduled notification and creating a new one, with new identifier and new trigger date.

New approach

When user is skipping a cycle, I am not removing anything, but creating a "new" notification with a new trigger date, but using the same identifier. As I understand from the video, using the same identifier is not actually creating a new notification, but updating the exisintg one. I will be able to confirm if this works in 24 hours.

grep
  • 566
  • 5
  • 20
  • Even with this new approach, I still see next trigger date not updated with the new date, so it is unlikely this will work, perhaps the issue is with how I am setting the trigger date from components. I am will update the question with that piece of code. – grep Apr 25 '20 at 16:52
  • Yes, the effect is (at least, it should be) to over-write the previous notification with the same identifier, with the new info, including the new date. – auspicious99 Apr 26 '20 at 02:31