I am working on a medicine reminder application, where user can add medicine reminders for either daily, weekly or alternate days and select a time for the reminder. It was a straight forward implementation for daily and weekly but I am struggling to find a solution for alternate days as Apple doesn't provide any option to pass in frequency value with Calendar Notification Trigger. I have searched all over the internet but haven't found any solution. So far what I have done is if user select alternate days for that reminder, I am creating multiple notifications in a loop. But only my first notification in the loop is getting fired and if change the system date and time to check the next notification, it didn't work. I am adding my code here, someone please correct me if I am doing anything wrong.
func scheduleNotification(_ notification: Notification) {
let content = UNMutableNotificationContent()
content.body = Constants.notificationDescription
content.title = notification.title
content.sound = .default
content.userInfo = ["id":notification.id]
content.categoryIdentifier = Constants.notificaitonCategoryIdentifier
// Notification Actions
let takenAction = UNNotificationAction(identifier: Constants.notiTakenActionIdentifier, title: Constants.notificationTakenText)
let notTakenAction = UNNotificationAction(identifier: Constants.notiNotTakenActionIdentifier, title: Constants.notificationNotTakenText)
let actionCategory = UNNotificationCategory(identifier: Constants.notificaitonCategoryIdentifier, actions: [takenAction, notTakenAction], intentIdentifiers: [])
UNUserNotificationCenter.current().setNotificationCategories([actionCategory])
var dateComponents = DateComponents()
if notification.frequency == .daily {
dateComponents.hour = notification.hour
dateComponents.minute = notification.min
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let nextTriggerDate = trigger.nextTriggerDate()
let request = UNNotificationRequest(
identifier: notification.id,
content: content,
trigger: trigger)
UNUserNotificationCenter.current().add(request)
} else if notification.frequency == .weekly {
dateComponents.hour = notification.hour
dateComponents.minute = notification.min
dateComponents.weekday = notification.weekDay
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true)
let nextTriggerDate = trigger.nextTriggerDate()
let request = UNNotificationRequest(
identifier: notification.id,
content: content,
trigger: trigger)
UNUserNotificationCenter.current().add(request)
} else if notification.frequency == .alternateDay {
let firstDate = notification.date
for i in stride(from: 0, to: 30, by: 2) {
guard let date = Calendar.current.date(byAdding: .day, value: i, to: firstDate) else {return}
let dateComponents = date.getDateComponents()
print(date)
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
/*let timeInterval = Double(120 + (i * 120))
print(timeInterval)
let timeTrigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval, repeats: false)
print(timeTrigger.nextTriggerDate())*/
let request = UNNotificationRequest(
identifier: notification.id + String(i),
content: content,
trigger: trigger)
UNUserNotificationCenter.current().add(request)
}
self.getPendingRequests()
}
}
Here I am using stride to add 2 days in every iteration and when I check the dates with print statement all the dates are printed and are correct but the notification is not coming up except for the first one. Thanks in advance.
EDIT:
extension Date {
public func getDateComponents() -> DateComponents {
let dateComponents = Calendar.current.dateComponents([.hour, .minute, .second, .day], from: self)
return dateComponents
}
}
You are generally on the right track but there are a couple of problems with your implementation for the alternate days.
Date.getDateComponents()
extension function to obtain the date components. Since you didn't provide how that was implemented, there might be a problem there. According to the documentation, you should only provide those date components that are relevant for the trigger. It is possible that you are not returning the correct set of components or returning more than you need. This is probably why it is not working.Date.getDateComponents()
fetched the correct date components, it would be extremely difficult to implement a repeating Gregorian calendar-based notification trigger. This is because for every other day frequency, there is almost nothing repeating: vis-a-vis, neither days of the week, nor the days of the month are repeating for every-other-day. Some months you will be on even days and some months you will be on odd days. You almost need a special calendar where each week is defined as 14 days. In that hypothetical calendar, the days-of-the-week would be repeating.Where do we go from here? The way I see it you have 2 realistic options.
UNUserNotificationCenter
for each day in your calculation. However, there might be some limits to this. OS might not let you schedule that many one-off/non-repeating notifications into the future to prevent abuse from various apps. So you have to work around this limitation, if exists.nil
to that. You can write that completion handler such that when the completion handler fires, your code submits the next scheduled trigger to UNUserNotificationCenter. It is like daisy chaining. In this scenario, you may need to handle the scenarios when the scheduled notification does not fire, for example, when the user's device is off. But this approach might get you closer to what you were looking for.